Skip to content

Require API key step up for list and revoke - #528

Open
thomasluizon wants to merge 3 commits into
mainfrom
fix/ticket-529-apikey-stepup
Open

thomasluizon wants to merge 3 commits into
mainfrom
fix/ticket-529-apikey-stepup

Conversation

@thomasluizon

@thomasluizon thomasluizon commented Sep 17, 2026

Copy link
Copy Markdown
Owner

Summary

Require the existing email challenge grant for API key listing and revocation while preserving the Pro pay gate.

Listing checks the grant without consuming it. Revocation consumes the grant exactly once. The existing 428 challenge response remains distinct from the 403 Pro refusal.

The shared challenge operation now represents API key management. Existing request and confirm endpoint names remain unchanged.

Deploy order

Deploy the API change first. Current store builds lose API key listing and revocation until updated. Thomas accepted this beta break in #529.

Test evidence

  • Before changing tests or implementation, dotnet test tests/Orbit.Application.Tests/Orbit.Application.Tests.csproj --filter "FullyQualifiedName~GetApiKeysQueryHandlerTests|FullyQualifiedName~RevokeApiKeyCommandHandlerTests" --logger "console;verbosity=normal" passed all 5 existing tests with the defect present.
  • After adding the two missing no-grant cases, dotnet test tests/Orbit.Application.Tests/Orbit.Application.Tests.csproj --filter "FullyQualifiedName~GetApiKeysQueryHandlerTests|FullyQualifiedName~RevokeApiKeyCommandHandlerTests" --no-restore --logger "console;verbosity=minimal" failed both new tests. Each observed Expected result.IsFailure to be True, but found False. while the 5 existing tests still passed.
  • After the implementation, the same focused command passed all 16 cases. Coverage includes absent, spent, expired, valid, and non-Pro grants for listing and revocation.
  • dotnet build Orbit.slnx completed with 0 errors.
  • dotnet test --no-build passed all 6,256 tests.

Manual steps

  • Set RequireApiKeyCreationStepUp to true in the AppConfigs table, after the server change is deployed. Read the row back and verify its value is true.

Fixes #529

@thomasluizon

Copy link
Copy Markdown
Owner Author

Approach:

  1. Rename the shared email challenge operation from creation to API key management while keeping the existing request and confirm endpoints unchanged.
  2. Inject IAppConfigService and EmailChallengeService into the list and revoke handlers.
  3. Keep each Pro pay gate first, then require the existing grant when RequireApiKeyCreationStepUp is enabled.
  4. Let listing peek at the grant so repeated reads work. Let revocation spend it immediately before mutation so one confirmation authorizes one destructive action.
  5. Reuse the existing 428 API_KEY_CREATION_CHALLENGE_REQUIRED response so clients can distinguish step up from the 403 Pro refusal.
  6. Add handler and flow coverage in tests/Orbit.Application.Tests, including absent, spent, expired, valid, and non-Pro cases.

The implementation lands in the existing API key handlers, email challenge service, shared error copy, and their unit tests. I rejected a second grant type because creation already establishes the server-backed mechanism. I rejected consuming on list because opening or refreshing the surface must remain usable. I rejected retaining the grant after revoke because that would weaken the destructive boundary compared with creation.

pullfrog[bot]
pullfrog Bot previously approved these changes Sep 17, 2026

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes Reviewed the current PR head, which contains no changed files or behavioral surface.

  • Bootstrap commit The sole commit establishes the implementation branch without code changes; the described API key step-up behavior will need review after implementation commits land.

Pullfrog  | View workflow run | Using GPT Sol𝕏

The step up change moved the API key endpoints' shape, so `architecture.json`
and `architecture.html` no longer matched the tree and the `drift` check
failed on `git diff --exit-code`. Regenerated with `node tools/arch-map.mjs`,
which reports 45 entities and 0 untested feature folders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@pullfrog pullfrog 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.

Important

The new grant checks break the advertised chat and MCP API-key list and revoke flows once the feature flag is enabled.

Reviewed changes Reviewed the API key management step-up implementation added since the prior Pullfrog review.

  • Shared management grant Renamed the challenge operation from creation to management while retaining the existing cache keys and confirmation endpoints.
  • Protected list access Added a non-consuming authorization check after the Pro pay gate and before cached or repository-backed key data is returned.
  • Protected revocation Added authorization checks around owned-key lookup and consumes the one-time grant before revoking the key.
  • Regression coverage Added absent, valid, repeated-read, spent, expired, and non-Pro cases for list and revoke behavior.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using GPT Sol𝕏

false,
cancellationToken);
if (requiresStepUp &&
!challengeService.HasAuthorization(EmailChallengeOperation.ApiKeyManagement, request.UserId))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

GetApiKeysQuery is also dispatched by chat/MCP get_api_keys, and the same new grant gates manage_api_keys revocation after agent confirmation. Neither agent surface can issue or confirm the email challenge, so enabling the flag makes listing fail and revocation fail even after valid agent step-up unless the user separately completes the HTTP challenge.

Technical details
# Agent API-key operations cannot establish the management grant

## Affected sites
- `src/Orbit.Application/ApiKeys/Queries/GetApiKeysQuery.cs:42` - requires an email grant for the query dispatched directly by chat and MCP `get_api_keys`.
- `src/Orbit.Application/ApiKeys/Commands/RevokeApiKeyCommand.cs:34` and `:48` - requires and consumes that grant after `manage_api_keys` has already passed its separate agent step-up.
- `src/Orbit.Application/Chat/Tools/Implementations/GetApiKeysTool.cs:21` and `ManageApiKeysTool.cs:75` - dispatch the gated handlers but expose no email challenge operation.
- `src/Orbit.Api/Mcp/Tools/ApiKeyTools.cs:25` and `:59` - the read dispatches directly, while the mutation bridge validates agent policy without creating an `EmailChallengeService` grant.

## Required outcome
- Every advertised chat/MCP list and revoke flow must remain usable when `RequireApiKeyCreationStepUp` is enabled, or be rejected as a direct-user-only operation before it is advertised or executed.
- Preserve the intended email security boundary rather than treating agent confirmation as equivalent unless that equivalence is an explicit product decision.

## Open questions for the human
- Should a successful agent step-up satisfy API-key management authorization, or should chat/MCP API-key operations become direct-user-only?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant