Skip to content

security(spp_api_v2_simulation): analytics API computes unpublished statistics (no is_published_api allowlist) [Severity: Medium] #360

Description

@gonzalesedwin1123

Follow-up from PR #351... correction: from PR #359 (GIS stats publication boundary). A sweep for the same pattern found a parallel exposure in the simulation analytics API, which PR #359 (scoped to GIS per owner decision) does not touch.

Finding

spp_api_v2_simulation accepts client-supplied statistic names and passes them straight to the aggregation engine with no publication allowlist:

  • spp_api_v2_simulation/schemas/analytics.py:31ComputeAggregationRequest.statistics: list[str] | None (client-supplied).
  • spp_api_v2_simulation/routers/analytics.py:~52 — passes request.statistics to the service; the only gate is the aggregation:read scope.
  • spp_api_v2_simulation/services/analytics_api_service.py:23-46compute_aggregation(...) forwards statistics to self.env["spp.analytics.service"].sudo().compute_aggregation(scope=..., statistics=statistics, context="api").

As with the GIS path (#359), the aggregation registry (spp_analytics/models/indicator_registry.py _try_statistic_model/_try_variable_model) resolves those names via sudo() against all spp.indicator and all spp.cel.variable records, with no is_published_api / context check. So a client with only aggregation:read can request unpublished indicator names, or raw CEL variable names, and receive their aggregates — bypassing the is_published_api publication boundary (spp.indicator has is_published_api, exposed via get_published_for_context("api")).

Severity: Medium

Same class and impact as #359 (k-anonymity mitigates, publication boundary bypassed for sensitive aggregates), for the API context instead of GIS.

Suggested fix

Mirror #359: in AnalyticsApiService.compute_aggregation (or the router), restrict supplied statistics to the API-published allowlist — env["spp.indicator"].get_published_for_context("api").mapped("name") — dropping unpublished/raw names before calling the aggregation service (drop, not error, to avoid an existence oracle). Consider whether the api context should also cover CEL variables (it currently doesn't publish them directly).

Broader consideration: both #359 and this issue stem from the shared registry resolving any name under sudo regardless of context. A defense-in-depth option is to have compute_aggregation itself honor the context's publication flag; deferred because it is context-agnostic today and shared by multiple callers (needs its own design decision).

Source: swept while implementing #359. Related: #359.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions