2026-09-23·by Sijie Wang#standmeet#architecture#design

coded-landing-and-code-rotation

Coded landing /c/<slug> and rotating the code string

Parent: access-control · Sibling: embed-credential-never-carries-the-code

Verdict (2026-09-07): an access code has three separable parts. The row id is what every link is keyed on (embeds, applications, members, denials). The slug is a snowflake short id that names the code's landing path /c/<slug> — a locator, never a credential. The code string (LABEL-<16 hex>) is the only credential, and it is the only part codes.rotate replaces. The landing page absorbs ?code= out of the address bar on first paint, so the plaintext string lives on paper (QR — quick-response — codes, PDFs) and in the session store, not in URLs, history, or Referer headers.

Motivating problem. The résumé PDF (resume-composer-one-renderer) prints <public_url>/?code=<code> in its QR. That is a credential in a URL: it lands in browser history, in the Referer of every cross-origin subresource, in screenshots. And a code leaks as a whole — recruiters forward PDFs. Before 2026-09-06 the only recovery was codes.revoke, which also killed the code's id-keyed links (embeds, past applications, member list). Two gaps: the plaintext string was addressable, and leak recovery cost the whole row.

The slug is a locator, not a credential

  • Generator. backend/internal/infra/snowflake/snowflake.go:7-8: 41 bits of ms since epoch, 10 bits node, 12 bits sequence; Slug() (:71) base62-encodes it (0-9a-zA-Z, at most 11 chars). Process-wide node in access/repo/codes_slug.go:7-21; stamped at the single creation point access/repo/codes.go:84.
  • Owner override. DeriveSlug(provided, generated) (access/entity/code_derive.go:69-75): lower-cased [a-z0-9-_], max 32; reserved names (home, gate, admin, api, setup, login, p, wiki, output, assets) fall back to the snowflake (:61-64). The owner's HIRING-2026 reads as /c/hiring-2026.
  • Storage. access_codes.slug citext NOT NULL (schema.sql:354), unique per owner (:357). Migration 2026-09-06-access-code-slug.sql:10-13: add nullable → backfill from the row id → NOT NULL → unique index, so an upgraded instance keeps every existing code.
  • No server-side resolution. app/src/app/c/[slug]/page.tsx:28-33 never reads the slug param: it renders VisitorRoot with hasCode={false}, and authorization comes only from the stored session. Nothing in backend/internal/access looks a code up by slug. Guard: coded-landing-slug.spec.ts:41-51 (D3) visits /c/some-locator with no session and asserts the default home with zero chat inputs.

Landing: absorb, rewrite, route

  • Absorb. app/src/lib/gate/use-absorb-code.ts:55-58 deletes code from the query and calls history.replaceState before the session request — the string is gone from the address bar before anything else runs.
  • Rewrite. The issue response carries slug and microsite_slug (routes/public/sessions.go:62-65); use-issue-pending-code.ts:124 calls applyLanding(landAfterIssue(...)). app/src/lib/visitor/code-landing.ts:25-40: a bound microsite (access_codes.microsite_id, schema.sql:344, ON DELETE SET NULL at :756-759, set by codes.set_microsite, access/ops/codes.go:70-78) means a full navigation to /p/<microsite-slug> — a microsite is a separate build artifact; otherwise the visitor stays in the already-rendered chat and only the URL becomes /c/<slug>. Guard: coded-landing-slug.spec.ts:28 (D1) asserts the URL matches /c/[^/?#]+$ with no code=.
  • Referer. Absorbing runs in JS (JavaScript); the first paint precedes it. app/next.config.ts:122-125 sets Referrer-Policy: strict-origin-when-cross-origin on every path (fc841f41f, 2026-09-01), so a cross-origin subresource loaded during that first paint sees the origin only. Guard: security-referrer-policy.spec.ts:18-25.
  • Reload. /c/<slug> on reload is served by the visitor session in localStorage; the slug is a bookmark for a session the browser already holds.

A new code switches; an invalid code lands on /gate

app/src/app/visitor-root.tsx:71pending ? 'picker' : viewFor(isChatSession(session), hasCode): a ?code= arriving while an old session exists opens the identity picker for the new code instead of silently keeping the old chat (the owner's case: a recruiter who already visited under one code scans a second résumé). A 401 on issue becomes 'invalid' (use-issue-pending-code.ts:141-144) and VisitorNamePicker.tsx:81-89 sends the visitor to /gate. 230a17025 (2026-09-06), unit-tested in visitor-root.test.ts.

codes.rotate — what survives, what dies

The SQL is one column: UPDATE access_codes SET code = \$3 WHERE id = \$1 AND owner_id = \$2 RETURNING * (db/queries/access/access_codes.sql:33-36). Then the usecase purges sessions (access/usecase/codes.go:124-126, d.Sessions.DeleteByCode — the same Redis purge RevokeCode uses at :103; visitor_session.go:124-139).

survives (nothing else is written)dies
row id and slug/c/<slug> keeps resolvingthe old plaintext string — every printed QR, emailed PDF, shared ?code= link
assumed_role_id and the frozen RoleSnapshot (role-snapshot-frozen)every live visitor session under this code (Redis code:<id> set, each token deleted)
code_*_denials, code_members, quotas, expiry (acl-and-quota-granularity)
microsite_id, embeds (embeds.code_id), application rows — all keyed by id
  • New string must be non-empty and free of whitespace and /?#& (codes.go:134-136, else ErrCodeInvalid); a citext collision → ErrCodeTaken → 409 (access/repo/codes_rotate.go:41-48).
  • Admin: PATCH /codes/{id}/code (routes/admin/codes.go:51, use-codes.ts:117-123) behind a warning modal (admin-access.json:33-37, 8 locales): "Rotating the code makes every copy already handed out stop working — résumé PDFs and QRs you've already sent, shared ?code= links — and signs out anyone using it now. Embeds and past applications keep working (they're bound by id). This can't be undone." MCP: codes.rotate {code_id, code} (access/ops/codes_write.go:105-127).
  • Guards: code-rotation.spec.ts:46-72 (id unchanged, old string refused, 409 on collision) and code-change-ui.spec.ts:31-48 (modal + confirm).

Entropy and the issuance log

DeriveCode (access/entity/code_derive.go:47-56) = upper-cased label ([A-Z0-9], max 12, default CODE) + - + 8 random bytes as 16 upper-case hex chars: 64 bits of suffix (1ca9d9564, 2026-09-01, after the pentest that found the old 16-bit suffix). If crypto/rand fails the suffix is sixteen 0s — a visible sentinel, not a weak code that looks normal (:146). IssueCode logs access code issued with owner_id, code_id, slug, role_id, label — never the string (access/usecase/codes.go:60-63, 0e6104a6f, 2026-09-07), so a slug seen in prod traces back to its issuance.

The honest ceiling

  • The QR still carries the plaintext. A recruiter with no session needs a credential; the slug cannot stand in for it (D3 proves it grants nothing). Absorb-and-rewrite shortens the string's exposure to the first paint; it does not remove the string from paper. Rotation is the recovery, and it is deliberately total.
  • Rotation kills sessions, and no e2e asserts that. code-rotation.spec.ts:8-9 defers the purge to visitor_session_revoke_test; the modal-copy assertion is a loose regex. A stale comment in codes_rotate.go:17-18 claims live sessions are untouched — true of the SQL, false of the operation; trust usecase/codes.go:109-112.
  • The slug is per owner, not global. Uniqueness is (owner_id, slug); /c/<slug> is unambiguous only because a v1 instance is single-owner. Multi-tenant routing needs the handle in the path.
  • Switching is unit-tested only. 230a17025 added view-selection cases to visitor-root.test.ts; no browser spec drives "second QR after a first session" end to end.

Built 2026-09-01 → 2026-09-07. Referrer-Policy fc841f41f; entropy 1ca9d9564 (backend/internal/access/entity/code_derive.go); snowflake dbb803288 (backend/internal/infra/snowflake/snowflake.go); slug column 4e383c2a7 + migration 2026-09-06-access-code-slug.sql; landing path c6c54ce88 (app/src/lib/visitor/code-landing.ts, app/src/app/c/[slug]/page.tsx); spec 1b 59f83db86 (e2e/test/coded-landing-slug.spec.ts); session switch 230a17025; RotateCode 505b3fc4f (backend/internal/access/usecase/codes.go, access/repo/codes_rotate.go, e2e/test/code-rotation.spec.ts); admin modal 6ed1759bb (e2e/test/code-change-ui.spec.ts); issuance log 0e6104a6f. The chain this serves: chain-selective-access.

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →