Skip to content

Escape prompt search filters - #392

Merged
ralyodio merged 1 commit into
profullstack:masterfrom
rissrice2105-agent:codex/prompt-search-escape
Jun 4, 2026
Merged

Escape prompt search filters#392
ralyodio merged 1 commit into
profullstack:masterfrom
rissrice2105-agent:codex/prompt-search-escape

Conversation

@rissrice2105-agent

Copy link
Copy Markdown
Contributor

Fixes prompt search queries that include PostgREST pattern/control characters by escaping %, _, comma, parentheses, dot, and related characters before building the .or(...) filter.

Validation:

  • vitest run src/app/api/prompts/route.test.ts
  • tsc --noEmit

@greptile-apps

greptile-apps Bot commented Jun 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR escapes user-supplied search terms before they are interpolated into PostgREST's .or() filter string, preventing wildcard and syntax characters from affecting query behaviour. The escaping is applied only to the GET /api/prompts search path and a companion unit test is added.

  • SQL LIKE wildcards (%, _) and the backslash escape character are correctly escaped, which is the most impactful part of the fix.
  • PostgREST syntax characters (,, ., (, )) are backslash-escaped, but PostgREST's documented mechanism for these is to wrap the filter value in double quotes \u2014 backslash-escaping is not a supported alternative for those characters, meaning searches containing commas, parentheses, or dots may still fail or produce incorrect results.

Confidence Score: 3/5

The SQL LIKE wildcard escaping is correct, but the PostgREST syntax character escaping uses a mechanism that is not documented as supported — searches containing commas, dots, or parentheses could still be parsed incorrectly by PostgREST.

The fix correctly handles SQL LIKE wildcards but the approach for PostgREST reserved characters relies on backslash-escaping while PostgREST's own documentation prescribes double-quoting. A search containing a comma may still cause PostgREST to misparse the filter. The unit test mocks the client entirely and cannot catch this gap.

Both changed files need attention: route.ts for the escaping logic, and route.test.ts because it validates only the JS-layer string output rather than actual PostgREST parsing.

Important Files Changed

Filename Overview
src/app/api/prompts/route.ts Adds escapePostgrestSearch helper to sanitise search input; correctly escapes SQL LIKE wildcards but uses backslash-escaping for PostgREST syntax characters which is not the documented supported mechanism.
src/app/api/prompts/route.test.ts New test file asserting the exact string passed to .or(); Supabase is fully mocked so actual PostgREST parsing behaviour is not verified.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Route as GET /api/prompts
    participant Escape as escapePostgrestSearch()
    participant Supabase as Supabase JS Client
    participant PostgREST
    participant Postgres as PostgreSQL ILIKE

    Client->>Route: "GET /api/prompts?search=ai%25,foo"
    Route->>Escape: escapePostgrestSearch("ai%,foo")
    Note over Escape: % -> \% SQL wildcard OK<br/>_ -> \_ SQL wildcard OK<br/>, -> \, NOT PostgREST-documented
    Escape-->>Route: "ai\%\,foo"
    Route->>Supabase: .or("title.ilike.%ai\%\,foo%,...")
    Supabase->>PostgREST: filter with backslash-escaped comma
    Note over PostgREST: Comma may still split condition
    PostgREST->>Postgres: ILIKE pattern
    Postgres-->>Route: results
    Route-->>Client: 200 listings
Loading

Reviews (1): Last reviewed commit: "Escape prompt search filters" | Re-trigger Greptile

Comment on lines +8 to +17
function escapePostgrestSearch(value: string) {
return value
.replace(/\\/g, "\\\\")
.replace(/%/g, "\\%")
.replace(/_/g, "\\_")
.replace(/,/g, "\\,")
.replace(/\(/g, "\\(")
.replace(/\)/g, "\\)")
.replace(/\./g, "\\.");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Backslash-escaping PostgREST reserved characters is not the supported mechanism

PostgREST's documented approach for handling reserved characters (,, ., :, ()) in filter values is to wrap the entire value in double quotes — not backslash-escape each character. The PostgREST URL grammar docs are explicit: "If filters include PostgREST reserved characters(,, ., :, ()) you'll have to surround them in percent encoded double quotes." Backslash-escaping is documented only for escaping " within a double-quoted value.

A search for AI, ML would produce title.ilike.%AI\, ML%,... — PostgREST's parser still treats the unquoted \, as a condition separator, which will either produce a parse error or an incorrectly constructed query. The correct approach is to double-quote the pattern and escape only " and \ within that quoted value, then use title.ilike."${safeSearch}" in the .or() string.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +53 to +63
it("escapes PostgREST filter characters in search terms", async () => {
const query = makePromptQuery();
mockFrom.mockReturnValue(query);

const response = await GET(makeGetRequest({ search: "ai%,foo_(v1)." }));

expect(response.status).toBe(200);
expect(query.or).toHaveBeenCalledWith(
"title.ilike.%ai\\%\\,foo\\_\\(v1\\)\\.%,description.ilike.%ai\\%\\,foo\\_\\(v1\\)\\.%,tagline.ilike.%ai\\%\\,foo\\_\\(v1\\)\\.%"
);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Test validates JS-layer output, not actual PostgREST behaviour

The test verifies that or() is called with a specific escaped string, but because Supabase is fully mocked it cannot detect whether PostgREST would accept or correctly interpret that string. A scenario where backslash-escaped commas silently produce wrong query results (or a 400 from PostgREST) would pass this test. An integration test or a test asserting the double-quoting format recommended by PostgREST docs would give much stronger coverage.

@ralyodio
ralyodio merged commit ec8f6b1 into profullstack:master Jun 4, 2026
4 checks passed
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.

2 participants