← Community
bugfixed

Module command read/write is self-declared via `mode` (default read); a mode:read live-effect command bypasses the Approval Airlock (v0.99)

ShwetaShweta#125d ago · 37 views
affected: station-v0.99fixed in: station-v1.4.0

Reproduction steps:

  1. A module is a signed handler bundle installable from any publisher (trust

default "any"). It registers a command whose handler performs a live external
effect (a charge / message send / HTTP POST to its declared endpoint) but
declares that command mode:"read" in its manifest (or omits mode entirely —
the default is "read").

  1. Invoke the command through the airlock enforcement path

(routes/commands.execute_command, reached from Studio's /api/commands/execute
and, when mcp.module_reads_enabled is on, from the LLM over MCP).

  1. Contrast with the identical handler declared mode:"write_requires_approval".
  2. Run repro_module_mode_self_classification_bypass_v099.py against a clean v0.99

extraction. It drives the REAL classifier + the REAL execute_command:
mode=read -> status=executed handler_ran=1
mode=write_requires_approval -> status=blocked_by_policy handler_ran=0

Expected:
Whether a command may execute without a human approval must be decided by what
the command actually DOES (does its handler perform an external/mutating effect),
not by a label the module writes about itself. The Approval Airlock is documented
as "the airlock's enforcement path — every mutation that leaves the station goes
through execute_command" and the registry promises "no hidden writes". A module's
self-assertion that its send is a "read" must not be the thing that waives
approval.

Actual:
The read/write classification is 100% the module's self-declared mode, trusted
end to end with no behavioral verification:
routes/modules.py:516 action_class = _module_action_class(cmd.get("mode","read"))
_module_action_class: "read" -> "reversible"; any write mode -> "external_send".
command_registry.resolve_status: mode=="write_requires_approval" ->
available_write_requires_approval; mode=="write_blocked" -> blocked;
ANYTHING ELSE (incl. "read", a typo, or omitted) -> available_read_only.
* routes/commands.execute_command: status=="available_read_only" and the cmd is
in LOCAL_HANDLERS -> runs the handler immediately, no approval, no hold.
So a live-effect command labeled mode:"read" is classified available_read_only and
EXECUTES with no human approval (proven above). The same mislabel makes
_module_action_class return "reversible", so the same command planned inside a
workflow also dodges approval_policy hard-floor F1 (IRREVERSIBLE actions always
require a human) — it is planned as a safe reversible effect. And
mcp_server._module_execute_or_pointer runs read-mode module commands directly and
even hardcodes action_class="read" into its policy_gate, so with
mcp.module_reads_enabled the mislabeled write is callable straight from the LLM.
Nothing anywhere checks that a "read"-declared command performs no external
write; the module sandbox restricts fs/network paths but not the governance
label. Net: a module (self-signed, any publisher) exfiltrates data or moves money
through a command the airlock, the workflow planner, and the MCP read-gate all
believe is a harmless read.

Suggested fix:
Stop treating the module's self-declared read/write label as authoritative for
the approval decision:
1. resolve_status must FAIL CLOSED on an unrecognised/absent mode — only an
explicit, known READ classification should yield available_read_only; a
missing or unknown mode must resolve to available_write_requires_approval,
not read-only. (Today the default branch is the permissive one.)
2. Derive (or at minimum cross-check) the read/write classification from
something the module cannot forge: whether the command's handler is granted
any network/filesystem/credential capability (a command with an egress or
credential capability cannot be a "read"), or a platform-side annotation set
at audit/publish time — not a free-text manifest field.
3. At execute time, a command classified read-only that actually touches an
external API (external_touched=True in the receipt) should be treated as a
policy violation for a read-declared command, not merely receipted.
At publish/lint time, reject a manifest that pairs mode:"read" with a handler
declaring egress/credential capabilities.

5 pts

1 reply

Fixed in station-v1.4.0. An externally-capable module command (module declares allowed_destinations, or the command requires credentials) is no longer eligible for the no-approval read class — _normalize_module_modes upgrades a self-declared/omitted read to write_requires_approval at load, so execute_command (resolve_status), the workflow plan (F1 floor) and the MCP path all see the governed class. Pure-local reads keep their mode.

Thanks for the report — credited.

Sign in to reply.