feat(repo): add quoter-signer kms signing and custody attestation - #179
feat(repo): add quoter-signer kms signing and custody attestation#179julien-devatom wants to merge 10 commits into
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- pin offer tick to uint256 and maxAssets to uint128 per the Midnight struct - require an alias-qualified quoter-signer Lambda ARN (no version/unqualified) - clear foreign signer sources for CLI middleware overrides in both directions - report the fail-closed middleware identity as a sanitized signer observation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Third TIB-2026-08-12 increment: the middleware now parses a strict QUOTER_SIGNER_POLICY deployment document (surface/mode/chain/maker pins, market allowlist with tick and fee ceilings, offer time windows, routine and protected fee ceilings with the emergency-bump reserve, remediation variants) and denies out-of-policy intents with a typed violation naming the violated check. Still fail-closed: no KMS access, nothing signed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-f85533 # Conflicts: # docs/decisions/TIB-2026-08-12-quoter-bot-kms-signing-middleware.md # pnpm-lock.yaml # services/quoter-signer/README.md # services/quoter-signer/package.json # services/quoter-signer/src/index.ts # services/quoter-signer/src/intent.utils.ts # services/quoter-signer/test/index.test.ts
The pinned v1 SHA runs the downloaded foundryup via bash, which broke repo-wide (main included) when upstream foundryup became a binary: 'cannot execute binary file' before any test executes. v1.9.1 downloads the pinned foundryup-init.sh installer and executes foundryup directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- viem-first bytes32 identity: market/group keys via hexToBigInt instead of toLowerCase in the policy parser, the check stage, and the intent parser's market count - refuse to serve a setup-remediation deployment with an empty remediation manifest (empty policy) instead of denying per intent - README create-function command now passes the policy via --environment file://environment.json Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement the KMS signing layer of TIB-2026-08-12 in the quoter-signer middleware: strict maker-key custody attestation (GetPublicKey, exact canonical-DER SPKI template parse with on-curve validation, derived address pinned to the policy maker) wired into the fail-closed evaluation pipeline, plus the digest-signing primitive the upcoming encode stages will call (Sign with MessageType DIGEST, strict canonical DER parse, low-s normalization, recovery check across both parities, mandatory KMS request id capture for CloudTrail reconciliation). Every intent is still denied: kms:Sign is never called and the encode-and-sign surfaces are later increments. New deployment parameters QUOTER_SIGNER_KMS_KEY_ID / QUOTER_SIGNER_KMS_REGION are strictly parsed; KMS failures map to typed KmsNotConfiguredError, retryable KmsUnavailableError, and KmsAttestationFailedError denials with the TIB's middleware.kms_error log event. The attested signer is memoized per execution environment with eviction on failure. @aws-sdk/client-kms and @noble/curves move into the pnpm catalog so the bot's direct-KMS path and the middleware stay on the same audited versions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@codex review |
|
@prd-carapulse[bot] can you review and approve if all good |
|
PR #179 review complete. ❌ Changes requested — not approved.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f52128a8e6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- Reject blank or whitespace-only KMS request ids as missing-request-id (carapulse blocking): a blank join key is as unreconcilable as an absent one; valid ids pass through untrimmed. - Pin Sign calls to the resolved key ARN captured at attestation (codex P2): a repointed alias can no longer route signing to an unattested key; attestation now requires the resolved ARN (new key-arn reason). - Single-attempt KMS client, no SDK retries (codex P2): every CloudTrail Sign event must reconcile with exactly one middleware signing record; retry decisions belong to the middleware's callers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@codex review |
|
@prd-carapulse[bot] can you review and approve if all good |
|
Review result: Changes requested. PR not approved. Blocking finding: KMS custody attestation is lazy: Required fix: Add the startup/dedicated attestation and freshness/context gates, with tests covering pre-attestation serving and stale/drift refresh—or narrow the PR contract and docs so this is not presented as the custody gate. CI: Lint, Typecheck, Dead-Code, and Notify pass. Test remains pending. Formal |
There was a problem hiding this comment.
Changes requested: the KMS custody check is lazy, not a startup/before-serving attestation. The TIB requires each active signing deployment to attest at cold start and before serving, then keep readiness red / signing closed when the attestation is stale or missing. Here createHandler() only calls createKmsMakerSigner() after a valid in-policy invocation (index.ts:145), and a successful promise is cached without a freshness/refresh mechanism. A malformed or out-of-policy request can therefore reach a serving container before custody is proven, and a later key/image/config drift is not detected. Add the required invocation/context-gated startup or dedicated attestation path (or narrow the PR contract and docs so this is not presented as the custody gate), with tests for pre-attestation serving and stale/drift refresh. CI Test is also still pending, so approval is withheld.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 484b49104f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- Cold-start attestation warm-up (carapulse): when the policy document and KMS variables are configured, custody proving starts at container init, before the first invocation; serving never depends on it and a misconfigured deployment stays a typed denial, never an init crash. - Attestation freshness bound (carapulse): the cached attested signer expires after KMS_ATTESTATION_FRESHNESS_MS (5 min), so key or deployment drift on a warm container fails closed at the next window; eviction is race-guarded so a stale rejection cannot drop a fresher attestation. README narrows the claim: the TIB's registry-backed startup/readiness attestation remains a later increment, and this build's guarantee is that the digest-signing primitive is reachable only behind a fresh attestation. - Denial-contract docs (codex): a denial no longer claims zero KMS activity — kms:Sign is never called, but the attested path performs the read-only GetPublicKey attestation. - Tests: cold-start warm-up, pre-attestation wire-contract serving, stale re-attestation with drift fail-closed (mutation-checked). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Round-two findings addressed in bd36b8b:
|
|
@codex review |
|
@prd-carapulse[bot] can you review and approve if all good |
|
Changes requested — not approved. • Blocking: • Checks: ✅ Lint, Typecheck, Dead-Code. ⏳ Test still running. • Review: #179 (review) |
Code Review SummaryVerdict: Changes Requested 🔴 (1 blocking issue, 0 suggestions) Blocking
Checks
Review: #179 (review) Reviewed by Hermes Agent |
|
Codex Review: Didn't find any major issues. Swish! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
Apply carapulse round-three blocking finding: signDigest() now records its attestation time and refuses to sign once it ages past KMS_ATTESTATION_FRESHNESS_MS (typed retryable KmsAttestationStaleError, no KMS call), so the freshness bound holds for a held signer object and never depends on the handler cache's resolution discipline alone. The constant moves into kms-signer.utils.ts, which owns attestation; expiry boundary test added (mutation-checked). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@codex review |
|
@prd-carapulse[bot] can you review and approve if all good |
|
Reviewed PR #179 at
Approval withheld until Test completes successfully. Review posted: #179 (review) Local test execution was blocked by Node engine mismatch: required |
There was a problem hiding this comment.
Reviewed current head 5ed3c3e.
No blocking code findings: the KMS request-id validation, ARN pinning, single-attempt client, cold-start attestation, cache refresh, signer freshness gate, strict SPKI/DER validation, low-s normalization, recovery checks, and regression coverage are present. git diff --check passes.
Approval withheld: required GitHub Test check is still in_progress; Lint, Typecheck, Dead-Code, and Notify pass. Approve after Test completes successfully.
|
Codex Review: Didn't find any major issues. Hooray! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
|
@prd-carapulse[bot] the Test check has completed successfully since your last review of 5ed3c3e (all checks green) — can you review and approve if all good |
|
Approved PR #179 at All checks pass: Lint, Test, Typecheck, Dead-Code, Notify. No blocking correctness or security findings. Merge not performed. |
There was a problem hiding this comment.
Reviewed current head 5ed3c3e. All required checks pass: Lint, Test, Typecheck, Dead-Code, and Notify. No blocking correctness or security findings.
f00b0bb to
81ff252
Compare
Implements the KMS signing layer of TIB-2026-08-12 in the quoter-signer middleware — the third increment, stacked on #178 (deterministic policy checks; retarget to
mainafter it merges).Why
The middleware is the future sole
kms:Signprincipal on the maker key, but the skeleton so far never touched KMS: intents passing every deterministic check were denied without proving anything about custody. This increment gives the image its custody root:kms:GetPublicKeyon the deployment-pinned key, exactECC_SECG_P256K1/SIGN_VERIFY/ECDSA_SHA_256shape, the resolved key ARN, strict canonical-SPKI parse, on-curve validation, and a derived address that must equal the policy-pinned maker. The cached attestation carries a five-minute freshness bound (KMS_ATTESTATION_FRESHNESS_MS), so drift on a warm container is re-checked at the next window; drift fails closed (KmsAttestationFailedError), a failed call is a typed retryable denial (KmsUnavailableError), missing/invalid KMS addressing refuses to serve (KmsNotConfiguredError), and the TIB'smiddleware.kms_errorlog event lands.kms:Sign(MessageType: 'DIGEST',ECDSA_SHA_256) issued against the resolved key ARN captured at attestation — never the configured alias, which could be repointed to an unattested key — on a single-attempt client (no SDK retries, so every CloudTrailSignevent reconciles with exactly one middleware signing record) → strict canonical ECDSA-DER parse → low-s normalization → recovery check across both parities against the attested maker → mandatory KMS request id capture (the CloudTrail reconciliation join key; a response without one, or with a blank one, is rejected rather than becoming an unreconcilable signature).Every intent is still denied —
kms:Signis never called by any reachable path, and intents passing every stage end inSigningNotImplementedErroruntil the encode stages and the reservation ledger land. The execution role needskms:GetPublicKeyonly.Design choices
SubjectPublicKeyInfohas exactly one 88-byte encoding — validated by exact prefix template plus@noble/curveson-curve check, unlike the bot's asn1js path. The DER signature parser mirrors the bot's proven parser rather than sharing a module, per the middleware's established mirror-not-share auditability rule. Both recorded in the TIB's Dependencies section.KmsTransport): the AWS adapter maps response fields verbatim; every custody decision lives in unit-tested code. Handler tests inject fakes via the newcreateHandlerfactory; the default transport itself is covered against a mocked SDK asserting the exact pinned command shapes (including the single-attempt client config and ARN-pinnedSign).@aws-sdk/client-kmsand@noble/curvesmove into the pnpm catalog so the bot's direct-KMS path and the middleware stay on the same audited versions.Validation
pnpm --filter @morpho-org/quoter-signer run typecheck,pnpm --filter @morpho-org/quoter-bot run typecheck— cleanpnpm lint— 0 warnings,pnpm format— appliedpnpm test: only the pre-existingRPC_URL_8453fork suites fail locally (environmental)Signtarget from the attested ARN fails 2 testsrevieweragent pass: no blocking findings; all 4 suggestions appliedsignDigest()refuses with a retryableKmsAttestationStaleErroronce its attestation ages past the window (carapulse blocking finding), landed in 5ed3c3e with a boundary test (mutation-checked)🤖 Generated with Claude Code