Skip to content

API-key create drops accounts/max_amount constraints (JSON tag mismatch between api and auth types) #951

Closed

Description

@IAmJSD

Summary

When a user creates an API key over HTTP (POST /api/api-keys), the accounts and max_amount constraints on its permissions are silently dropped. The key is stored without the account and purchase-amount limits the caller asked for. providers, services and regions survive.

This was found while working on #83 (PR #940). The reviewer on #940 agreed it should be tracked separately. It is pre-existing and #940 doesn't change it.

Cause

The two layers use different JSON field names for the same constraint, and the API-key path converts between them with a JSON round-trip:

  • The handler decodes the body into api.CreateAPIKeyRequest → api.Permission → api.PermissionConstraint (internal/api/types.go:371). Its JSON tags are accounts and max_amount, which match the OpenAPI spec (internal/api/openapi.yaml, around line 2706).
  • auth.Service.CreateAPIKeyAPI (internal/auth/service_apikeys_api.go:191) receives that value as any. It json.Marshals it and json.Unmarshals it into auth.APICreateAPIKeyRequest, whose Permissions are []auth.Permission.
  • auth.PermissionConstraints (internal/auth/types.go:59) tags those fields account_ids and max_purchase_amount. The unmarshal ignores accounts and max_amount, so AccountIDs and MaxPurchaseAmount come out empty.

Groups don't have this problem. They map the fields explicitly in apiPermissionToPermission / permissionToAPIPermission (internal/auth/service_api.go:184-214).

Impact

A key the caller meant to restrict to particular accounts or a purchase cap is created without those limits. It's still bounded by the owner's group permissions and the group ceiling, so it can't exceed what the owner can already do. It is not a privilege escalation past the owner. But the narrower scope the user asked for is silently not applied, and nothing in the response says so.

I haven't checked whether the listing (APIKeyInfo.Permissions, also []auth.Permission) shows the constraints back in the account_ids shape, which would hide the drop from API clients that read accounts. The fix PR should check this.

Expected

The account and amount constraints on POST /api/api-keys are stored and enforced, using the documented accounts / max_amount field names. The listing should return them in the same shape.

Suggested fix

Have APICreateAPIKeyRequest carry []APIPermission (the API-shaped type), convert with the existing apiPermissionToPermission, and add a regression test that posts accounts / max_amount through the real handler and checks the stored key.

Suggested labels (I can't apply them): type/security, type/bug, priority/p2, severity/medium.

🤖 Generated with Claude Code

Activity

  1. self-assigned this
    on Oct 16, 2026
  2. IAmJSD commented on Oct 16, 2026

    @IAmJSD
    CollaboratorAuthor

    Claiming: working on this

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions