Skip to content

AI Gateway usage endpoints leak cross-project data to any AiGatewayRead holder #705

Description

@dviejokfs

Summary

GET /api/ai/usage/summary and GET /api/ai/usage/by-provider (crates/temps-ai-gateway/src/handlers/usage.rs) are gated only by permission_guard!(auth, AiGatewayRead), with no ownership check tying the returned rows to projects/environments the caller actually has access to.

Permission::AiGatewayRead is held by default by Role::User, Role::Reader, Role::Admin, and Role::PlatformAdmin (crates/temps-auth/src/permissions.rs) — i.e. any authenticated project member, not just operators.

Impact

A caller with AiGatewayRead (the default for ordinary project members, granted for their own day-to-day AI usage) can pass an arbitrary project_id or environment_id query parameter and read AI usage data — including deployment_token_id — for any project on the instance, not just their own. This is cross-tenant enumeration of model usage, token volumes, and deployment token IDs belonging to other tenants.

Root cause

The handlers build their UsageFilter directly from query parameters (UsageQueryParams::to_filter()) with no scoping to auth.user_id() or a project-ownership check. usage_service.rs::build_filter_clause does support a user_id filter, but the handlers never populate it from the authenticated caller — filtering is entirely caller-controlled and opt-in.

Discovery context

Found during a /review-pr security-audit pass on PR #543 ("feat(ai-gateway): add project usage governance"), which added the project_id/environment_id query-param wiring that makes this easy to target. The underlying authorization gap (no ownership scoping at all) predates that PR — the endpoint already returned the full instance-wide dataset to any AiGatewayRead holder when unfiltered; the PR only added a convenient way to slice that already-unscoped data by project/environment. Not blocking that PR's merge; filed separately per this repo's existing pattern for the same bug class (see #403, the analogous proxy-logs cross-project IDOR).

Suggested fix

Either:

  • Add a require_operator-style check (only operators may query other projects' usage), or
  • When the caller is not an operator, restrict the query to project_id IN (<projects the caller is authorized for>) server-side, ignoring/rejecting a caller-supplied project_id outside that set.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    securitySecurity vulnerability fix

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions