Add GeneratingKeyProvider with production guard (ADR-0018, ADR-0019)#671
Open
ojongerius wants to merge 5 commits into
Open
Add GeneratingKeyProvider with production guard (ADR-0018, ADR-0019)#671ojongerius wants to merge 5 commits into
ojongerius wants to merge 5 commits into
Conversation
Implements ADR-0019 §S2 (#476) across the TypeScript, Go, and Python SDKs. Introduces the KeyProvider abstraction from ADR-0018 and its dev-only GeneratingKeyProvider, which: - throws ProductionKeyProviderError (Go: ErrProductionKeyProvider) when AGENTRECEIPTS_PRODUCTION=true, before any key is generated - emits exactly one loud stderr warning per process otherwise - generates a stable Ed25519 keypair for dev/bootstrap use Documents the guard explicitly in the ephemeral-compute deployment guide.
Drop the dual `import ... as kp_module` + `from ... import` for the same module in the key-provider test; reset the once-per-process latch via monkeypatch's string-target form instead. Addresses a PR review nit.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a development-only GeneratingKeyProvider to the Go/TypeScript/Python SDKs, guarded by AGENTRECEIPTS_PRODUCTION=true to prevent accidental key auto-generation (and DID rotation) on production ephemeral compute deployments, and documents the recommended deployment posture.
Changes:
- Introduces
GeneratingKeyProvider+ production guard + one-time stderr warning across TS/Python/Go. - Exposes new provider/error APIs from the SDK entrypoints and adds unit tests for guard/warning/stability.
- Updates the ephemeral compute deployment guide with explicit production guidance.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| site/src/content/docs/deployment/ephemeral-compute.mdx | Documents why auto-generated keys are unsafe on cold starts and how AGENTRECEIPTS_PRODUCTION=true prevents misuse. |
| sdk/ts/src/receipt/key-provider.ts | Adds TS KeyProvider interface, GeneratingKeyProvider, and ProductionKeyProviderError with production guard + one-time warning. |
| sdk/ts/src/receipt/key-provider.test.ts | Adds TS tests for production guard, warning behavior, and keypair stability. |
| sdk/ts/src/index.ts | Re-exports the new TS key-provider symbols from the root entrypoint. |
| sdk/py/src/agent_receipts/receipt/key_provider.py | Adds Python KeyProvider protocol, GeneratingKeyProvider, and ProductionKeyProviderError with production guard + one-time warning. |
| sdk/py/tests/receipt/test_key_provider.py | Adds Python tests for guard, warning behavior, keypair stability, and sign/verify using generated keys. |
| sdk/py/src/agent_receipts/receipt/init.py | Exposes the new Python key-provider API from the receipt package. |
| sdk/py/src/agent_receipts/init.py | Exposes the new Python key-provider API from the top-level package. |
| sdk/go/receipt/keyprovider.go | Adds Go KeyProvider, GeneratingKeyProvider, and ErrProductionKeyProvider with production guard + one-time warning. |
| sdk/go/receipt/keyprovider_test.go | Adds Go tests for guard, warning behavior, keypair stability, and sign/verify using generated keys. |
- Go: wrap the GenerateKeyPair error with operation context in
NewGeneratingKeyProvider, matching the SDK's fmt.Errorf("...: %w")
convention.
- Go test: check the error from the second GetKeyPair call rather than
discarding it.
- TS: re-export GeneratingKeyProvider / KeyProvider /
ProductionKeyProviderError from the ./receipt subpath entrypoint, so
consumers importing "@agnt-rcpt/sdk-ts/receipt" get them too.
The "exactly one warning per process" test installed a second vi.spyOn(process.stderr, "write") on top of the one beforeEach already creates. Double-spying the same global method is fragile (restore ordering / potential flakiness). process.stderr is a singleton, so the beforeEach spy already captures writes from the re-imported module — assert on it directly instead. Addresses a PR review comment.
Comment on lines
+5
to
+24
| import os | ||
| import sys | ||
| from typing import Protocol, runtime_checkable | ||
|
|
||
| from agent_receipts.receipt.signing import KeyPair, generate_key_pair | ||
|
|
||
| _PRODUCTION_ENV_VAR = "AGENTRECEIPTS_PRODUCTION" | ||
| """Environment variable that marks a production deployment. A | ||
| :class:`GeneratingKeyProvider` refuses to run when it is set to the exact | ||
| value ``"true"`` (see ADR-0018 § Key generation policy and ADR-0019 § S2).""" | ||
|
|
||
| _DEV_WARNING = ( | ||
| "⚠ GeneratingKeyProvider is dev-only — set AGENTRECEIPTS_PRODUCTION=true " | ||
| "to disable in production" | ||
| ) | ||
| """The one-line, dev-only warning emitted at most once per process.""" | ||
|
|
||
| # One stderr warning per process, regardless of how many providers are built. | ||
| _dev_warning_emitted = False | ||
|
|
Comment on lines
+71
to
+73
| if not _dev_warning_emitted: | ||
| _dev_warning_emitted = True | ||
| print(_DEV_WARNING, file=sys.stderr) |
7d4650b to
36fdf9e
Compare
Comment on lines
+46
to
+55
| it("generates a usable, stable keypair outside production", async () => { | ||
| const provider = new GeneratingKeyProvider(); | ||
| const kp = await provider.getKeyPair(); | ||
|
|
||
| expect(kp.publicKey).toContain("BEGIN PUBLIC KEY"); | ||
| expect(kp.privateKey).toContain("BEGIN PRIVATE KEY"); | ||
|
|
||
| // Stable for the lifetime of the provider. | ||
| expect(await provider.getKeyPair()).toEqual(kp); | ||
| }); |
Comment on lines
+52
to
+54
| // It is explicitly prohibited in production: construct one when | ||
| // AGENTRECEIPTS_PRODUCTION=true and NewGeneratingKeyProvider returns | ||
| // ErrProductionKeyProvider before any key is generated. |
Comment on lines
+5
to
+23
| import os | ||
| import sys | ||
| from typing import Protocol, runtime_checkable | ||
|
|
||
| from agent_receipts.receipt.signing import KeyPair, generate_key_pair | ||
|
|
||
| _PRODUCTION_ENV_VAR = "AGENTRECEIPTS_PRODUCTION" | ||
| """Environment variable that marks a production deployment. A | ||
| :class:`GeneratingKeyProvider` refuses to run when it is set to the exact | ||
| value ``"true"`` (see ADR-0018 § Key generation policy and ADR-0019 § S2).""" | ||
|
|
||
| _DEV_WARNING = ( | ||
| "⚠ GeneratingKeyProvider is dev-only — set AGENTRECEIPTS_PRODUCTION=true " | ||
| "to disable in production" | ||
| ) | ||
| """The one-line, dev-only warning emitted at most once per process.""" | ||
|
|
||
| # One stderr warning per process, regardless of how many providers are built. | ||
| _dev_warning_emitted = False |
Comment on lines
+71
to
+73
| if not _dev_warning_emitted: | ||
| _dev_warning_emitted = True | ||
| print(_DEV_WARNING, file=sys.stderr) |
- Python: guard dev-only warning latch with threading.Lock so concurrent GeneratingKeyProvider construction cannot emit the warning more than once. - TypeScript: add sign+verify test to confirm the generated keypair produces a valid Ed25519 receipt signature. - Go: fix grammatically incorrect doc comment on GeneratingKeyProvider.
| (see ADR-0018). | ||
| """ | ||
|
|
||
| def get_key_pair(self) -> KeyPair: ... |
|
|
||
| with _dev_warning_lock: | ||
| if not _dev_warning_emitted: | ||
| _dev_warning_emitted = True |
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.
What
Implements
GeneratingKeyProvideracross all SDKs (Go, TypeScript, Python) with a production safety guard. This dev-only key provider generates fresh Ed25519 keypairs and refuses to run whenAGENTRECEIPTS_PRODUCTION=true, preventing silent DID rotation on ephemeral compute cold starts.Why
ADR-0018 defines a
KeyProviderabstraction for supplying keypairs to the signing layer. ADR-0019 § S2 identifies a critical failure mode: auto-generating keys in production silently creates a new DID on every cold start, producing an unverifiable audit trail with no error surfaced.GeneratingKeyProvideris intentionally limited to development and bootstrap scenarios. The production guard makes this failure mode unreachable by failing fast and loudly when misconfigured in production.Implementation Details
AGENTRECEIPTS_PRODUCTION=true(exact match) before generating any key material. If set, they raiseProductionKeyProviderErrorimmediately.Checklist
go vet,ruff check,biomeas applicable)Security
crypto/ed25519, TypeScriptcrypto.generateKeyPairSync, Pythoncryptography.hazmat.primitives.asymmetric.ed25519)