Skip to content

fix: escape user input before interpolating into PostgREST filters - #18

Merged
sonnymay merged 2 commits into
mainfrom
fix/postgrest-filter-injection
Sep 10, 2026
Merged

sonnymay merged 2 commits into
mainfrom
fix/postgrest-filter-injection

Conversation

@sonnymay

@sonnymay sonnymay commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • GET /tickets/search and GET /tickets/filter interpolated raw q, status, priority, and assigned_user_id into PostgREST filter strings. Commas and parentheses are structural in or() expressions, while & and = delimit query parameters.
  • Adds database.escape_filter_value. Ordinary values are percent-encoded; search patterns are wrapped in PostgREST double quotes with embedded backslashes and quotes escaped, so logical delimiters remain data.
  • Applies the helper to every listed user-supplied value while leaving the template's own wildcards, commas, parentheses, and &order= untouched.

Testing

  • pytest -q — 37 passed
  • ruff check ., ruff format --check ., mypy .., pre-commit run --all-files — all clean
  • Regression coverage checks the decoded PostgREST logical expression, plain-text/UUID passthrough, reserved syntax, and query-parameter injection attempts.

Notes

  • No behavior change for legitimate inputs.
  • CHANGELOG has a ### Fixed entry under [Unreleased].

🤖 Generated with Claude Code

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
@vercel

vercel Bot commented Sep 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
supportops Ready Ready Preview Sep 10, 2026 6:01pm UTC

@sonnymay sonnymay left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation review: PASS after 1993164. The full logical ilike operand is now quoted per PostgREST grammar, embedded backslashes and quotes are escaped, query delimiters are percent-encoded, template structure remains trusted, and all required checks pass.

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