You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Security/Medium] readPrivate dateOfBirth-search gate bypassable via Lucene query_string #1724
Location:backend/social-work-app/lambdas/python/search/handlers/search.py:204-258 (_query_references_field / _validate_date_of_birth_permission). Identical vulnerable copy at backend/cosmetology-app/lambdas/python/search/handlers/search.py:204-258.
Not affected:compact-connect (production) — its search handler has no such gate and does not index dateOfBirth into OpenSearch, so this vector does not apply there.
Summary
The advanced search endpoint accepts a raw OpenSearch DSL query (SearchProvidersRequestSchema.query = Raw, only index/_index keys blocked) and is authorized only by @authorize_compact_level_only_action(READ_GENERAL). Searching on dateOfBirth (indexed as date, opensearch_client.py:282) is meant to require the higher readPrivate scope. The sole enforcement, _query_references_field, only flags a dict key ending in .dateOfBirth or a string value that equals/ends-with .dateOfBirth — it never inspects substrings inside a string value.
Exploit chain
A staff user with only {compact}/readGeneral (implicitly held by all staff), not readPrivate, POSTs to /v1/compacts/{compact}/providers/search:
{"query":{"bool":{"must":[
{"term":{"providerId":""}},
{"query_string":{"query":"licenses.dateOfBirth:[1985-01-01 TO 1985-06-30]"}}
]}}}
The value "licenses.dateOfBirth:[...]" ends with ], not .dateOfBirth, so the gate passes and the query runs. The response schema doesn't echo DOB, but hit-vs-no-hit + total is a boolean oracle — binary-searching the date range (~15 requests) yields any provider's exact DOB, defeating the readGeneral/readPrivate boundary. wildcard/regexp/script types are also unrestricted.
Reachability
Wired & real on main. Route added in stacks/search_api_stack/v1_api/provider_search.py (add_method('POST',...)), scope ['{compact}/readGeneral'] (v1_api/api.py:29-40); SearchApiStack instantiated at pipeline/backend_stage.py:197 for both apps; dateOfBirth is in the index mapping (type: date).
Suggested fix
Don't rely on field-name string matching over attacker-controlled DSL.
Preferred: allowlist safe query types (term/terms/match/bool/nested/range) and reject free-form query_string/simple_query_string/wildcard/regexp/script.
Minimal stopgap: match the dateOfBirth substring inside any string value and reject fields specs containing *.
Best long-term: OpenSearch field-level masking.
Apply to both social-work-app and cosmetology-app copies.
@landonshumway-ia Just verified this (thanks for your help!). Using a token with only socw/readGeneral, a direct dateOfBirth query is correctly blocked (400 readPrivate), but the same filter smuggled through a Lucene query_string (licenses.dateOfBirth:[lo TO hi]) slips past the gate and runs.
The script I have recovered a provider's exact DOB in 16 requests, matching the DynamoDB ground truth.
@landonshumway-ia I ran the script (which originally reproduced this vulnerability), and I have confirmed that it no longer runs successfully now that #1805 has been merged and deployed into the test environment.
Activity
jlkravitz commentedon Jul 6, 2026
@landonshumway-ia Claude found this issue. I have not verified it. Let me know what you think.
landonshumway-ia commentedon Jul 14, 2026
@jlkravitz Has this been verified yet? If you are able to verify it, I'll pull it into the backlog.
jlkravitz commentedon Jul 14, 2026
@landonshumway-ia Just verified this (thanks for your help!). Using a token with only
socw/readGeneral, a direct dateOfBirth query is correctly blocked (400 readPrivate), but the same filter smuggled through a Lucene query_string (licenses.dateOfBirth:[lo TO hi]) slips past the gate and runs.The script I have recovered a provider's exact DOB in 16 requests, matching the DynamoDB ground truth.
landonshumway-ia commentedon Jul 23, 2026
Correlated with #1745
[Social Work] - Support public searching by CUID and license type (#1805
jlkravitz commentedon Aug 5, 2026
@landonshumway-ia I ran the script (which originally reproduced this vulnerability), and I have confirmed that it no longer runs successfully now that #1805 has been merged and deployed into the test environment.