-
Notifications
You must be signed in to change notification settings - Fork 196
fix(keys): scope key management to workspace and gate privileged ops #93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
hasitpbhatt
wants to merge
4
commits into
Continuum-AI-Corp:main
Choose a base branch
from
hasitpbhatt:clean/89
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
e1d146a
fix(keys): scope key list/revoke to caller workspace and block restri…
hasitpbhatt 757c277
test(keys): add provisioning test for restricted/budgeted keys
hasitpbhatt c6b1097
fix(keys): gate privileged management behind require_unrestricted
hasitpbhatt 46a79ea
fix(keys): gate DELETE /v1/quality/overrides behind require_unrestricted
hasitpbhatt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| """Authorization guards for privileged key-management operations. | ||
|
|
||
| A *restricted* key is one that carries any limitation — a ``model_allowlist`` | ||
| or a ``budget_limit_cents`` cap. Restricted keys are issued as child keys with | ||
| reduced privilege; letting them mint/rotate provider credentials, rewrite | ||
| routing, or override quality scores would let them escalate to the full | ||
| privilege of an unrestricted key. Only unrestricted keys may perform those | ||
| operations, so the escalation path is closed everywhere, not just on | ||
| ``/v1/keys``. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from fastapi import Depends, HTTPException | ||
|
|
||
| from app.deps import get_key_context | ||
| from packages.auth.types import KeyContext | ||
|
|
||
|
|
||
| def is_restricted(kc: KeyContext) -> bool: | ||
| """True if the key carries any usage restriction.""" | ||
| return kc.model_allowlist is not None or kc.budget_limit_cents is not None | ||
|
|
||
|
|
||
| def require_unrestricted(kc: KeyContext = Depends(get_key_context)) -> None: | ||
| """FastAPI dependency: reject restricted keys from management endpoints. | ||
|
|
||
| Usable both as ``Depends(require_unrestricted)`` on a route and as a direct | ||
| ``require_unrestricted(kc)`` call. Synchronous on purpose — it performs no | ||
| I/O, only a privilege check and a raise — so it works identically whether | ||
| FastAPI awaits it as a dependency or a route calls it inline. | ||
| """ | ||
| if is_restricted(kc): | ||
| raise HTTPException( | ||
| status_code=403, | ||
| detail=( | ||
| "Restricted API keys cannot perform management operations. " | ||
| "Use an unrestricted key." | ||
| ), | ||
| ) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟠 P1 Enforce the budget_limit_cents cap the new key-minting API promises
The existing restriction mechanism for API keys — model_allowlist — is enforced on every inference path: chat.py:303 (pinned requests), auto_routing.py:306-313 (auto resolution filters the candidate set), and check_model_allowlist in anthropic_compat.py / gemini_compat.py (native surfaces + count_tokens). This change introduces the second restriction mechanism, budget_limit_cents, as a settable field on POST /v1/keys, and packages/auth/guards.py documents it as "a budget_limit_cents cap" and treats it as a real restriction (is_restricted returns True when set). But nothing anywhere in the request path reads budget_limit_cents to limit spend: not the AuthMiddleware, not validate_api_key, not chat.py/execute_chat, not router_cache. A "budgeted" child key minted via the new API (e.g. budget_limit_cents=500) is blocked from management endpoints but spends without any cap at inference time — exactly like an unrestricted key. So the new variant of "restricted key" omits the one thing the existing variant (allowlist) does: applying its limit at runtime. An operator who provisions a $5-capped key gets an unlimited key, and the only consumer-visible effect of the cap is the 403 on management routes. If the budget cap is intended to be functional (as the field name, docstring and test naming imply), the omission is a money/limit defect: the key is limited wrongly (not at all).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved by merging with #91:
budget_limit_centsis enforced at inference time in #91 (packages/auth/spend.py+app/routes/chat.py— pre-checkis_exhaustedandcharge_budget). #93 provisions the field and gates key-management; the runtime cap arrives with #91. Marked as a dependency in the PR description.