Reproduction steps:
- Run a clean Station v0.97 workspace with a valid signed Team manifest and
its root key available for the normal Team policy ceremony.
- Adopt a signed Team policy bundle v1 requiring quorum=2 for
action_class=external_send:
{
"action_classes": {
"external_send": {"role": "approver", "quorum": 2}
}
}
- Confirm the applied
team/approval_policy.jsoncontains the
external_send requirement and that a workflow team gate with no
satisfied approval returns blocked/pending, not proceed.
- Using the normal signed root-key policy bundle path, create and adopt a
strictly newer bundle v2 whose requires_team_approval map is empty:
{}
- Inspect the applied policy and run the same synthetic workflow through the
normal build_workflow_team_gate() + run_workflow() path with a harmless
HTTP mock (no socket and no provider write).
Expected:
The v1 Team policy contract explicitly says policy bundles may only ADD
control, never remove it. A newer bundle that deletes the existingexternal_send requirement must be rejected, or at minimum must not weaken
the effective local policy. A workflow that still requires the v1 quorum must
remain blocked/pending until the stronger requirement is satisfied.
Actual:
team_policy.adopt_bundle() accepts the signed v2 bundle and returns:
(True, 'adopted policy bundle v2 (applied to approval gate)')
It rewrites team/approval_policy.json without the previousexternal_send rule. The workflow team gate then returns proceed with no
approval, and the normal workflow engine reaches the HTTP effect executor.
The harmless mock was called once and the run completed:
strong_adopt -> True
strong_gate_no_approval -> blocked
weak_adopt (signed v2) -> True
weak_gate -> proceed
mock_run -> COMPLETED, calls=1
Control:
The same v2 versioning and signature path accepts a stronger requirement and
the v1 gate correctly refuses an unapproved external-send workflow. The
weakening behavior is deterministic and is reproduced repeatedly with fresh
workspaces.
Station version (railcall version):
station-v0.97
Affected code path:
workbench/primitives/team_policy.py, adopt_bundle() (approximately
lines 172–193) verifies the root signature and checks only that the version
is newer. It never compares the new requires_team_approval map with the
currently adopted map. The code immediately replacesteam/approval_policy.json at lines 186–192.
The contract is stated in the same file's policy specification (lines 10–20):
"A bundle may only ADD control, never remove it" and adoption must enforce
monotone tightening. The Team policy design also says the applied bundle is
the map read by the live gate.
Security / integrity impact:
A valid newer Team policy update can silently disable a previously mandatory
quorum gate. A workflow can therefore reach its external-effect executor
without the approval requirement that was active before the update. The
reproduction used only a signed synthetic bundle and a harmless HTTP mock;
no network request, credential, or financial write was made.
This is a governance-policy bypass, not merely a display or metadata issue.
Deterministic: Yes.
Counter-evidence checked:
- The bundle is signed and correctly bound to the adopted Team root key.
- Version monotonicity still works; the problem is that a higher version is
allowed to weaken the policy.
- The normal strong-policy control blocks the same workflow without approval.
- The live workflow gate reads the weakened applied map and has no downstream
check that restores the removed requirement.
- This does not require filesystem tampering or a forged signature; it uses
the normal policy-publish/adopt contract.
- The documented "root key is the Team authority" rule does not make this
intentional: the policy contract explicitly forbids removing control from a
newer bundle.
Suggested fix:
During adopt_bundle(), compare every new requirement with the currently
adopted requirement map and reject any removal, lower quorum, or role
weakening. Treat missing entries as weaker than existing entries. Preserve
the monotonic version check and sign/verify the resulting effective map.
AFFECTED VERSION:
station-v0.97