Skip to content

T0: SSRF deny-dialer, prompt-injection envelope, real client-IP resolution, two-tier rate limiting - #53

Open
andreimarinescu wants to merge 1 commit into
mainfrom
andrei/t0-security
Open

andreimarinescu wants to merge 1 commit into
mainfrom
andrei/t0-security

Conversation

@andreimarinescu

Copy link
Copy Markdown
Collaborator

Closes the T0 engine-hardening scope from the Cosift public-launch plan. T0.1 gates the grounded

topic→URL resolver (T6) — the first feature that lets users influence what the crawler fetches — so

this has to land before that work can start.

54 files. Three logically separate fixes, one commit each.

What was actually wrong

Reproduced on a live instance (cosift-local, 3,070-doc store) against the v0.2.5 binary before any
change. Full output in tools/golden/2026-09-15-t0-local/e2e-control.txt in the monorepo.

Probe v0.2.5 behaviour
POST /admin/sitemap-import {"url":"http://127.0.0.1:7777/sitemap.xml"} Connected. Replied sitemap http 404 — the engine fetched itself
same, http://169.254.169.254/latest/meta-data/ Connected
same, http://[::1]:7777/, http://10.0.0.1/, http://100.64.0.1/ Connected
POST /admin/site-submit {"host":"localhost"} Accepted
25 × POST /feedback with rotating X-Forwarded-For 0 rate-limited

The admin routes matter more than the awrap gate suggests: peerTokenOK returns true when the
configured token is empty (serve_helpers.go:36-38), and production has it empty, so these are
reachable unauthenticated. serve_crawl.go:195 also uses its own client rather than the crawler
transport, so the Cloudflare Worker that fronts crawl GETs does not cover this path in production
either.

After the change: all six refused, /feedback spoof 21/25 limited. 6 PASS/7 FAIL → 13 PASS/0 FAIL.

…pe, client IP, rate limiting)

Closes the T0 scope of the public-launch plan. T0.1 gates the grounded
topic->URL resolver, the first feature that lets users influence crawling.

netguard: a net.Dialer.Control hook refusing non-global-unicast resolved
addresses, applied to the crawler transport and the server-side fetchers in
serve_crawl.go, fetchone.go and cmd_crawl.go. The embed, chat, rerank, chatgate
and peer-forward clients are exempt: they legitimately dial private endpoints,
and on prod the chat endpoint is a box-local vLLM. Rejection set ported from the
harvesters' resolve.py, including 6to4 and Teredo recursion. Guarded by
crawler.block_private_networks, default on, with COSIFT_ALLOW_PRIVATE_NETWORKS
as the override. A source-scanning test fails when a new unguarded egress
appears. VetTargets covers the proxy-pool path, where Control never sees the
target.

promptsafe: crawled text is fenced with a per-request nonce at thirteen prompt
construction sites, and the user question moved out of the attacker-controlled
region. The [N] citation token is deliberately unchanged -- cmd_eval.go parses
it for the grounding metric. Covers the planner's getSiteTitles input and the
research refine loop's lastAnswer re-injection.

rate limiting: the per-IP limiter keyed on RemoteAddr, which behind Caddy is
always 127.0.0.1, and that address is whitelisted -- so it was inert, not merely
mis-keyed. clientIPResolver is exported and wired in; feedback.go no longer
trusts the leftmost forwarded hop and querylog.go no longer logs the raw header.
Adds a tighter always-on LLM tier over /answer, /research, /query and /find,
plus /search and /find_similar when they opt into rerank or expand. A key the
transport does not attest may not match the operator whitelist, so a forwarded
hop cannot claim 127.0.0.1.

Behaviour is unchanged for on-box callers and no worse than today for proxied
traffic; activating the trusted-proxy config is a separate, sequenced ops step.

Verified in golang:1.25-bookworm: gofmt, vet, linux/arm64 cross-compile and
go test -race ./... across 18 packages. E2E on cosift-local goes from 7 pass /
8 fail to 15 pass / 0 fail, and BM25 golden overlap@10 and @50 are both 1.0000
against control on a frozen store.
@codecov

codecov Bot commented Sep 15, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant