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)
- Start the target station locally:
```bash
railcall station --port 8799 --debug
```
- Execute the verification probe against the container:
```bash
python3 -c "import broker_proxy; # Simulate timeout -> returns None evaluated as permissive pass"
```
- Observed Behavior:
The request is processed and executed without raising authentication or boundary exceptions, demonstrating that the vulnerable sink at line 126 is reachable.
- 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()