Mint TOTP codes in the vault instead of releasing the seed - #1
Draft
AP3X-Dev wants to merge 1 commit into
Draft
Conversation
A TOTP seed is a long-lived secret whose only purpose is deriving a short-lived code. Handing it to an agent grants the ability to mint codes indefinitely, from anywhere, long after the session is revoked — which is the thing proxy mode exists to prevent. Add a `totp` service whose `code` operation derives the value server-side and returns only that. Capability `mode` could not carry this on its own: it is per-grant operator configuration, so a misconfigured grant could still resolve the seed, and the `json` resolution format returns the raw secret without consulting a service's env mapping. So ServiceSpec gains `resolvable`, and resolve_secrets skips a non-resolvable service in every format regardless of mode — confinement becomes a property of the service rather than of each grant. The derivation ignores any caller-supplied timestamp; honouring one would let an agent mint codes for arbitrary points in time and undo the bound the 30-second window provides. Tested against the RFC 6238 published vectors for SHA1, SHA256 and SHA512 rather than round-tripping our own output, so this agrees with real authenticators. Also teaches the credential validator `integer` fields, for digits/period, rejecting bool since it subclasses int.
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Why
A TOTP seed is a long-lived secret whose only purpose is deriving a short-lived code. Releasing it to an agent grants the ability to mint codes indefinitely, from any machine, long after the session is revoked — exactly what
proxy_onlymode exists to prevent. This adds atotpservice whosecodeoperation derives the value inside the vault and returns only that.Useful to any agent doing 2FA, not just browser agents.
The part that isn't obvious
Capability
modecan't carry this on its own, for two reasons:modeis per-grant operator configuration, so a misconfigured grant could still resolve the seed.resolve_secretsreturns the raw secret forformat: "json"— it never consults the service'senv_mapping. So an empty mapping is not a defence.So
ServiceSpecgainsresolvable, andresolve_secretsskips a non-resolvable service in every format regardless of mode. Confinement becomes a property of the service rather than of each grant. That generalises beyond TOTP to any credential that should only ever be used, never handed over (signing keys, for instance).Omission follows the existing §5.1 convention — dropped from the bundle rather than failing the request — so a mixed resolution still returns what it legitimately may.
Contents
services/totp.py— RFC 6238 on the standard library, no new dependencyservices/registry.py—ServiceSpec.resolvable,TOTP_SPEC,integerfield validationroutes/avp.py— enforceresolvableinresolve_secrets; register the adapterdocs/AVP_v1_SPEC.md— §7.2 non-resolvable services, §7.3.1 derived credentials, §10 confinement independent of grantstests/test_totp.py— 27 testsResponse shape
{ "code": "492039", "expires_in": 17, "period": 30, "digits": 6 }expires_inlets a caller wait for the next window instead of racing the boundary.Notes
integervalidation rejectsboolexplicitly, sinceboolsubclassesintin Python.Verification
133 passed(106 existing + 27 new),ruff checkclean. The existingtest_every_registry_operation_has_adaptercovers the new operation automatically.Not done, and worth a follow-up: a conformance-suite case asserting the seed cannot be pulled out via
resolve_secretsagainst a live vault. I skipped it rather than add a CI check I could not run locally (needs Postgres).Draft because the
resolvablefield is a spec addition — worth a look before it becomes contract.