fix(W-23622931): escape single quotes in SmartSQL json_extract path and FTS MATCH value - #2993
fix(W-23622931): escape single quotes in SmartSQL json_extract path and FTS MATCH value#2993wmathurin wants to merge 2 commits into
Conversation
…nd FTS MATCH value
Escape single quotes in two injection sinks in SmartStore:
1. SmartSqlHelper: unindexed path used in json_extract(soup, '$.path')
- a quote in the path broke out of the SQL string literal
2. QuerySpec: qualifyMatchKey result inlined into MATCH 'value'
- statement arg binding doesn't work for FTS MATCH, so inlining is required;
escaping '' prevents breaking out of the surrounding string literal
Both sinks are reachable from the SmartStore Cordova bridge in hybrid apps.
Generated by 🚫 Danger |
JohnsonEricAtSalesforce
left a comment
There was a problem hiding this comment.
Thanks for tightening these SmartSQL sinks, Wolfgang. I traced the query-construction path and the fix is correct on both sinks it targets:
SmartSqlHelper.java:176(non-indexedjson_extractpath) — every{soup:path}reference in smart/exact/like/range/order queries routes throughconvertSmartSqlinto this line, so escaping here covers those callers. Inside the'$.<path>'string literal,'is the only metacharacter and''is the correct SQLite escape, so the break-out is closed. (The$→\$handling at line 149 is orthogonal regex-replacement escaping and is unaffected.)QuerySpec.java:384(FTSMATCHvalue) — this is the one query type that inlines its key rather than binding a?arg (getArgs()returnsnullformatch; exact/like/range bind params and are safe). Escaping the output ofqualifyMatchKey(...)— i.e. after thefield:qualifiers are inserted — is the right layer; escaping earlier would let the qualifier regex re-tokenize the doubled quotes.
I confirmed platform parity: the iOS twin (SalesforceMobileSDK-iOS PR 4132, W-23622931, merged to dev on 2026-08-14) makes the identical change at the same two sinks and the same layer, so this PR is consistent with the shipped iOS fix.
Requested change — fold in the third json_extract sink (SmartStore.java:379)
A third, structurally identical sink is left unescaped in SmartStore.java:379 (index-registration path):
columnName = "json_extract(" + SOUP_COL + ", '$." + indexSpec.path + "')";Here indexSpec.path is concatenated unescaped. It's reachable from the same hybrid attack surface: JS → SmartStorePlugin pgRegisterSoup → IndexSpec.fromJSON (which does no validation on path, IndexSpec.java:44) → this DDL, where the column expression is baked into CREATE TABLE/CREATE INDEX and persisted in soup_index_map. A ' in the index path breaks out of the literal the same way — same CWE-89 class, just at registration time instead of query time. Since it's the identical fix in the same file, could we fold it into this PR so the sink family is fully closed?
columnName = "json_extract(" + SOUP_COL + ", '$." + indexSpec.path.replace("'", "''") + "')";I confirmed this line isn't being addressed in any other open/merged PR or GUS item. For cross-platform parity, note the iOS twin (PR 4132) fixed the two query-time sinks but left the equivalent SFSmartStore.m:1120 index-path sink untouched, so a matching iOS follow-up would keep the platforms aligned.
Non-blocking: No regression test accompanies the escaping — see the inline note on SmartSqlHelper.java:176.
Reviewed at HEAD 7e31f066.
This review was generated by an AI agent on behalf of @JohnsonEricAtSalesforce.
Add testConvertSmartSqlForNonIndexedColumnWithSingleQuoteInPath to SmartSqlTest to verify that a ' in a non-indexed path is doubled in the json_extract literal, and testMatchQuerySmartSqlWithSingleQuoteInMatchKey to QuerySpecTest to verify the same doubling in the FTS MATCH predicate (QuerySpec.java:384).
|
The regression tests address the non-blocking note — thanks. The one requested change is still open: the third columnName = "json_extract(" + SOUP_COL + ", '$." + indexSpec.path.replace("'", "''") + "')";— would close the sink family completely rather than leaving a registration-time sibling. (I confirmed no other open/merged PR or work item covers this line. The iOS twin PR left the equivalent This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce. |
Summary
json_extractpath (SmartSqlHelper.java:176): escapes'→''in the soup path before buildingjson_extract(soup, '$.path'). A quote in the path broke out of the SQL string literal, allowing SmartSQL injection via a non-indexed query path (CWE-89).QuerySpec.java:384): escapes'→''in the qualified match key before inlining intoMATCH 'value'. Statement arg binding doesn't work for FTS MATCH so inlining is unavoidable; escaping prevents breaking out of the surrounding string literal.Test plan
./gradlew :libs:SmartStore:build— confirmed passing./gradlew :libs:SmartStore:connectedAndroidTest— confirmed all passing