Skip to content

Escape directory search filters#448

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

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

Conversation

@rissrice2105-agent

Copy link
Copy Markdown
Contributor

Fixes #447.

What changed

  • Uses the existing escapePostgrestSearchValue helper for /api/directory search filters.
  • Sanitizes the search URL parameter through the shared URL parameter sanitizer.
  • Adds a regression test for %, _, comma, parentheses, dot, and * in directory search.

Validation

  • ./node_modules/.bin/vitest.cmd run src/app/api/directory/route.test.ts
  • ./node_modules/.bin/tsc.cmd --noEmit

@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes unescaped user input being interpolated directly into PostgREST filter strings in the directory search endpoint. The search parameter is now passed through sanitizeSearchParams (consistent with tag) and then through escapePostgrestSearchValue before being embedded in the ilike filter, preventing wildcard injection via %, _, *, and PostgREST syntax characters like commas and parentheses.

  • route.ts: Replaces the raw url.searchParams.get(\"search\") call with sanitizeSearchParams, then applies escapePostgrestSearchValue to the result before building the .or() filter string.
  • route.test.ts (new): Adds a regression test verifying that a search string containing %, _, ,, (, ), ., and * produces correctly-escaped filter arguments.

Confidence Score: 4/5

Safe to merge — the core fix is correct and the two-step pipeline (XSS sanitization then PostgREST escaping) is sound.

The route change is minimal and correct: sanitizeSearchParams + escapePostgrestSearchValue closes the filter-injection window. The new test exercises all the documented special characters. The only gap is that the backslash character, which escapePostgrestSearchValue does escape, is not covered by any test in this PR, leaving a silent failure mode if the sanitization pipeline is ever reordered.

The new test file (route.test.ts) would benefit from including a backslash in its input string to verify end-to-end doubling of \ through the escape function.

Important Files Changed

Filename Overview
src/app/api/directory/route.ts Applies sanitizeSearchParams to the search parameter (it was previously raw) and escapes PostgREST special characters with escapePostgrestSearchValue before string interpolation — straightforward, correct fix.
src/app/api/directory/route.test.ts New regression test verifying the escape pipeline for %, _, comma, parentheses, dot, and *; backslash is not included as a test case despite being handled by the escape function.

Sequence Diagram

sequenceDiagram
    participant Client
    participant GET as GET /api/directory
    participant sanitize as sanitizeSearchParams
    participant escape as escapePostgrestSearchValue
    participant Supabase

    Client->>GET: "?search=100%_demo,(v1.2)*"
    GET->>sanitize: sanitizeSearchParams(url, "search")
    sanitize-->>GET: ""100%_demo,(v1.2)*" (XSS chars stripped)"
    GET->>escape: escapePostgrestSearchValue(search)
    escape-->>GET: "100\\%\\_demo\\,\\(v1\\.2\\)\\*"
    GET->>Supabase: query.or("title.ilike.%100\\%\\_demo...%,...")
    Supabase-->>GET: "{ data, count, error }"
    GET-->>Client: "200 { listings, total, page, per_page }"
Loading

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

Comment on lines +47 to +52
const res = await GET(makeRequest({ search: "100%_demo,(v1.2)*" }));

expect(res.status).toBe(200);
expect(chain.or).toHaveBeenCalledWith(
"title.ilike.%100\\%\\_demo\\,\\(v1\\.2\\)\\*%,description.ilike.%100\\%\\_demo\\,\\(v1\\.2\\)\\*%"
);

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 The test input covers %, _, comma, parentheses, dot, and * but omits backslash. The escape function does handle \ (the regex [\\%*_,().] includes it), but nothing currently verifies that a literal backslash in user input is doubled to \\ rather than passed through or silently dropped. If sanitizeUrlParam were ever updated to strip backslashes, the escape ordering would silently break with no test failure.

Suggested change
const res = await GET(makeRequest({ search: "100%_demo,(v1.2)*" }));
expect(res.status).toBe(200);
expect(chain.or).toHaveBeenCalledWith(
"title.ilike.%100\\%\\_demo\\,\\(v1\\.2\\)\\*%,description.ilike.%100\\%\\_demo\\,\\(v1\\.2\\)\\*%"
);
const res = await GET(makeRequest({ search: "100%_demo,(v1.2)*\\path" }));
expect(res.status).toBe(200);
expect(chain.or).toHaveBeenCalledWith(
"title.ilike.%100\\%\\_demo\\,\\(v1\\.2\\)\\*\\\\path%,description.ilike.%100\\%\\_demo\\,\\(v1\\.2\\)\\*\\\\path%"
);

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!

@rissrice2105-agent

Copy link
Copy Markdown
Contributor Author

CI is green now: build, test, Greptile, Socket, and security checks all passed.

@ralyodio ralyodio merged commit a9338db into profullstack:master Jun 14, 2026
6 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.

[Bug] Directory search does not escape PostgREST filter punctuation

2 participants