Durable-fix follow-up spanning PR #234 and PR #235 (internal/plans/pr234-dci-access-audit-findings.md, "Related: PR #235"). Filing for tracking.
Finding
PR #235 gates external DCI Social Registry predicate searches from filtering/counting/paging on sensitive DCI metrics (a read-oracle for private disability r.dci.dr.* and vital/civil-status r.dci.crvs.* data). The guard (spp_dci_server_social/services/search_service.py, _validate_external_predicate_expression) is a best-effort regex denylist over the raw CEL predicate string.
It is evasion-resistant today only because the CEL translator accepts the metric name solely as a literal first-positional arg (string-concat and kwarg-reorder do not resolve). But it is a denylist — it fails open on every new sensitive accessor added in the future.
Suggested fix
Replace the denylist with an allowlist of safe metrics enforced against the parsed AST (spp_cel_domain/services/cel_parser.py Call/Attr/Ident nodes) or the translated plan (MetricCompare.metric / AggMetricCompare.metric in spp_cel_domain/models/cel_translator.py), keyed on resolved metric identity rather than surface syntax. cel_service.compile_expression does not currently surface the referenced-metric set, so this needs either a small addition to that return shape or a parser-level AST walk.
A single allowlist-based metric-access policy at the CEL layer would cover both the read oracle (#235) and the cache-population route (#234), replacing route-by-route denylist patching.
Source: internal/plans/pr234-dci-access-audit-findings.md. Related: #234, #235.
Durable-fix follow-up spanning PR #234 and PR #235 (
internal/plans/pr234-dci-access-audit-findings.md, "Related: PR #235"). Filing for tracking.Finding
PR #235 gates external DCI Social Registry predicate searches from filtering/counting/paging on sensitive DCI metrics (a read-oracle for private disability
r.dci.dr.*and vital/civil-statusr.dci.crvs.*data). The guard (spp_dci_server_social/services/search_service.py,_validate_external_predicate_expression) is a best-effort regex denylist over the raw CEL predicate string.It is evasion-resistant today only because the CEL translator accepts the metric name solely as a literal first-positional arg (string-concat and kwarg-reorder do not resolve). But it is a denylist — it fails open on every new sensitive accessor added in the future.
Suggested fix
Replace the denylist with an allowlist of safe metrics enforced against the parsed AST (
spp_cel_domain/services/cel_parser.pyCall/Attr/Identnodes) or the translated plan (MetricCompare.metric/AggMetricCompare.metricinspp_cel_domain/models/cel_translator.py), keyed on resolved metric identity rather than surface syntax.cel_service.compile_expressiondoes not currently surface the referenced-metric set, so this needs either a small addition to that return shape or a parser-level AST walk.A single allowlist-based metric-access policy at the CEL layer would cover both the read oracle (#235) and the cache-population route (#234), replacing route-by-route denylist patching.
Source:
internal/plans/pr234-dci-access-audit-findings.md. Related: #234, #235.