Fix LIKE operator to escape wildcards in user-supplied values#33
Merged
Conversation
Like::GetSqlValue wrapped the bound value in %...% but never escaped %, _, or \ occurring inside the caller's value itself, and the generated LIKE clause had no ESCAPE clause. SQLite's LIKE has no default escape character, so a value like "50%" matched far more than the literal text - not injection (the value is still bound as a parameter), but wrong match semantics. Escape \, % and _ (backslash first, single pass, so an inserted escape backslash is never re-escaped) in Like::GetSqlValue before wrapping in the outer "contains" percents, and emit " ESCAPE '\\'" from QueryPredicate::Evaluate() whenever symbol_ == "LIKE". The ESCAPE clause has to live in QueryPredicate::Evaluate() rather than a Like-only override: QueryPredicate::Clone() returns a base QueryPredicate (not a Like), and BinaryPredicate (And/Or) stores Clone()d operands, so a Like combined via And()/Or() is downgraded to a base QueryPredicate before Evaluate() runs. Since symbol_ and the already-escaped value_ both survive Clone(), keying the ESCAPE clause off symbol_ keeps compound predicates correct too - confirmed with a dedicated Clone-survival test. Tests: literal %, literal _, literal backslash, a Like combined via And() still matching literally (locks in the Clone-survival fix), and a regression test that a plain Like with no special characters still behaves as a substring/contains match. Updated the pre-existing LikePayloadStaysInBindings test's expected SQL/binding strings to reflect the now-correct escaped output. Confirmed 6 of 8 new/updated Like assertions fail on the pre-fix code and all pass after. Full suite: 72/72 tests pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUt3c1wdseRtRSSXfg3MCK
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
Fixed the
Likequery predicate to properly escape SQLite wildcard characters (%,_, and\) in user-supplied search values, ensuring they are matched literally rather than interpreted as wildcards. This prevents unintended pattern matching behavior and potential SQL injection vulnerabilities.Key Changes
EscapeLikeWildcards()helper function inquery_predicates.ccthat escapes backslash, percent, and underscore characters by prefixing them with a backslashQueryPredicate::Evaluate()to emit anESCAPE '\'clause for all LIKE operations, enabling the escape sequences to take effect in SQLiteLike::GetSqlValue()to applyEscapeLikeWildcards()to all user-supplied values (text, int, bool, real) before wrapping them with the outer "contains" wildcards (%)%matching in search values_matching in search values\matching in search valuesClone())Implementation Details
QueryPredicate::Evaluate()(not aLike-specific override) to ensure it survives theClone()operation used byBinaryPredicatewhen combining conditions withAnd()/Or()https://claude.ai/code/session_01NUt3c1wdseRtRSSXfg3MCK