Skip to content

release(v0.43.0): SEP-2787 attestation + execution-receipt pairing in proxy#168

Merged
vaaraio merged 1 commit into
mainfrom
release/v0.43.0
May 29, 2026
Merged

release(v0.43.0): SEP-2787 attestation + execution-receipt pairing in proxy#168
vaaraio merged 1 commit into
mainfrom
release/v0.43.0

Conversation

@vaaraio
Copy link
Copy Markdown
Owner

@vaaraio vaaraio commented May 29, 2026

Summary

  • Adds _mcp_attest.py: AttestPairEmitter emits a SEP-2787 request attestation and an execution receipt per allowed tools/call; paired JSON files go to a configurable receipts directory
  • --attest-signing-key + --attest-receipts-dir flags on vaara-mcp-proxy, off by default; key type is auto-detected: EC P-256 PEM = ES256, RSA PEM = RS256, raw bytes = HS256
  • serverFingerprint starts as a SHA-256 of the upstream command string and upgrades to a manifest hash on the first tools/list response, capturing which tools the proxy actually exposed
  • X-Vaara-Intent HTTP header lets operators supply a richer intent label per call; stdio falls back to tools/call/{tool_name}
  • Receipt carries a backLink digest over the full attestation wire bytes; a verifier can confirm the pair belongs together without trusting the proxy
  • An attestation is always paired with a receipt: if the upstream raises before returning a response, an errored receipt is still written so no attestation is left orphaned
  • Removes the policy modes section from README (already local since a5f19aa)
  • Bumps server.json + server-vaara-server.json to 0.43.0 alongside pyproject, __init__.py, and the npm package (five-surface version parity)
  • 963 tests pass, ruff clean

Test plan

  • pip install vaara==0.43.0 resolves on PyPI after Release workflow
  • npm install @vaara/client@0.43.0 resolves
  • MCP Registry both slots updated to 0.43.0
  • Smoke: generate an EC P-256 key, run proxy with --attest-signing-key + --attest-receipts-dir, call a tool, confirm paired *-attest.json / *-receipt.json written and verify_back_link passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added SEP-2787 attestation and execution receipt pairing for MCP proxy tool calls.
    • Introduced --attest-signing-key and --attest-receipts-dir CLI flags to enable attestation (off by default).
    • Added X-Vaara-Intent HTTP header support for intent override.
    • Improved server fingerprint initialization and upgrade behavior.
  • Chores

    • Bumped version to 0.43.0 across all packages.
    • Updated .gitignore to exclude local development artifacts.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Warning

Review limit reached

@vaaraio, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 46 minutes and 21 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f0675c42-cfe8-49bb-8892-ba06dc5e1e89

📥 Commits

Reviewing files that changed from the base of the PR and between a2a2635 and c74628a.

📒 Files selected for processing (11)
  • .gitignore
  • CHANGELOG.md
  • README.md
  • clients/ts/package.json
  • pyproject.toml
  • server-vaara-server.json
  • server.json
  • src/vaara/__init__.py
  • src/vaara/integrations/_mcp_attest.py
  • src/vaara/integrations/mcp_proxy.py
  • tests/test_integrations_mcp_proxy_attest.py
📝 Walkthrough

Walkthrough

This PR introduces SEP-2787 attestation and execution-receipt pairing for the Vaara MCP proxy, emitting cryptographically linked JSON artifacts per tool call with signature verification and back-link validation, controlled via new CLI flags, and comprehensively tested end-to-end.

Changes

SEP-2787 Attestation and Receipt Pairing

Layer / File(s) Summary
Attestation emitter core implementation
src/vaara/integrations/_mcp_attest.py
AttestConfigError exception, AttestPairEmitter class managing monotonic counter and per-upstream fingerprints (upgraded from command hash to manifest hash on first tools/list), and build_attest_emitter factory with signing-key auto-detection (EC/PEM → ES256, RSA/PEM → RS256, raw bytes → HS256) and validation.
MCP proxy structure for attestation
src/vaara/integrations/mcp_proxy.py
New imports for attestation types, HTTP request-scoped _REQUEST_INTENT context variable for operator intent override, extended VaaraMCPProxy.__init__ to accept optional attest_emitter, and instance state wiring.
Proxy request-flow attestation integration
src/vaara/integrations/mcp_proxy.py
X-Vaara-Intent header capture in HTTP /mcp endpoint with context cleanup, manifest fingerprint updates on tools/list response, attestation emission on tools/call entry with intent override, and receipt emission in all outcome paths (success/error/transport-failure) with paired counter/nonce linkage.
CLI flags and attestation configuration
src/vaara/integrations/mcp_proxy.py
--attest-signing-key and --attest-receipts-dir flags, _build_attest_emitter_from_args validation helper that refuses startup on misconfiguration, and wiring of constructed emitter into proxy constructor.
Test coverage and release documentation
tests/test_integrations_mcp_proxy_attest.py, CHANGELOG.md, README.md, .gitignore, version manifests
17 integration tests validating pairing correctness, signature/back-link verification, outcome status mapping, intent override via context variable, perimeter denylist filtering, fingerprint upgrade idempotency, and configuration error paths; plus CHANGELOG release entry (0.43.0), README subsection, version bumps across pyproject.toml, clients/ts/package.json, server-vaara-server.json, server.json, src/vaara/__init__.py, and .gitignore CodeGraph exclusion.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • vaaraio/vaara#139: Introduces SEP-2787 attestation envelope shape and signing helpers that this PR directly depends on for generating and verifying paired attestation/receipt artifacts.
  • vaaraio/vaara#113: Establishes the operator tools allowlist/denylist behavior that attestation emission respects (suppressing artifacts for denied tool calls).
  • vaaraio/vaara#116: Also modifies VaaraMCPProxy request flow to wire an optional receipt emitter into tools/call handling, creating potential merge conflict or precedent in the same proxy integration points.

🐰 A proxy that signs and pairs,
Two JSON files, both cryptographically fair,
Attestations fly free,
Receipts are verified thee,
SEP-2787 answers with care! 🎙️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: a release version bump (v0.43.0) with the primary feature being SEP-2787 attestation and execution-receipt pairing in the MCP proxy.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/v0.43.0

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 and usage tips.

Comment on lines +1470 to +1474
def _build_attest_emitter_from_args(
args: argparse.Namespace,
*,
upstreams: dict[str, list[str]],
) -> Optional[AttestPairEmitter]:
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/vaara/integrations/_mcp_attest.py`:
- Around line 298-307: When detecting an EllipticCurvePrivateKey in
build_attest_emitter() where you set alg = "ES256", add a check that the
key.curve is the P-256 curve (secp256r1 / ec.SECP256R1) and if not raise
AttestConfigError with a clear message requiring secp256r1; reference
sign_es256() in the message or comment so it's clear ES256 signing expects
32-byte r/s values, and note that AttestPairEmitter.emit_attestation() currently
swallows signing errors so we must reject unsupported curves up front.

In `@src/vaara/integrations/mcp_proxy.py`:
- Around line 1396-1400: The emitter is being built with the raw upstreams dict
keyed by the operator name (e.g. "github") which mismatches the proxy's
collapsing to "default" for a single upstream, causing
fingerprint_for(_REQUEST_UPSTREAM) to miss entries; to fix, normalize the
upstreams before calling _build_attest_emitter_from_args so its keys match
VaaraMCPProxy's slot names (when legacy_single is non-None replace the single
upstream key with "default" or otherwise construct normalized_upstreams =
{'default': list(next(iter(upstreams.values())))} and pass normalized_upstreams
to _build_attest_emitter_from_args instead of the original upstreams), ensuring
attest_emitter, legacy_single, and fingerprint_for use the same upstream name
mapping.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a89150b2-b996-4b4d-afcc-de3f1267679c

📥 Commits

Reviewing files that changed from the base of the PR and between 4608c36 and a2a2635.

📒 Files selected for processing (11)
  • .gitignore
  • CHANGELOG.md
  • README.md
  • clients/ts/package.json
  • pyproject.toml
  • server-vaara-server.json
  • server.json
  • src/vaara/__init__.py
  • src/vaara/integrations/_mcp_attest.py
  • src/vaara/integrations/mcp_proxy.py
  • tests/test_integrations_mcp_proxy_attest.py

Comment on lines +298 to +307
if isinstance(key, EllipticCurvePrivateKey):
alg: str = "ES256"
elif isinstance(key, RSAPrivateKey):
alg = "RS256"
else:
raise AttestConfigError(
"--attest-signing-key must be EC P-256 (ES256) or RSA (RS256). "
"Generate: openssl ecparam -genkey -name prime256v1 | "
"openssl pkcs8 -topk8 -nocrypt -out attest_key.pem"
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the SEP-2787 ES256 signing path for curve handling.
rg -nP -C4 'ES256|SECP256R1|ECDSA|def emit_attestation|def _sign' --type=py -g '!**/test*'

Repository: vaaraio/vaara

Length of output: 28818


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect ES256 sign/verify implementation details (curve assumptions like fixed 32-byte r||s).
rg -n "def sign_es256|def verify_es256" -S src/vaara/attestation/_sep2787_signing.py
sed -n '1,120p' src/vaara/attestation/_sep2787_signing.py
sed -n '120,220p' src/vaara/attestation/_sep2787_signing.py

# Inspect SEP-2787 emit/verify path for exception swallowing / behavior on sign failures.
rg -n "def emit_attestation|try:|except" -S src/vaara/attestation/_sep2787_emit.py
sed -n '1,240p' src/vaara/attestation/_sep2787_emit.py

# Inspect the MCP integration's emit_attestation to see if it swallows exceptions and produces artifacts.
rg -n "def emit_attestation|try:|except" -S src/vaara/integrations/_mcp_attest.py
sed -n '120,220p' src/vaara/integrations/_mcp_attest.py
sed -n '220,380p' src/vaara/integrations/_mcp_attest.py

Repository: vaaraio/vaara

Length of output: 17566


Enforce P-256 (secp256r1) for ES256 auto-detection

build_attest_emitter() labels any EllipticCurvePrivateKey as ES256, but sign_es256() always encodes r and s as fixed 32-byte values for the SEP-2787 ES256 wire format. For non-P-256 curves this can raise during signing, and AttestPairEmitter.emit_attestation() then catches the exception, logs it, returns None, and does not persist an attestation (or receipt). Reject non-secp256r1 keys up front with a clear AttestConfigError.

🛡️ Proposed curve check
         if isinstance(key, EllipticCurvePrivateKey):
-            alg: str = "ES256"
+            from cryptography.hazmat.primitives.asymmetric import ec
+            if not isinstance(key.curve, ec.SECP256R1):
+                raise AttestConfigError(
+                    "--attest-signing-key EC key must use the P-256 (secp256r1) "
+                    f"curve for ES256; got {key.curve.name!r}."
+                )
+            alg: str = "ES256"
         elif isinstance(key, RSAPrivateKey):
             alg = "RS256"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if isinstance(key, EllipticCurvePrivateKey):
alg: str = "ES256"
elif isinstance(key, RSAPrivateKey):
alg = "RS256"
else:
raise AttestConfigError(
"--attest-signing-key must be EC P-256 (ES256) or RSA (RS256). "
"Generate: openssl ecparam -genkey -name prime256v1 | "
"openssl pkcs8 -topk8 -nocrypt -out attest_key.pem"
)
if isinstance(key, EllipticCurvePrivateKey):
from cryptography.hazmat.primitives.asymmetric import ec
if not isinstance(key.curve, ec.SECP256R1):
raise AttestConfigError(
"--attest-signing-key EC key must use the P-256 (secp256r1) "
f"curve for ES256; got {key.curve.name!r}."
)
alg: str = "ES256"
elif isinstance(key, RSAPrivateKey):
alg = "RS256"
else:
raise AttestConfigError(
"--attest-signing-key must be EC P-256 (ES256) or RSA (RS256). "
"Generate: openssl ecparam -genkey -name prime256v1 | "
"openssl pkcs8 -topk8 -nocrypt -out attest_key.pem"
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vaara/integrations/_mcp_attest.py` around lines 298 - 307, When detecting
an EllipticCurvePrivateKey in build_attest_emitter() where you set alg =
"ES256", add a check that the key.curve is the P-256 curve (secp256r1 /
ec.SECP256R1) and if not raise AttestConfigError with a clear message requiring
secp256r1; reference sign_es256() in the message or comment so it's clear ES256
signing expects 32-byte r/s values, and note that
AttestPairEmitter.emit_attestation() currently swallows signing errors so we
must reject unsupported curves up front.

Comment on lines +1396 to 1400
attest_emitter = _build_attest_emitter_from_args(args, upstreams=upstreams)

legacy_single = (
list(next(iter(upstreams.values()))) if len(upstreams) == 1 else None
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Emitter fingerprint table is keyed under the wrong upstream name for a named single upstream.

The emitter is built with the raw upstreams dict (keyed by the operator-supplied name, e.g. github), but when there is exactly one upstream, legacy_single is non-None and VaaraMCPProxy.__init__ collapses it into the "default" slot. At runtime _REQUEST_UPSTREAM resolves to "default" for the single-upstream case (a named X-Vaara-Upstream header even 404s, since only "default" exists), so fingerprint_for("default") misses the precomputed cmd:sha256:... entry stored under github and falls back to the cmd:sha256:unknown-default placeholder. Any tools/call before the first tools/list upgrade therefore carries a bogus server fingerprint. A bare --upstream CMD (keyed default) and multi-upstream fan-out are unaffected; only the documented --upstream NAME=CMD single-upstream form is.

Normalize the names the emitter is built with to match how the proxy slots them.

🐛 Proposed fix
-    attest_emitter = _build_attest_emitter_from_args(args, upstreams=upstreams)
-
-    legacy_single = (
-        list(next(iter(upstreams.values()))) if len(upstreams) == 1 else None
-    )
+    legacy_single = (
+        list(next(iter(upstreams.values()))) if len(upstreams) == 1 else None
+    )
+    # The proxy collapses a single upstream into the "default" slot, so the
+    # attestation emitter must key its fingerprint table the same way or
+    # fingerprint_for("default") misses the precomputed cmd-hash.
+    attest_upstreams = (
+        {"default": legacy_single} if legacy_single is not None else upstreams
+    )
+    attest_emitter = _build_attest_emitter_from_args(args, upstreams=attest_upstreams)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
attest_emitter = _build_attest_emitter_from_args(args, upstreams=upstreams)
legacy_single = (
list(next(iter(upstreams.values()))) if len(upstreams) == 1 else None
)
legacy_single = (
list(next(iter(upstreams.values()))) if len(upstreams) == 1 else None
)
# The proxy collapses a single upstream into the "default" slot, so the
# attestation emitter must key its fingerprint table the same way or
# fingerprint_for("default") misses the precomputed cmd-hash.
attest_upstreams = (
{"default": legacy_single} if legacy_single is not None else upstreams
)
attest_emitter = _build_attest_emitter_from_args(args, upstreams=attest_upstreams)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vaara/integrations/mcp_proxy.py` around lines 1396 - 1400, The emitter is
being built with the raw upstreams dict keyed by the operator name (e.g.
"github") which mismatches the proxy's collapsing to "default" for a single
upstream, causing fingerprint_for(_REQUEST_UPSTREAM) to miss entries; to fix,
normalize the upstreams before calling _build_attest_emitter_from_args so its
keys match VaaraMCPProxy's slot names (when legacy_single is non-None replace
the single upstream key with "default" or otherwise construct
normalized_upstreams = {'default': list(next(iter(upstreams.values())))} and
pass normalized_upstreams to _build_attest_emitter_from_args instead of the
original upstreams), ensuring attest_emitter, legacy_single, and fingerprint_for
use the same upstream name mapping.

@vaaraio vaaraio force-pushed the release/v0.43.0 branch from a2a2635 to c74628a Compare May 29, 2026 13:50
@vaaraio vaaraio merged commit dfba0f0 into main May 29, 2026
12 checks passed
@vaaraio vaaraio deleted the release/v0.43.0 branch May 29, 2026 14:01
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.

2 participants