Make the self-host server safe-by-default; add ReDoS input cap#13
Merged
Merged
Conversation
This is the reference/self-host server (for users without Cloudflare). It previously bound to 0.0.0.0, exposed an unauthenticated POST /admin/reload, sent CORS "*", and fed unbounded query input to registry regexes. Safe-by-default server (secid_server.py): - Default bind is now 127.0.0.1; --host 0.0.0.0 is an explicit opt-in with a loud startup warning (F-06-03). - POST /admin/reload is gated by a dedicated reload token (SECID_RELOAD_TOKEN / --reload-token, header X-Reload-Token), compared with hmac.compare_digest. Fails closed: no token configured => 401. Scoped to reload only, not a master key — future admin endpoints get their own token (F-06-01). - CORS is opt-in: with no --cors-origin / SECID_CORS_ORIGINS configured the middleware is not added at all (F-06-02). Read path stays anonymous by design. ReDoS runtime bound (resolver.py): - Registry regexes run against attacker input every request (Python re, no RE2, no timeout) and FastAPI's secid Query was unbounded. resolve() now rejects queries over MAX_SECID_QUERY_CHARS (1024), and over-long components (MAX_REGEX_INPUT, 256) are treated as no-match — bounding worst-case backtracking without changing any valid resolution (F-03-02). +9 regression tests (43 pass). README documents the new defaults and the backward-compat break for existing self-hosters. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SecID-Server-APIis the reference / self-host server (for users without Cloudflare). It previously bound to0.0.0.0, exposed an unauthenticatedPOST /admin/reload, sent CORS*, and fed unbounded query input to registry regexes. This hardens all four to safe-by-default.Audit reference: SecID-2026-06-14-claude-skill (findings F-06-01, F-06-02, F-06-03, F-03-02).
Safe-by-default server (
python/secid_server.py)--host 0.0.0.0default--host 127.0.0.1default;0.0.0.0is an explicit opt-in with a loud startup warningPOST /admin/reloadunauthenticatedSECID_RELOAD_TOKEN/--reload-token, headerX-Reload-Token),hmac.compare_digest. Fails closed: no token configured → 401allow_origins=["*"]--cors-origin/SECID_CORS_ORIGINS→ middleware not added at allThe reload token is scoped to reload only — deliberately not a master key. A future admin endpoint should get its own
SECID_<CAP>_TOKENrather than widening this one. The read path (/api/v1/resolve,/api/v1/types,/mcp) stays anonymous by design.ReDoS runtime bound (
python/resolver.py)Registry-authored regexes run against attacker-controlled input on every request via Python
re— no RE2, no timeout — and FastAPI'ssecid: str = Query(...)was unbounded.resolve()now rejects queries overMAX_SECID_QUERY_CHARS(1024), and over-long components (MAX_REGEX_INPUT, 256) are treated as no-match rather than truncated — so worst-case backtracking is bounded without changing any valid resolution (real SecIDs are < 200 chars). RE2 is the robust follow-up; this cap is the minimal stopgap. (F-03-02)Tests
+9 regression tests, 43 pass (
pytest test_smoke.py): reload 401-without-token / 401-wrong-token / 200-correct-token, read-path-needs-no-token, CORS off-by-default / on-when-allowlisted, oversize-query rejection (direct + HTTP), and the component-bound helper.Existing self-hosters must now: (a) pass
--host 0.0.0.0to keep listening on all interfaces, (b) set a reload token to use/admin/reloadat all, (c) pass--cors-originif browser clients call cross-origin. A new "Security & Exposure Defaults" README section + an upgrade note cover this.Notes for review
0.0.0.0is used, front with TLS. Consider rate-limiting/admin/reload./admin/*to loopback only or replace it with a CLI/SIGHUPrefresh. The token path was chosen because it was the requested design.🤖 Generated with Claude Code