The Approval Airlock's own approve/execute endpoints require
no session token -- any local caller can self-approve and fire any
registered write command with zero proof a human was involved
The Approval Airlock's stated contract, repeated across dozens of files in
this codebase, is that a write action requires an explicit human decision
before it can execute -- "hard-floor require_human," "never
auto-approved," "the operator approves the blast radius." The pipeline
that actually mints that human decision has no check that a human, or
even an authenticated Studio session, was involved at all.
Every write-capable route in the same dispatch file requires a session
token first (/api/build, /api/flow/run, /api/flow/automap, every
cap-off connector's stage/approve pair). The four routes that make up the
Approval Airlock's own command pipeline do not:
/api/commands/validate -> validate_command(command_id, inputs, claimed, intent)
/api/commands/preview -> preview_command(command_id, inputs, intent)
/api/commands/approve -> approve_command(command_id, inputs, method, intent)
/api/commands/execute -> execute_command(command_id, inputs, intent)
None of these four functions receive the request handler at all, so none
of them can check a session token even in principle. approve_command()'s
own docstring is "Bind a human approval to the EXACT payload" -- but themethod parameter it takes ("ui_click" or "terminal_confirm") is a bare
string the caller supplies; nothing verifies the claim. Binding an
approval is just:
airlock.bind_approval(command_id, inputs, method, stamp)
-> {"method": method, "timestamp": stamp,
"approved_payload_hash": payload_hash(command_id, inputs)}
No identity, no session, no channel -- just a hash of the payload the
caller itself provided.
This matters because the platform elsewhere goes out of its way to
distinguish a human channel from an automated one specifically for
approval purposes: primitives/operator_identity.py's HUMAN_CHANNELS
excludes the scheduler and MCP channels on purpose ("the scheduler
channel can NEVER carry a human actor... would manufacture accountability
for a decision nobody made"), and routes/team.py's session gate refuses
the MCP channel outright for team actions ("the ceremony belongs to
humans"). The command-execution pipeline -- covering every registered
write command in the catalog, including real money-moving actions like a
Stripe refund -- makes no such distinction. Anything that can reach the
loopback port with a POST carrying a loopback Origin or Referer header
(satisfied by any local process setting its own headers, not just a real
browser tab -- the MCP sidecar, a CLI script, a compromised local process)
can call approve then execute for any command, with nothing standing in
for "a human clicked this."
Reproduction steps:
- Extract a clean station-v0.74 tarball, sys.path.insert(0, "workbench"),
boot studio_server against a scratch workspace.
- Build a stub
handlerobject with only a_send()method -- no
_require_session() at all, so if the dispatch path ever tried to call
it, this would raise AttributeError instead of silently succeeding.
- Call routes.dispatch_cap_off_wave3.try_dispatch() directly for
"/api/commands/preview", then "/api/commands/approve"
(method="ui_click"), then "/api/commands/execute" -- the exact
functions the real HTTP routes call -- for a real registered write
command (local.file_write).
Expected: approve and/or execute refuse without a valid session token,
the way every other write-capable route in the same file does.
Actual: preview returns requires_approval=True; approve returns
ok=True, status="approved_not_executed" with a bound approval; execute
consumes that approval and actually runs the write -- the receipt shows
result_status="executed", execution_class="executed", and the target file
is genuinely written to disk with the exact requested content. All three
calls succeeded through a handler object that structurally could not have
passed a session check, because it was never asked for one.
Root cause: routes/dispatch_cap_off_wave3.py's try_dispatch() dispatches
"/api/commands/validate", "/api/commands/preview", "/api/commands/approve",
and "/api/commands/execute" without a handler._require_session() call --
unlike every other branch in the same function. approve_command() and
execute_command() (studio_server.py / routes/commands.py) take no handler
argument and have no other identity/session parameter, so the omission
cannot be patched anywhere except the dispatch layer.
Suggested fix: require handler._require_session() (or an equivalent
explicit human-channel check, consistent with how team.py and
operator_identity.py already distinguish human from automated channels)
before /api/commands/approve and /api/commands/execute -- at minimum
before approve, since that is the step that mints the "human decided"
claim every downstream receipt and audit trail relies on.