Skip to content

2026-05-10 — Per-user auth via Airlock cross-apex handoff (TASKSET 4)

status: published
mr: MR-7

The petrova.host dashboard previously had no user-level authentication. All act mutations were attributed to the anonymous actor "human:dashboard", making it impossible to audit which operator submitted a given act PR. The Vercel Password Protection that had been gating the surface was a deployment-level secret, not an identity.

TASKSET 4 introduces per-operator identity as the fourth step in the governance-completeness plan, unlocking multi-operator use and threaded actor attribution on every act PR.

Use Airlock’s cross-apex JWT handoff (flow F9, GET /api/auth/handoff?return=<callback>) rather than GitHub OAuth or a standalone OIDC client registration.

Why F9 and not standard OIDC (authorization_code)?
F9 requires only a one-time row insertion in the handoff_consumers table (via Hatch) rather than provisioning an OAuth client secret, storing it in Vercel, and implementing PKCE. The handoff JWT has a 60-second TTL so there is no refresh-token lifecycle to manage. The protocol is already battle-tested by stratt.dev and other cross-apex consumers in the fleet.

Why not GitHub OAuth (original spec)?
Airlock is the canonical identity provider for the devarno ecosystem. GitHub OAuth would introduce a second IdP for the same operator population, fragment audit logs across two providers, and require separate credential rotation. Centralising on Airlock is the right architectural choice.

Session model:
After the handoff callback verifies the EdDSA JWT against Airlock’s JWKS, a __petrova_session HS256 cookie (7-day TTL, signed with SESSION_SECRET) is issued. Middleware validates this cookie on every request to /console/** and /api/act, setting Astro.locals.user. No server-side session store is needed.

Role gate:
Only Airlock users with role === "admin" are admitted. Any other authenticated user is redirected to /denied. This matches hatch’s model and ensures the control-plane dashboard is not accessible to non-operator airlock accounts (e.g. SMO1 end users who share the same Airlock instance).

Actor attribution:
/api/act reads locals.user.email and injects actor: "human:<email>" into every RPC params object. Act PRs created via the dashboard now carry a per-user actor string visible on /console/acts. Unauthenticated calls to /api/act return 401 (belt-and-suspenders — middleware blocks them first).

  • GitHub OAuth — rejected (two IdPs, separate credential lifecycle, no audit log convergence).
  • Airlock standard OIDC (authorization_code + PKCE) — valid but heavier; requires OAuth client provisioning and refresh-token handling. No incremental benefit over F9 for an operator-only surface.
  • API key per operator — stateless but cannot leverage Airlock’s existing session UX or TOTP/passkey 2FA.
  • positive: Act PRs carry actor: "human:<email>" — full attribution in petrova.acts.recent and on GitHub PR metadata.
  • positive: No new credential type. Operators reuse their existing Airlock admin session (including 2FA if enrolled).
  • positive: Logout clears only the __petrova_session cookie; the Airlock session itself remains active for other devarno surfaces.
  • constraint: https://petrova.host must be registered in the handoff_consumers table before the first production deploy. See docs/runbooks/petrova-host-airlock-setup.md.
  • constraint: Preview deployments cannot use the live handoff flow. AUTH_DISABLED=true must be set in Vercel Preview environment for preview workflows to function.
  • neutral: SESSION_SECRET rotation logs out all active sessions simultaneously. Document and schedule rotations.
VarDefaultRequired
SESSION_SECRETyes (≥32 chars)
AIRLOCK_URLhttps://airlock.devarno.cloudno
PETROVA_HOST_ORIGINhttps://petrova.hostrecommended explicit
  • dashboard/src/middleware.ts (new)
  • dashboard/src/pages/api/auth/callback.ts (new)
  • dashboard/src/pages/api/auth/logout.ts (new)
  • dashboard/src/pages/api/act.ts (modified — actor injection + 401 guard)
  • dashboard/src/layouts/Shell.astro (modified — user chip + logout)
  • docs/runbooks/petrova-host-airlock-setup.md (new)
  • Airlock F9 flow: airlock/src/routes/handoff.ts
  • Related: atlas/findings/2026-04-18-stratt-dev-cross-apex-diagnose.md