diff --git a/docs/ADDING_PROTOCOLS.md b/docs/ADDING_PROTOCOLS.md index 0091f80..1421d14 100644 --- a/docs/ADDING_PROTOCOLS.md +++ b/docs/ADDING_PROTOCOLS.md @@ -224,7 +224,106 @@ async rewrites() { }, ``` -## 7. Optional: Looking Glass executor (for real execution) +## 7. Parameter explainers and references + +Two UI surfaces give learners security context for the new protocol: per-parameter explainer panels (clickable `(?)` icons beside flow parameters) and the "Specs & references" panels on the protocol overview and flow detail pages. Both are populated from data files; adding a new protocol means adding entries on both surfaces. + +### 7.1 Per-parameter explainers + +Create `frontend/src/protocols/explainers/.ts` exporting a map keyed by parameter name: + +```ts +import type { ParameterExplainer } from './index' + +export const NEWPROTOCOL_EXPLAINERS: Record = { + param_name: { + purpose: 'What this parameter does and why it exists.', + attacks: [ + { + id: 'named-attack', + name: 'Named attack pattern (CVE or research label)', + scenario: 'How an attacker exploits the parameter when it is absent or mishandled.', + impact: 'What the attacker achieves.', + }, + ], + mitigations: [ + { + action: 'Concrete mitigation step.', + rationale: 'Optional one-line reason this works.', + mitigates: ['named-attack'], + }, + ], + references: [ + { label: 'RFC/spec section', href: 'https://...' }, + ], + }, +} +``` + +Then register the map in `frontend/src/protocols/explainers/index.ts` by adding an import and spreading it into the `EXPLAINERS` record: + +```ts +import { NEWPROTOCOL_EXPLAINERS } from './newprotocol' + +const EXPLAINERS: Record = { + ...OAUTH2_EXPLAINERS, + // ... + ...NEWPROTOCOL_EXPLAINERS, +} +``` + +Notes: + +- Mitigations link back to attacks via `mitigates: string[]` of attack IDs. A single mitigation may cover several attacks. +- For parameters whose semantics differ across protocols (for example, `nonce` in OIDC versus OID4VP), use the override key form `${protocolId}:${name}` instead of the bare name. +- Source content from canonical specs and named real-world incidents (CVE numbers, research disclosures). Avoid universal-baseline mitigations such as "use HTTPS"; reviewers can be assumed to know those. +- See any existing protocol's explainer file (for example `oauth2.ts`) for a worked reference of tone, length, and structure. +- The map keys must match the parameter names emitted by the backend's `FlowStep.Parameters` map (see section 3). Mismatched names will silently fail to render an explainer. + +### 7.2 Per-protocol references panel + +Update `frontend/src/protocols/presentation/protocol-catalog-data.ts` to add a `references` array on the protocol's catalog entry: + +```ts +{ + id: 'newprotocol', + name: 'New Protocol', + // ...existing fields + references: [ + { category: 'core', label: 'Core spec name', href: 'https://...' }, + { category: 'security', label: 'Security & privacy considerations', href: 'https://...' }, + { category: 'companion', label: 'Companion RFC', href: 'https://...' }, + { category: 'profile', label: 'Deployment profile', href: 'https://...' }, + ], +} +``` + +The categories render as separate sections in the UI: + +- `core`: specs that normatively define the protocol. +- `security`: dedicated security and privacy considerations documents (or deep-anchored security sections of core specs). +- `companion`: extension RFCs and related standards the protocol composes with. +- `profile`: deployment profiles that constrain the protocol for specific assurance regimes (FAPI, HAIP, eIDAS, and so on). + +### 7.3 Per-flow references panel + +In the same file, add a `references` array to each flow definition. Prefer deep-anchored URLs to specific sections rather than spec landing pages: + +```ts +{ + id: 'flow-id', + name: 'Flow Name', + rfc: '§X.Y', + references: [ + { category: 'core', label: 'Spec §X.Y - Section title', href: 'https://...#anchor' }, + { category: 'security', label: 'Security considerations §Z', href: 'https://...#anchor' }, + ], +} +``` + +The same `ProtocolReferences` component renders both surfaces, so flow references should be focused (typically two to four per flow) and non-overlapping with the protocol-level reading list. + +## 8. Optional: Looking Glass executor (for real execution) If your flow is executable, implement a flow executor. diff --git a/frontend/package.json b/frontend/package.json index f612d52..3fb3d23 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "build": "next build", "start": "next start", "lint": "eslint src --report-unused-disable-directives --max-warnings 0", + "verify-refs": "node scripts/verify-references.mjs", "generate-og-image": "node scripts/generate-og-image.mjs" }, "dependencies": { diff --git a/frontend/scripts/verify-references.mjs b/frontend/scripts/verify-references.mjs new file mode 100644 index 0000000..62bfdae --- /dev/null +++ b/frontend/scripts/verify-references.mjs @@ -0,0 +1,418 @@ +#!/usr/bin/env node +// Verifies every {label, href} reference shipped in protocol-catalog-data.ts +// and src/protocols/explainers/*.ts against the live spec document. +// +// Three classes of bug this catches that tsc/eslint can't: +// 1. Dead URLs (HTTP != 200). +// 2. Broken anchors — href has a #fragment that no longer exists on the page, +// e.g. after a spec rev renamed the section. +// 3. Drift between the label and where the anchor actually lands — e.g. +// label says "§11" but the anchor sits in §13, or label says "Refresh +// Token Protection" but the heading reads "TLS Terminating Reverse +// Proxies". This is the failure mode that tripped this codebase up most. +// +// Usage: npm run verify-refs +// npm run verify-refs -- --only=oid4vp filter by file basename +// npm run verify-refs -- --strict exit non-zero on warnings too + +import { readFile, readdir } from 'node:fs/promises' +import { fileURLToPath } from 'node:url' +import path from 'node:path' + +const HERE = path.dirname(fileURLToPath(import.meta.url)) +const ROOT = path.resolve(HERE, '..') +const FILES_TO_SCAN = [ + path.join(ROOT, 'src/protocols/presentation/protocol-catalog-data.ts'), +] + +const args = process.argv.slice(2) +const onlyArg = args.find((a) => a.startsWith('--only=')) +const onlyFilter = onlyArg ? onlyArg.split('=')[1] : null +const strict = args.includes('--strict') + +// Two ref shapes exist in the codebase: +// - protocol-catalog-data.ts: { category, label, href, note? } objects +// - explainer files: { label, href } pairs nested inside Reference[] +// REFERENCE_REGEX matches the catalog form and is tried first; SIMPLE_REF_REGEX +// is the fallback for explainer refs. We track match offsets so the second pass +// doesn't double-count refs already captured by the first. +const REFERENCE_REGEX = + /\{\s*category:\s*'(?[^']+)'\s*,\s*label:\s*'(?