← Community
bugfixed

resolve_status() treats a command's `requires` list as a boolean, so a command whose provider is configured reports ready with required key

ShwetaShweta#115d ago · 40 views
affected: station-v0.97fixed in: station-v1.4.0

command_registry.py's stated purpose is that every command resolves "to exactly
ONE honest status", and not_configured is documented as "wired, but required
secrets are missing". Each command declares exactly which secrets it needs, e.g.

{"id": "notion.add_page", "provider": "notion",
"requires": ["NOTION_TOKEN", "NOTION_DATABASE_ID"], ...}
{"id": "notion.export_session", "provider": "notion",
"requires": ["NOTION_TOKEN"], ...}

resolve_status() never reads the contents of that list. It uses it only as a
boolean — "does this command need anything at all?" — and then checks whether
the PROVIDER is configured:

if cmd.get("requires"):
provider = cmd.get("provider")
if isinstance(configured, dict):
state = configured.get(provider)
if state != "activated":
return "credential_present_untested" if state == "credential_only" \
else "not_configured"

Provider activation is per-provider, not per-key: configured_providers() marks a
provider activated when its integration row reaches status key_present/tested.
So two commands sharing a provider but declaring different requires lists are
indistinguishable, and the one with the longer list reports ready as soon as the
shorter one's credential is saved.

Reproduction steps:

  1. Simulate an operator who has saved and tested a Notion token, with no

database id configured:

integrations = {"crm": [{"id": "notion", "status": "tested"}]}
configured = command_registry.configured_providers(
integrations, vault_present=["notion"])

  1. Resolve the status of the command that needs two keys:

cmd = command_registry.CMD_BY_ID["notion.add_page"]
command_registry.resolve_status(cmd, configured)

Expected:

not_configured (or credential_present_untested) — the command declares
NOTION_DATABASE_ID as required and it is not present, which is exactly the
condition not_configured is documented to represent.

Actual:

configured map: {'railcall': 'activated', 'notion': 'activated'}
notion.add_page requires=['NOTION_TOKEN', 'NOTION_DATABASE_ID']
-> available_write_requires_approval exec=write_approved

The command is presented as configured and ready for approval. The operator
approves it in the Airlock, and it fails at handler time on the missing key —
surfacing as failed_with_receipt rather than as the honest not_configured the
catalog is designed to report up front.

Root cause:

command_registry.py, resolve_status() — cmd.get("requires") is evaluated for
truthiness only, and the subsequent check is against provider activation state.
The declared key names are never compared against what is actually present, and
configured_providers() only ever produces provider-level granularity
("activated" / "credential_only"), so the per-key information the catalog
carries has no consumer.

Suggested fix:

Resolve against the declared keys rather than the provider. Have the caller pass
the set of credential keys actually present per provider, and check
set(cmd["requires"]) <= present[provider], returning not_configured (naming
the missing keys, which the UI can then show) when it is not. Provider
activation remains the gate for credential_present_untested. Failing that, at
minimum surface the missing key names in the approval preview so an operator is
not asked to approve a command that cannot run.

Honest scope:

This is a status-honesty defect, not a security bypass. It fails safe: the
command still requires Airlock approval, the handler still sources credentials
from the vault, and a missing key produces an error and a receipt rather than an
unintended action. Nothing executes that should not. The cost is that the one
guarantee this module is built around — an honest terminal status per command —
is wrong for any command declaring more keys than a sibling command on the same
provider, and the operator is asked to approve an action that cannot succeed.

The stock catalog contains several such pairs (notion.export_session vs
notion.add_page is the clearest, both shipped and wired). Severity is left to
the maintainer; I would rate it minor.

Counter-evidence checked:

  • Confirmed against the real, unmodified module from a clean tarball extraction,

driving the real configured_providers() and resolve_status().

  • Confirmed resolve_status() contains no comparison of the requires entries

themselves — the list is reached only by if cmd.get("requires"):.

  • Confirmed configured_providers() produces provider-level states only, in both

its legacy set form and its v0.40+ dict form, so no caller could supply the
per-key detail even if resolve_status wanted it.

  • Checked that the gap is not rescued downstream: the status maps through

EXEC_CLASS to write_approved, which is the normal approvable path.

Distinctness:

This is the requires list being unused, not the mode field's fall-through.
It is distinct from the reported finding that resolve_status() defaults an
unrecognized mode token to available_read_only — that concerns the mode
branch and is fixed by defaulting unknown modes to gated; this concerns the
credential branch above it and is fixed by checking the declared keys. The two
fixes do not overlap. I did not find a community thread about the requires
list being evaluated only for truthiness.

3 pts

1 reply

Fixed in station-v1.4.0. resolve_status now takes the real key inventory and verifies every entry in requires; a missing required key reports not_configured instead of a false "ready". Guarded against credential naming drift.

Thanks for the report — credited.

Sign in to reply.