fix: escape id path parameter before interpolating into PostgREST filters - #19
Merged
Merged
Conversation
search_tickets (GET /tickets/search) and filter_tickets (GET /tickets/filter) interpolated user-supplied values (q, status, priority, assigned_user_id) directly into PostgREST filter strings. Comma and parentheses are structural in PostgREST or() syntax and nothing encoded & or =, so a value such as "x),status.eq.Closed&limit=1" could close the or() group, append extra conditions, and inject additional query parameters. Add database.escape_filter_value, which backslash-escapes PostgREST's reserved characters (\ , ( )) and then percent-encodes the result with safe="", and route every interpolated value through it. The surrounding template (wildcards, operators, &order=) is left untouched. Tests cover plain-text passthrough, that reserved characters never survive unescaped, the exact escape-then-encode output, and end-to-end injection attempts against both endpoints. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6wR9fK3YEMMKDwEAwr5Ju
…ters
db_patch, db_delete, and the {id}-based ticket routes (GET /tickets/{id},
GET /tickets/{id}/notes, GET /tickets/{id}/history, and the status-history
lookup in PUT /tickets/{id}) interpolated the raw id path parameter into
PostgREST filter strings such as "id=eq.{id}". FastAPI decodes the path
segment before handing it over, so a crafted id like "t1&status=eq.Closed"
could append additional filters or query parameters to the Supabase request.
Route every id through database.escape_filter_value (introduced for the
search/filter endpoints) so the value is backslash-escaped for PostgREST's
reserved characters and percent-encoded before it reaches the query string.
Tests cover db_patch/db_delete directly and each affected route end-to-end.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6wR9fK3YEMMKDwEAwr5Ju
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…filter-injection # Conflicts: # CHANGELOG.md # backend/test_database.py
sonnymay
commented
Sep 10, 2026
sonnymay
left a comment
Owner
Author
There was a problem hiding this comment.
Implementation review: PASS at 80bb3ae. Every listed id filter is escaped exactly once, UUIDs remain unchanged, the select/order template suffixes remain structural, and the combined 43-test plus lint, format, type-check, and pre-commit suite passes.
sonnymay
changed the base branch from
fix/postgrest-filter-injection
to
main
September 10, 2026 18:06
…ter-injection # Conflicts: # CHANGELOG.md # backend/test_database.py # backend/test_main.py
sonnymay
marked this pull request as ready for review
September 10, 2026 18:07
This was referenced Sep 10, 2026
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
db_patch,db_delete, and the{id}ticket routes (GET /tickets/{id},/notes,/history, and the status lookup inPUT /tickets/{id}) interpolated the decoded path parameter into PostgREST filters.idthroughdatabase.escape_filter_value, which percent-encodes query delimiters before the value reaches the request URL.Testing
pytest -q— 43 passedruff check .,ruff format --check .,mypy ..,pre-commit run --all-files— all cleandb_patch,db_delete, each{id}GET route, and the PUT status lookup.Notes
&select=and&order=remain structural.🤖 Generated with Claude Code