Control Plane HTTP surface: token/Origin gates for the run endpoints, Host validation, and Content-Type strictness

Open
#109 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
35/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
python
Domain
api, backend, security

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_post checks require_config_access + require_same_origin_write on the config/workspace/history routes (_server.py L435-436, L451-452, L578-579, L627-628), but the run routes go straight through: POST /api/control-plane/runs reaches _start_run(body) (L467) and the /runs/<id>/cancel, /save-yaml, /replay-video suffixes (L470-498) carry neither check. The run/readiness/targets/cases GETs (L267-402) are likewise ungated.
  • require_config_access (_config.py L39-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-chosen Host/Origin headers.
  • require_same_origin_write (L46-51) compares the Origin netloc to the Host header and returns early when Origin is 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 consulting Content-Type, and there is no OPTIONS handler, 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.py L48-58) accepts http:// 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
  1. Mint a per-session bearer token at server start, embed it in the served frontend, and require it on every mutating endpoint, including /runs.
  2. Reject requests whose Host header does not match the bound host; validate Origin on GETs as well (or require Sec-Fetch-Site: same-origin).
  3. Enforce Content-Type: application/json before _decode_json_body accepts a write body.
  4. Apply require_config_access to the run endpoints whenever the bind is non-loopback.
  5. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.