Skip to content

fix(customer/search): escape SQL LIKE metacharacters in the user-supplied substring#158

Merged
CryptoJones merged 1 commit into
masterfrom
fix/customer-search-escape-like-wildcards
May 19, 2026
Merged

fix(customer/search): escape SQL LIKE metacharacters in the user-supplied substring#158
CryptoJones merged 1 commit into
masterfrom
fix/customer-search-escape-like-wildcards

Conversation

@CryptoJones
Copy link
Copy Markdown
Owner

Closes #157.

Summary

GET /v1/customer/search?q=… builds an ILIKE pattern as %${q}% and hands it to Sequelize's Op.iLike. Sequelize does NOT auto-escape SQL LIKE metacharacters for that operator — it treats the pattern as verbatim user-controlled SQL. So q=% produces %%% and matches every customer in scope; q=__@ matches anything-2-chars-then-@; q=\\ lands a raw backslash in the pattern.

Same security boundary still holds (non-master keys still scoped to their own company), but the substring contract silently breaks. A user typing 100% into a search-as-you-type input gets all customers back, not just those whose name contains 100%.

Add a small escapeLikeWildcards(s) helper that prefixes the three metacharacters (%, _, \) with \. Use it on q before constructing the pattern. Postgres ILIKE then treats them as literals. Helper exposed via exports._helpers so future sibling endpoints can reuse it.

Test plan

  • npm run lint — clean
  • npm test — 612 passed (was 608 + 4 new helper tests), 15 skipped
  • Four tests pin: the three metacharacters individually, ordinary text passthrough, multi-meta strings, defensive non-string coercion

Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/

…lied substring

`GET /v1/customer/search?q=…` constructs an ILIKE pattern as
`%${q}%` and hands it to Sequelize's `Op.iLike`. Sequelize does
NOT auto-escape SQL LIKE metacharacters for that operator — it
treats the pattern as verbatim user-controlled SQL. So a request
like `q=%` produces `%%%` which matches **every** customer in
scope; `q=__@` matches anything-2-chars-then-@; `q=\\` lands a
raw backslash in the pattern.

Same security boundary (still company-scoped — non-master keys
can't reach beyond their own company), but the substring contract
silently breaks: a user typing `100%` into a search-as-you-type
input gets all customers back, not customers whose name contains
"100%".

Add a small `escapeLikeWildcards(s)` helper that prefixes the
three LIKE metacharacters (`%`, `_`, `\`) with `\`. Use it on `q`
before constructing the pattern. Helper is exposed via
`exports._helpers` for unit testing and so future siblings
(invoice number search, etc.) can reuse it.

Four new tests cover: the three metacharacters individually,
ordinary text passthrough, multi-meta strings (e.g. `100%_done`,
`path\\to\\file`), and defensive non-string coercion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@CryptoJones CryptoJones merged commit b557009 into master May 19, 2026
3 checks passed
@CryptoJones CryptoJones deleted the fix/customer-search-escape-like-wildcards branch May 19, 2026 07:41
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.

customer/search: SQL LIKE metacharacters in q not escaped — q=% matches every customer in scope

1 participant