Reject cross-site POSTs to state-changing API routes#14
Merged
Conversation
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.
Problem
In no-key local mode the API is open by design, but that also means a web page the user merely visits can submit an HTML
<form>tohttp://127.0.0.1:8000/api/ingestand poison the local corpus. A form POST is a CORS simple request — it runs with no preflight, so the CORS config never blocks the side effect (it only blocks the attacker from reading the response). Classic CSRF.Fix
New
require_same_sitedependency on the four state-changing routes (POST /ingest,POST /query,DELETE /documents,DELETE /documents/{id}). It trusts the browser-setSec-Fetch-Siteheader (rejectscross-site), falls back to anOriginallowlist for older browsers, and allows requests carrying neither header (curl, the CLI, server-to-server) since those have no ambient-credential CSRF surface.Verification
test_security.py(cross-site rejected, same-origin/none/no-header allowed, Origin fallback both ways).TestClient: cross-site form POST to/api/ingest→ 403; same-origin → 200.Closes #13