Skip to content

Add scoped execution and settlement proof verification#28

Merged
GsCommand merged 1 commit into
mainfrom
codex/add-support-for-clas-scoped-proofs
Jun 13, 2026
Merged

Add scoped execution and settlement proof verification#28
GsCommand merged 1 commit into
mainfrom
codex/add-support-for-clas-scoped-proofs

Conversation

@GsCommand

Copy link
Copy Markdown
Contributor

Motivation

  • Support CLAS clas.execution.receipt.v1 receipts that can contain multiple scoped proofs[] where each proof signs only the fields listed in proof.covers[] so execution and settlement attestations can coexist on one receipt.
  • Ensure each proof is verified only against its exact covered payload (preventing agents from being treated as signing settlement unless they explicitly cover settlement).
  • Preserve existing single-proof metadata.proof behavior for backward compatibility and avoid introducing network calls or rail-specific logic in core verification.

Description

  • Add typed scoped-proof primitives: CommandLayerScopedProof, ScopedProofType, SCOPED_PROOF_COVERS, VerifyScopedProofResult, and VerifyScopedProofsResult to model execution and settlement proofs and their exact ordered coverage requirements.
  • Implement buildCoveredPayload(receipt, proof) which materializes only the top-level fields named by proof.covers[], validates exact ordered covers per SCOPED_PROOF_COVERS, and fails closed on malformed/unsupported covers or missing fields.
  • Implement verifyScopedProof() and verifyScopedProofs() to canonicalize the covered payload with the existing json.sorted_keys.v1, recompute SHA-256, and verify Ed25519 signatures, returning per-proof results (signature_valid, hash_matches, covered, signer, ok, errors).
  • Export new helpers and types from the package entrypoint and add test/scoped-proofs.test.ts plus a README note documenting the scoped proof model and backward compatibility with verifyCommandLayerReceipt().

Testing

  • Ran unit tests with npm test, which executed the full suite including the new CLAS scoped proofs tests and reported 86 tests passing and 0 failures.
  • Built the package with npm run build and a typecheck with npm run typecheck, both completed successfully.
  • New tests exercise covered-payload materialization, execution-only verification, execution+settlement verification, tamper isolation between proofs, invalid covers detection, missing settlement proof behavior, unknown proof type, and malformed covers[] handling.

Codex Task

@GsCommand GsCommand merged commit c50de26 into main Jun 13, 2026
3 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 92d7dbfb4e

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/compat.ts
Comment on lines +269 to +270
if (receipt.settlement !== undefined && !proofs.some((proof) => proof.type === "settlement" && proof.ok)) {
errors.push("ERR_MISSING_SETTLEMENT_PROOF");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require an execution proof before returning VERIFIED

For a clas.execution.receipt.v1 receipt that contains a valid settlement proof but no execution proof, this code only checks that settlement has an OK proof, so proofs.every(...) can still return VERIFIED while verb, agent, and action are completely unsigned and can be arbitrary or tampered. Since scoped proofs intentionally separate settlement from execution, require at least one OK execution proof before accepting the receipt.

Useful? React with 👍 / 👎.

Comment thread src/compat.ts
const covered = Array.isArray(proof?.covers) ? [...proof.covers] : [];
const result: VerifyScopedProofResult = {
type: typeof proof?.type === "string" ? proof.type : "",
signer: proof ? getScopedProofSigner(proof) : undefined,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return INVALID when scoped signatures are missing

When a proof object omits signature, result construction calls getScopedProofSigner(proof) before the validation below can add ERR_MISSING_SIGNATURE, and that helper dereferences proof.signature.signer. A malformed scoped proof with no signature therefore throws a TypeError from verifyScopedProofs() instead of failing closed with an INVALID result.

Useful? React with 👍 / 👎.

Comment thread src/compat.ts
const key = opts.resolvePublicKey?.(proof) ?? opts.publicKeysByKid?.[proof.signature.kid] ?? opts.publicKeyPemOrDer;
if (!key) {
errors.push("ERR_MISSING_PUBLIC_KEY");
} else if (verifyCanonical(canonical, proof.signature.value, key)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Catch malformed scoped signatures during verification

If the covered-payload hash matches but signature.value is malformed base64 or decodes to the wrong length, verifyCanonical() throws before returning false; this call is not caught, so attacker-supplied malformed proofs can make verifyScopedProofs() throw rather than return INVALID. Wrap this verification path and convert verification errors to ERR_SIGNATURE_INVALID or a malformed-signature error.

Useful? React with 👍 / 👎.

Comment thread src/compat.ts
}
}

result.ok = errors.length === 0 && result.hash_matches && result.signature_valid;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate scoped proof signers before accepting proofs

For an execution proof whose signature.signer/signer_id does not match the signed receipt.agent identity, this still sets ok from only hash and signature validity, so a receipt can be reported VERIFIED as long as the provided kid/key signs the covered fields even though the declared signer is not the agent. Scoped verification should fail closed on signer mismatch, e.g. by comparing execution proof signer to receipt.agent/receipt.agent.id or accepting an expected signer constraint as the older verifier does.

Useful? React with 👍 / 👎.

Comment thread src/compat.ts
if (!opts.publicKeyPemOrDer && !opts.publicKeysByKid && !opts.resolvePublicKey) {
throw new Error("verifyScopedProofs requires publicKeyPemOrDer, publicKeysByKid, or resolvePublicKey");
}
if (!Array.isArray(receipt.proofs) || receipt.proofs.length === 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return INVALID for null scoped receipts

When the verifier is called on an untrusted parsed body that is null or undefined, this dereferences receipt.proofs before any structure check, so verifyScopedProofs() throws a TypeError instead of failing closed with an INVALID result. Guard receipt itself before reading proofs so malformed receipt inputs cannot crash callers.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant