Skip to content

security(dci): deny sensitive parameterized DCI metrics in external predicate searches#235

Merged
gonzalesedwin1123 merged 3 commits into
19.0from
codex/fix-vulnerability-exposing-dci-metrics
Jun 26, 2026
Merged

security(dci): deny sensitive parameterized DCI metrics in external predicate searches#235
gonzalesedwin1123 merged 3 commits into
19.0from
codex/fix-vulnerability-exposing-dci-metrics

Conversation

@jeremi

@jeremi jeremi commented Jun 10, 2026

Copy link
Copy Markdown
Member

Motivation

  • DCI CEL metrics in the disability (r.dci.dr.*) and vital/civil-status (r.dci.crvs.*) tiers were registered and could be rewritten into metric(...) 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 as r.dci.crvs.is_alive == false discloses the value one query at a time via total_count, even though it never appears in the response schema.
  • The goal is to prevent external Social Registry predicate queries from using these sensitive cached metrics as an oracle while preserving internal/optional module behavior and existing CEL compilation paths.

Description

  • Add a local denylist _DCI_PREDICATE_DENIED_METRICS to the Social Registry search service (kept local to avoid importing the optional spp_dci_indicators addon). 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.
  • Introduce _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 raises ValueError when 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).
  • Invoke the validation from _parse_predicate before 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.
  • Tests: test_parse_predicate_rejects_sensitive_dci_method_metrics asserts the parameterized, boolean, bare, metric(...), and whitespace-dot forms are all rejected; test_validate_external_predicate_allows_benign_and_lower_risk_metrics pins 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

  • This branch predated the 19.0 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.
  • Expanded from the two parameterized methods to the full DR/CRVS tier after review: the non-parameterized boolean flags leak the same class of data through the same predicate path and are referenced without parentheses.
  • The denylist remains a best-effort string guard. A durable follow-up is to enforce an allowlist of safe metrics against the parsed AST / translated MetricCompare.metric rather than raw text (tracked separately).

Testing

  • ./spp t spp_dci_server_social → 70 passed, 0 failed, 0 errors (Docker test runner, full Odoo stack).
  • Lint clean (ruff, ruff-format). CI green.

Codex Task

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread spp_dci_server_social/services/search_service.py
Comment thread spp_dci_server_social/tests/test_search_service_internals.py
@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.71%. Comparing base (8b98b78) to head (4af62a0).

Additional details and impacted files

Impacted file tree graph

@@            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     
Flag Coverage Δ
spp_base_common 90.26% <ø> (ø)
spp_dci_compliance 92.76% <ø> (?)
spp_dci_server_social 88.47% <100.00%> (+0.22%) ⬆️
spp_programs 65.12% <ø> (ø)
spp_registry 86.83% <ø> (ø)
spp_security 66.66% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_dci_server_social/services/search_service.py 90.35% <100.00%> (+0.25%) ⬆️

... and 13 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

jeremi and others added 2 commits June 26, 2026 17:13
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.
@gonzalesedwin1123 gonzalesedwin1123 force-pushed the codex/fix-vulnerability-exposing-dci-metrics branch from 57523b2 to 73082f9 Compare June 26, 2026 09:24
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.
@gonzalesedwin1123 gonzalesedwin1123 merged commit 0400977 into 19.0 Jun 26, 2026
21 checks passed
@gonzalesedwin1123 gonzalesedwin1123 deleted the codex/fix-vulnerability-exposing-dci-metrics branch June 26, 2026 10:29
gonzalesedwin1123 added a commit that referenced this pull request Jun 29, 2026
#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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants