security(dci): deny sensitive parameterized DCI metrics in external predicate searches#235
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a validation mechanism to reject sensitive DCI metrics in external predicate searches, preventing unauthorized callers from filtering on raw indicator cache values. Unit tests are also added to verify this restriction. The review feedback highlights a potential security bypass where whitespace around member selection dots in CEL expressions could circumvent the regex validation, and suggests a more robust regex pattern along with corresponding test cases to address this vulnerability.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 19.0 #235 +/- ##
==========================================
+ Coverage 74.60% 74.71% +0.10%
==========================================
Files 1076 1089 +13
Lines 64318 64699 +381
==========================================
+ Hits 47987 48341 +354
- Misses 16331 16358 +27
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
The denied-metric names predated the 19.0 r.dci.<registry>.<metric> accessor rename, so on 19.0 the guard matched nothing and the vulnerability stayed open (the test passed against obsolete names). Update the denylist and test to the real accessors (r.dci.dr.severity, r.dci.crvs.has_event). Also harden against the CEL whitespace-dot bypass (e.g. 'r . dci . dr . severity(...)') by allowing optional whitespace around member-selection dots, and cover it in the test.
57523b2 to
73082f9
Compare
Expand the predicate denylist beyond the two parameterized methods to the full
disability (r.dci.dr.*) and vital/civil-status (r.dci.crvs.*) accessor tier:
boolean oracles like r.dci.crvs.is_alive == false leak the same class of
private data via count/page queries. Lower-risk r.dci.sr.*/r.dci.ibr.* metrics
are intentionally left filterable.
Unify the regex so it matches the accessor as a bounded dotted path in any
resolving spelling -- bare (r.dci.dr.has_disability), called
(r.dci.dr.severity('Vision')), and quoted metric('...') -- since
non-parameterized metrics are referenced without parentheses. Boundaries reject
prefix/substring matches (severity_score, is_alive_x). Add tests for the
boolean forms and a negative test for benign and intentionally-allowed metrics.
#251) Replace #235's regex denylist with a positive, default-deny allowlist that closes the sensitive-data oracle across both sender-controlled DCI Social Registry query paths. - CEL predicate path: allowlist enforced on the resolved AST inside compile_expression (closes the backslash-escaped-dot bypass; robust to domain rewriting; TOCTOU-safe). - Structured expression path: field allowlist in _condition_to_domain (closes the query_type="expression" sibling oracle, e.g. attribute=disability_severity_id). External searches may reference only allowlisted person fields, safe scalar functions, and r.dci.sr.*/r.dci.ibr.* metrics. Disability/CRVS fields & metrics, relations and arbitrary partner fields are denied. No change to spp_dci_indicators; internal/outbound CEL unaffected (predicate_policy defaults to None). idtype-value unaffected.
Motivation
r.dci.dr.*) and vital/civil-status (r.dci.crvs.*) tiers were registered and could be rewritten intometric(...)calls, allowing authenticated external DCI predicate searches to filter/count/page on sensitive values and thereby infer private disability/event information. A boolean metric such asr.dci.crvs.is_alive == falsediscloses the value one query at a time viatotal_count, even though it never appears in the response schema.Description
_DCI_PREDICATE_DENIED_METRICSto the Social Registry search service (kept local to avoid importing the optionalspp_dci_indicatorsaddon). It covers the full disability and CRVS accessor tier —r.dci.dr.severity,r.dci.dr.has_disability,r.dci.dr.assessed,r.dci.dr.vision_severe,r.dci.dr.hearing_severe,r.dci.dr.mobility_severe,r.dci.crvs.has_event,r.dci.crvs.is_alive,r.dci.crvs.birth_verified,r.dci.crvs.is_married. Lower-risk Social Registry / inter-registry metrics (r.dci.sr.*,r.dci.ibr.*) are intentionally left filterable._validate_external_predicate_expression(expression: str), which matches each denied accessor as a bounded dotted member path in any spelling that resolves to the metric — bare (r.dci.dr.has_disability), called (r.dci.dr.severity('Vision')), and quoted (metric('r.dci.dr.severity', ...)) — and raisesValueErrorwhen present. Optional whitespace around member-selection dots (r . dci . dr . severity) is tolerated since CEL treats it as equivalent, and word boundaries prevent prefix/substring false positives (r.dci.dr.severity_score,r.dci.crvs.is_alive_x)._parse_predicatebefore calling the CEL compiler so sensitive metrics are rejected prior to compilation/execution. Internal CEL use is unaffected — this guard only applies to external Social Registry predicate search.test_parse_predicate_rejects_sensitive_dci_method_metricsasserts the parameterized, boolean, bare,metric(...), and whitespace-dot forms are all rejected;test_validate_external_predicate_allows_benign_and_lower_risk_metricspins that benign predicates and the intentionally-allowed SR/IBR metrics are not blocked (guards against over-matching and documents the scope decision).Notes on the rebase / review
r.dci.<registry>.<metric>accessor rename. The original denylist/test used the pre-rename names (dr.dci.severity,crvs.dci.has_event), which no longer exist on 19.0 — so on the current base the guard matched nothing and the test passed against obsolete names. Corrected to the real accessors.MetricCompare.metricrather than raw text (tracked separately).Testing
./spp t spp_dci_server_social→ 70 passed, 0 failed, 0 errors (Docker test runner, full Odoo stack).ruff,ruff-format). CI green.Codex Task