Control Plane HTTP surface: token/Origin gates for the run endpoints, Host validation, and Content-Type strictness
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
Research direction
Start in fsq_agent/adapters/control_plane/_server.py and _config.py, tracing handle_post, the run/readiness/targets/cases routes, require_config_access, require_same_origin_write, and _decode_json_body; also inspect fsq_agent/config/_user_provider.py. Map the existing frontend response where the session token would be embedded. Done means mutating endpoints use token and origin checks, Host and JSON Content-Type are validated, and provider URLs require https://.
Written by the indexing model from the issue text.
Description
Problem or use case
The Control Plane's HTTP surface (fsq_agent/adapters/control_plane/) applies its request gates unevenly:
handle_postchecksrequire_config_access+require_same_origin_writeon the config/workspace/history routes (_server.pyL435-436, L451-452, L578-579, L627-628), but the run routes go straight through:POST /api/control-plane/runsreaches_start_run(body)(L467) and the/runs/<id>/cancel,/save-yaml,/replay-videosuffixes (L470-498) carry neither check. The run/readiness/targets/cases GETs (L267-402) are likewise ungated.require_config_access(_config.pyL39-43) validates only that the bind host and the peer are loopback — a loopback-only peer check does not survive DNS rebinding, where a page served from a rebound hostname connects from 127.0.0.1 with attacker-chosenHost/Originheaders.require_same_origin_write(L46-51) compares theOriginnetloc to theHostheader and returns early whenOriginis absent; under rebinding both headers derive from the attacker's hostname, so the comparison is self-consistent rather than binding._decode_json_body(L1003-1006) parses the raw body without consultingContent-Type, and there is noOPTIONShandler, so JSON-body routes also accept CORS-simple request shapes (e.g.text/plain) that browsers send without a preflight._normalize_azure_base_url(fsq_agent/config/_user_provider.pyL48-58) acceptshttp://and arbitrary hosts so long as the path ends in/openai/v1/.
Standard loopback-server practice covers all of these: a per-session token embedded in the served UI, Host validation, Origin checks on reads as well as writes, Content-Type enforcement on JSON writes, and scheme/host restrictions on provider endpoints.
Desired outcome
Every mutating endpoint — the run endpoints included — requires the same origin/token proof that the config writes already require; the server rejects requests whose Host does not match its bind; JSON parsing applies only to application/json bodies; provider base URLs are restricted to https://.
Proposed change
- Mint a per-session bearer token at server start, embed it in the served frontend, and require it on every mutating endpoint, including
/runs. - Reject requests whose
Hostheader does not match the bound host; validateOriginon GETs as well (or requireSec-Fetch-Site: same-origin). - Enforce
Content-Type: application/jsonbefore_decode_json_bodyaccepts a write body. - Apply
require_config_accessto the run endpoints whenever the bind is non-loopback. - Restrict provider base URLs to
https://and consider an allowlist of known provider hosts.
- Dominant language
- Python
- Stars
- 23
- Forks
- 4
- Avg merge
- 2h 15m
- Merged PRs (30d)
- 46
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100