fix(org-tokens): verified-session (human) mints skip the approval gate (core#2593) #2596

Merged
devops-engineer merged 1 commits from fix/org-token-mint-verified-session into main 2026-06-11 17:43:59 +00:00
Member

Live regression (CTO-reported twice today): canvas Settings → Org API Keys → + New Key returns the raw #2579 anchor 400. The browser authenticates via a CP-verified WorkOS session (AdminAuth sets cp_session_actor strictly after VerifiedCPSession), but approvalAnchorForGate has no session branch — #2579 assumed "the UI mints via the concierge", which is wrong: the canvas mints directly with the browser session.

Design (core#2593): the gate puts a HUMAN between an AGENT and a privileged mint. When the minter IS the human, a pending-approval that same human would approve is a no-op round-trip → verified-session callers mint directly. created_by now records the per-session actor hash (better audit than the lossy "session" constant). Agent classes (admin-token / org-token) remain fully gated; their anchor-4xx contract is unchanged.

Security: the human discriminator is cp_session_actor (set ONLY post-verification) — never the raw Cookie header, which any bearer-authed agent can forge. The pre-existing forgeable Cookie checks in orgTokenActor/orgTokenActorClass are replaced with the verified key.

Tests: Create_VerifiedSession_SkipsGate (200, ExpectationsWereMet proves zero approval SQL ran, created_by = actor) + Create_ActorFromSession repurposed as the bypass-resistance pin (junk Cookie without verification → still 400). Full handlers package green; go build -tags=integration clean.

Remaining #2593 asks (NOT this PR): CP provisioner projecting MOLECULE_PLATFORM_WORKSPACE_ID (agents-team hand-fixed), UI handling of 202 for the agent path.

🤖 Generated with Claude Code

**Live regression (CTO-reported twice today):** canvas Settings → Org API Keys → *+ New Key* returns the raw #2579 anchor 400. The browser authenticates via a **CP-verified WorkOS session** (`AdminAuth` sets `cp_session_actor` strictly after `VerifiedCPSession`), but `approvalAnchorForGate` has no session branch — #2579 assumed "the UI mints via the concierge", which is wrong: the canvas mints directly with the browser session. **Design (core#2593):** the gate puts a HUMAN between an AGENT and a privileged mint. When the minter IS the human, a pending-approval that same human would approve is a no-op round-trip → verified-session callers mint directly. `created_by` now records the per-session actor hash (better audit than the lossy `"session"` constant). Agent classes (admin-token / org-token) remain **fully gated**; their anchor-4xx contract is unchanged. **Security:** the human discriminator is `cp_session_actor` (set ONLY post-verification) — never the raw Cookie header, which any bearer-authed agent can forge. The pre-existing forgeable Cookie checks in `orgTokenActor`/`orgTokenActorClass` are replaced with the verified key. **Tests:** `Create_VerifiedSession_SkipsGate` (200, `ExpectationsWereMet` proves zero approval SQL ran, created_by = actor) + `Create_ActorFromSession` repurposed as the bypass-resistance pin (junk Cookie without verification → still 400). Full handlers package green; `go build -tags=integration` clean. Remaining #2593 asks (NOT this PR): CP provisioner projecting `MOLECULE_PLATFORM_WORKSPACE_ID` (agents-team hand-fixed), UI handling of 202 for the agent path. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
core-devops added 1 commit 2026-06-11 17:38:51 +00:00
fix(org-tokens): verified-session (human) mints skip the approval gate (core#2593)
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 4s
CI / Python Lint & Test (pull_request) Successful in 6s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 5s
E2E API Smoke Test / detect-changes (pull_request) Successful in 7s
Harness Replays / detect-changes (pull_request) Successful in 5s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 7s
Harness Replays / Harness Replays (pull_request) Successful in 2s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 6s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 6s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 3s
sop-checklist / review-refire (pull_request_target) Has been skipped
reserved-path-review / reserved-path-review (pull_request_target) Successful in 5s
CI / Detect changes (pull_request) Successful in 19s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: comprehensive-testing, local-postgres-e2
sop-checklist / na-declarations (pull_request) N/A: (none)
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 16s
sop-checklist / all-items-acked (pull_request_target) Successful in 10s
CI / Canvas (Next.js) (pull_request) Successful in 1s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 1s
CI / Canvas Deploy Status (pull_request) Successful in 1s
gate-check-v3 / gate-check (pull_request_target) Failing after 18s
E2E Chat / detect-changes (pull_request) Successful in 25s
E2E Chat / E2E Chat (pull_request) Successful in 4s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (stub) (pull_request) Successful in 40s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 48s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (real image + MiniMax LLM, advisory) (pull_request) Failing after 26s
qa-review / approved (pull_request_target) Approved via pull_request_review trigger
qa-review / approved (pull_request_review) Successful in 4s
security-review / approved (pull_request_target) Approved via pull_request_review trigger
security-review / approved (pull_request_review) Successful in 5s
reserved-path-review / reserved-path-review (pull_request_review) Successful in 9s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2m24s
CI / Platform (Go) (pull_request) Successful in 4m15s
CI / all-required (pull_request) Successful in 2s
audit-force-merge / audit (pull_request_target) Successful in 5s
575789ca3c
The canvas Settings -> Org API Keys "+ New Key" returns the raw anchor
400 from #2579: the browser authenticates via a CP-VERIFIED WorkOS
session (AdminAuth sets cp_session_actor strictly after
VerifiedCPSession), but approvalAnchorForGate has no session branch —
the #2579 assumption "the UI mints via the concierge" was wrong; the
canvas mints directly with the browser session.

Design (per core#2593): the gate exists to put a HUMAN between an
AGENT and a privileged mint. When the minter IS a human at the
dashboard, a pending-approval the same human would approve is a no-op
round-trip — verified-session callers now mint directly (audited:
created_by records the session actor hash instead of the lossy
"session" constant). Agent classes (admin-token, org-token) remain
fully gated; the anchor 4xx contract is unchanged for them.

SECURITY: the human discriminator is cp_session_actor (set only
post-verification), NEVER the raw Cookie header — a bearer-authed
agent attaching a junk Cookie cannot bypass the gate. The previously
forgeable Cookie checks in orgTokenActor/orgTokenActorClass are
replaced with the verified key (they only affected audit labels, but
the same sloppy pattern next to a gate-skip would have been a hole).

Tests: TestOrgTokenHandler_Create_VerifiedSession_SkipsGate (200, no
approval SQL, created_by = actor — ExpectationsWereMet proves the gate
did not fire) + ActorFromSession repurposed as the bypass-resistance
pin (junk Cookie without verification -> still 400). Full handlers
package green; go build -tags=integration clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
agent-reviewer-cr2 approved these changes 2026-06-11 17:40:47 +00:00
agent-reviewer-cr2 left a comment
Member

5-axis review on head 575789ca3c. Correctness: fixes the #2579 regression by allowing only CP-verified browser-session callers to mint directly, while admin-token/org-token callers still go through approvalAnchorForGate and gateDestructive. Robustness: empty-anchor handling remains fail-closed for non-session callers; created_by now records the stable session actor and the raw-cookie path still returns controlled 400. Security: the bypass is keyed on cp_session_actor, which AdminAuth sets only after VerifiedCPSession succeeds; raw Cookie classification was removed, and the bypass-resistance test pins that forged/junk cookies do not skip the gate. Agent classes remain gated. Performance: no new expensive calls on the handler path; verified-session work is already done by middleware. Readability: helper and comments make the human-vs-agent distinction explicit; tests cover the direct human mint path and forged-cookie non-bypass. Handlers-PG is green; broader CI was still settling at review time. Approved as agent-reviewer-cr2.

5-axis review on head 575789ca3c775c8853e184762ed18e6f2cea209d. Correctness: fixes the #2579 regression by allowing only CP-verified browser-session callers to mint directly, while admin-token/org-token callers still go through approvalAnchorForGate and gateDestructive. Robustness: empty-anchor handling remains fail-closed for non-session callers; created_by now records the stable session actor and the raw-cookie path still returns controlled 400. Security: the bypass is keyed on cp_session_actor, which AdminAuth sets only after VerifiedCPSession succeeds; raw Cookie classification was removed, and the bypass-resistance test pins that forged/junk cookies do not skip the gate. Agent classes remain gated. Performance: no new expensive calls on the handler path; verified-session work is already done by middleware. Readability: helper and comments make the human-vs-agent distinction explicit; tests cover the direct human mint path and forged-cookie non-bypass. Handlers-PG is green; broader CI was still settling at review time. Approved as agent-reviewer-cr2.
devops-engineer merged commit 035123bf42 into main 2026-06-11 17:43:59 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#2596