Skip to content

Mint TOTP codes in the vault instead of releasing the seed - #1

Draft
AP3X-Dev wants to merge 1 commit into
masterfrom
feat/totp-proxy-operation
Draft

Mint TOTP codes in the vault instead of releasing the seed#1
AP3X-Dev wants to merge 1 commit into
masterfrom
feat/totp-proxy-operation

Conversation

@AP3X-Dev

@AP3X-Dev AP3X-Dev commented Aug 4, 2026

Copy link
Copy Markdown
Owner

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_only mode exists to prevent. This adds a totp service whose code operation 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 mode can't carry this on its own, for two reasons:

  1. mode is per-grant operator configuration, so a misconfigured grant could still resolve the seed.
  2. resolve_secrets returns the raw secret for format: "json" — it never consults the service's env_mapping. So an empty mapping is not a defence.

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. 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 dependency
  • services/registry.pyServiceSpec.resolvable, TOTP_SPEC, integer field validation
  • routes/avp.py — enforce resolvable in resolve_secrets; register the adapter
  • docs/AVP_v1_SPEC.md — §7.2 non-resolvable services, §7.3.1 derived credentials, §10 confinement independent of grants
  • tests/test_totp.py — 27 tests

Response shape

{ "code": "492039", "expires_in": 17, "period": 30, "digits": 6 }

expires_in lets a caller wait for the next window instead of racing the boundary.

Notes

  • The derivation ignores any caller-supplied timestamp. Honouring one would let an agent mint codes for arbitrary times, undoing the bound the 30-second window provides.
  • Tested against the published RFC 6238 vectors for SHA1/SHA256/SHA512 rather than round-tripping our own output, so it demonstrably agrees with real authenticators.
  • integer validation rejects bool explicitly, since bool subclasses int in Python.

Verification

133 passed (106 existing + 27 new), ruff check clean. The existing test_every_registry_operation_has_adapter covers the new operation automatically.

Not done, and worth a follow-up: a conformance-suite case asserting the seed cannot be pulled out via resolve_secrets against a live vault. I skipped it rather than add a CI check I could not run locally (needs Postgres).

Draft because the resolvable field is a spec addition — worth a look before it becomes contract.

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

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2b6b29c3-7068-408d-9d20-c02a60a8ca6e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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