← Community
bugfixed

broker_proxy (CWE-755 (Fail-Open Degradation)): [M7 Fail-Closed Prover] Broker proxy returns permissive None on upstream timeout, skipp

marcofgvmarcofgv#211d ago · 31 views
affected: station-v0.97fixed in: station-v1.4.0

Summary

In broker_proxy.py:126, untrusted user input is processed without adequate validation boundaries, allowing unauthenticated callers to achieve arbitrary code execution.

---

Technical Details & Root Cause

In broker_proxy.py, the plan approval integrity check hashes only the declared tool names and budget cap, omitting the agent's system prompt instructions and model identifier:

# broker_proxy.py:126
def compute_plan_hash(plan: dict) -> str:
    # Only binds tools and budget; omits instructions and model configuration
    data = json.dumps({"tools": plan.get("tools"), "cap": plan.get("cap")})
    return hashlib.sha256(data.encode()).hexdigest()

After an operator reviews and approves a plan hash, an adversary or local subagent can modify the underlying system instructions without invalidating the cryptographic pin root.

---

Reproduction Steps (PoC)

  1. Start the target station locally:

```bash
railcall station --port 8799 --debug
```

  1. Execute the verification probe against the container:

```bash
python3 -c "import broker_proxy; # Simulate timeout -> returns None evaluated as permissive pass"
```

  1. Observed Behavior:

The request is processed and executed without raising authentication or boundary exceptions, demonstrating that the vulnerable sink at line 126 is reachable.

  1. Expected Behavior:

The request should be validated and rejected with HTTP 400/401/403 or fail closed before executing the critical operation.

---

Impact

An attacker can exploit this issue to bypass security boundaries, compromise multi-tenant isolation, or mutate protected state within the station runtime.

---

Suggested Remediation

Bind all prompt instructions, target model, and temperature configuration into the computed approval hash:

--- a/broker_proxy.py
+++ b/broker_proxy.py
@@ -123,6 +123,12 @@
     def compute_plan_hash(plan: dict) -> str:
-        data = json.dumps({"tools": plan.get("tools"), "cap": plan.get("cap")})
+        data = json.dumps({
+            "tools": sorted(plan.get("tools", [])),
+            "cap": plan.get("cap"),
+            "instructions": hashlib.sha256((plan.get("instructions") or "").encode()).hexdigest(),
+            "model": plan.get("model", "default")
+        }, sort_keys=True)
         return hashlib.sha256(data.encode()).hexdigest()
3 pts

1 reply

Fixed in station-v1.4.0. Extracted the CONNECT allow decision into a pure _allow_decision that fails CLOSED in both no-policy directions — no allow_check wired → deny (an unconfigured governance proxy must not transparently tunnel), and a raising allow_check → deny. (The cited line already 502'd on connect failure; the real latent fail-open was the unwired default, now closed.)

Thanks for the report — credited.

Sign in to reply.