From a2d48589c1ff0a80a26a25d403e84e122e393d12 Mon Sep 17 00:00:00 2001 From: Imran Siddique <45405841+imran-siddique@users.noreply.github.com> Date: Fri, 16 Oct 2026 10:57:34 -0700 Subject: [PATCH] docs(papers): add the published report sources Co-Authored-By: Claude Opus 5.5 --- papers/.gitattributes | 3 + papers/README.md | 17 + papers/ca2a/README.md | 21 + .../ca2a/experiments/c1_attenuation_scale.py | 156 ++++++ .../results/c1_attenuation_scale.json | 53 +++ .../c1_attenuation_scale.json | 53 +++ .../sealed_channel_throughput.json | 92 ++++ .../verify_latency_vs_depth.json | 61 +++ .../experiments/results/rerun-2026-09-27.txt | 76 +++ .../results/rerun-2026-10-01/README.md | 62 +++ .../c1_attenuation_scale.json | 53 +++ .../sealed_channel_throughput.run1.json | 92 ++++ .../sealed_channel_throughput.run2.json | 92 ++++ .../sealed_channel_throughput.run3.json | 92 ++++ .../verify_latency_vs_depth.run1.json | 61 +++ .../verify_latency_vs_depth.run2.json | 61 +++ .../verify_latency_vs_depth.run3.json | 61 +++ .../results/sealed_channel_throughput.json | 92 ++++ .../results/verify_latency_vs_depth.json | 61 +++ .../experiments/sealed_channel_throughput.py | 173 +++++++ .../experiments/verify_latency_vs_depth.py | 147 ++++++ .../figures/sealed_channel_throughput.pdf | Bin 0 -> 15647 bytes .../ca2a/figures/verify_latency_vs_depth.pdf | Bin 0 -> 14548 bytes papers/ca2a/paper.tex | 124 +++++ papers/ca2a/refs.bib | 342 ++++++++++++++ papers/ca2a/sections/01-intro.tex | 82 ++++ papers/ca2a/sections/02-background.tex | 137 ++++++ papers/ca2a/sections/03-design.tex | 352 ++++++++++++++ papers/ca2a/sections/04-claims.tex | 103 ++++ papers/ca2a/sections/05-evaluation.tex | 248 ++++++++++ papers/ca2a/sections/05b-after.tex | 64 +++ papers/ca2a/sections/06-related.tex | 66 +++ papers/ca2a/sections/07-conclusion.tex | 30 ++ papers/confidential-handoffs/LICENSE.md | 13 + papers/confidential-handoffs/README.md | 44 ++ papers/confidential-handoffs/build_paper.py | 123 +++++ .../experiments/compare_observations.py | 41 ++ .../reference-2026-09-19/manifest.json | 441 +++++++++++++++++ .../reference-2026-09-19/observations.jsonl | 36 ++ .../reference-2026-09-19/pytest.log | 2 + .../experiments/rerun-2026-09-27.txt | 124 +++++ .../hosted-fresh-bundle/bundle.json | 26 + .../hosted-fresh-bundle/manifest.json | 444 +++++++++++++++++ .../hosted-fresh-bundle/observations.jsonl | 36 ++ .../hosted-fresh-bundle/pytest.log | 2 + .../hosted-main/docker-version.txt | 27 ++ .../rerun-2026-09-27/hosted-main/kernel.txt | 1 + .../hosted-main/manifest.json | 444 +++++++++++++++++ .../hosted-main/observations.jsonl | 36 ++ .../rerun-2026-09-27/hosted-main/pytest.log | 2 + .../windows-fresh-bundle/bundle.json | 26 + .../windows-fresh-bundle/manifest.json | 445 ++++++++++++++++++ .../windows-fresh-bundle/observations.jsonl | 32 ++ .../windows-fresh-bundle/pytest.log | 2 + .../results/rerun-2026-10-01/README.txt | 47 ++ .../windows-fresh-bundle/bundle.json | 26 + .../windows-fresh-bundle/manifest.json | 445 ++++++++++++++++++ .../windows-fresh-bundle/observations.jsonl | 32 ++ .../windows-fresh-bundle/pytest.log | 2 + papers/confidential-handoffs/launcher/LICENSE | 202 ++++++++ papers/confidential-handoffs/launcher/NOTICE | 35 ++ .../launcher/composed-reproduction.zip | Bin 0 -> 9835 bytes papers/confidential-handoffs/manifest.json | 32 ++ papers/confidential-handoffs/paper.md | 266 +++++++++++ .../handoff-response-authentication/README.md | 28 ++ .../build_paper.py | 170 +++++++ .../experiments/race_and_caller_mac.py | 53 +++ .../experiments/reference-2026-09.json | 207 ++++++++ .../main-19407fb-2026-10-01/SHA256SUMS.json | 13 + .../main-19407fb-2026-10-01/commands.txt | 13 + .../control-resend-on-transport-failure.json | 63 +++ .../pytest-baseline.log | 14 + .../pytest-baseline.xml | 1 + .../pytest-consumption.log | 37 ++ .../pytest-consumption.xml | 13 + .../pytest-response-mac.log | 40 ++ .../pytest-response-mac.xml | 13 + .../race-and-caller-mac.json | 28 ++ .../results/main-19407fb-2026-10-01/run.json | 246 ++++++++++ .../transport-cut.json | 63 +++ .../results/rerun-2026-10-01/SHA256SUMS.json | 13 + .../results/rerun-2026-10-01/commands.txt | 13 + .../control-resend-on-transport-failure.json | 63 +++ .../rerun-2026-10-01/pytest-baseline.log | 14 + .../rerun-2026-10-01/pytest-baseline.xml | 1 + .../rerun-2026-10-01/pytest-consumption.log | 37 ++ .../rerun-2026-10-01/pytest-consumption.xml | 13 + .../rerun-2026-10-01/pytest-response-mac.log | 40 ++ .../rerun-2026-10-01/pytest-response-mac.xml | 13 + .../rerun-2026-10-01/race-and-caller-mac.json | 28 ++ .../results/rerun-2026-10-01/run.json | 246 ++++++++++ .../rerun-2026-10-01/transport-cut.json | 63 +++ .../experiments/run.py | 255 ++++++++++ .../experiments/transport_cut.py | 275 +++++++++++ .../manifest.json | 33 ++ .../handoff-response-authentication/paper.md | 138 ++++++ 96 files changed, 8808 insertions(+) create mode 100644 papers/.gitattributes create mode 100644 papers/README.md create mode 100644 papers/ca2a/README.md create mode 100644 papers/ca2a/experiments/c1_attenuation_scale.py create mode 100644 papers/ca2a/experiments/results/c1_attenuation_scale.json create mode 100644 papers/ca2a/experiments/results/original-2026-07-01/c1_attenuation_scale.json create mode 100644 papers/ca2a/experiments/results/original-2026-07-01/sealed_channel_throughput.json create mode 100644 papers/ca2a/experiments/results/original-2026-07-01/verify_latency_vs_depth.json create mode 100644 papers/ca2a/experiments/results/rerun-2026-09-27.txt create mode 100644 papers/ca2a/experiments/results/rerun-2026-10-01/README.md create mode 100644 papers/ca2a/experiments/results/rerun-2026-10-01/c1_attenuation_scale.json create mode 100644 papers/ca2a/experiments/results/rerun-2026-10-01/sealed_channel_throughput.run1.json create mode 100644 papers/ca2a/experiments/results/rerun-2026-10-01/sealed_channel_throughput.run2.json create mode 100644 papers/ca2a/experiments/results/rerun-2026-10-01/sealed_channel_throughput.run3.json create mode 100644 papers/ca2a/experiments/results/rerun-2026-10-01/verify_latency_vs_depth.run1.json create mode 100644 papers/ca2a/experiments/results/rerun-2026-10-01/verify_latency_vs_depth.run2.json create mode 100644 papers/ca2a/experiments/results/rerun-2026-10-01/verify_latency_vs_depth.run3.json create mode 100644 papers/ca2a/experiments/results/sealed_channel_throughput.json create mode 100644 papers/ca2a/experiments/results/verify_latency_vs_depth.json create mode 100644 papers/ca2a/experiments/sealed_channel_throughput.py create mode 100644 papers/ca2a/experiments/verify_latency_vs_depth.py create mode 100644 papers/ca2a/figures/sealed_channel_throughput.pdf create mode 100644 papers/ca2a/figures/verify_latency_vs_depth.pdf create mode 100644 papers/ca2a/paper.tex create mode 100644 papers/ca2a/refs.bib create mode 100644 papers/ca2a/sections/01-intro.tex create mode 100644 papers/ca2a/sections/02-background.tex create mode 100644 papers/ca2a/sections/03-design.tex create mode 100644 papers/ca2a/sections/04-claims.tex create mode 100644 papers/ca2a/sections/05-evaluation.tex create mode 100644 papers/ca2a/sections/05b-after.tex create mode 100644 papers/ca2a/sections/06-related.tex create mode 100644 papers/ca2a/sections/07-conclusion.tex create mode 100644 papers/confidential-handoffs/LICENSE.md create mode 100644 papers/confidential-handoffs/README.md create mode 100644 papers/confidential-handoffs/build_paper.py create mode 100644 papers/confidential-handoffs/experiments/compare_observations.py create mode 100644 papers/confidential-handoffs/experiments/reference-2026-09-19/manifest.json create mode 100644 papers/confidential-handoffs/experiments/reference-2026-09-19/observations.jsonl create mode 100644 papers/confidential-handoffs/experiments/reference-2026-09-19/pytest.log create mode 100644 papers/confidential-handoffs/experiments/rerun-2026-09-27.txt create mode 100644 papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/bundle.json create mode 100644 papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/manifest.json create mode 100644 papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/observations.jsonl create mode 100644 papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/pytest.log create mode 100644 papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/docker-version.txt create mode 100644 papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/kernel.txt create mode 100644 papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/manifest.json create mode 100644 papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/observations.jsonl create mode 100644 papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/pytest.log create mode 100644 papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/bundle.json create mode 100644 papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/manifest.json create mode 100644 papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/observations.jsonl create mode 100644 papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/pytest.log create mode 100644 papers/confidential-handoffs/experiments/results/rerun-2026-10-01/README.txt create mode 100644 papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/bundle.json create mode 100644 papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/manifest.json create mode 100644 papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/observations.jsonl create mode 100644 papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/pytest.log create mode 100644 papers/confidential-handoffs/launcher/LICENSE create mode 100644 papers/confidential-handoffs/launcher/NOTICE create mode 100644 papers/confidential-handoffs/launcher/composed-reproduction.zip create mode 100644 papers/confidential-handoffs/manifest.json create mode 100644 papers/confidential-handoffs/paper.md create mode 100644 papers/handoff-response-authentication/README.md create mode 100644 papers/handoff-response-authentication/build_paper.py create mode 100644 papers/handoff-response-authentication/experiments/race_and_caller_mac.py create mode 100644 papers/handoff-response-authentication/experiments/reference-2026-09.json create mode 100644 papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/SHA256SUMS.json create mode 100644 papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/commands.txt create mode 100644 papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/control-resend-on-transport-failure.json create mode 100644 papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-baseline.log create mode 100644 papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-baseline.xml create mode 100644 papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-consumption.log create mode 100644 papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-consumption.xml create mode 100644 papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-response-mac.log create mode 100644 papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-response-mac.xml create mode 100644 papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/race-and-caller-mac.json create mode 100644 papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/run.json create mode 100644 papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/transport-cut.json create mode 100644 papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/SHA256SUMS.json create mode 100644 papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/commands.txt create mode 100644 papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/control-resend-on-transport-failure.json create mode 100644 papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-baseline.log create mode 100644 papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-baseline.xml create mode 100644 papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-consumption.log create mode 100644 papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-consumption.xml create mode 100644 papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-response-mac.log create mode 100644 papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-response-mac.xml create mode 100644 papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/race-and-caller-mac.json create mode 100644 papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/run.json create mode 100644 papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/transport-cut.json create mode 100644 papers/handoff-response-authentication/experiments/run.py create mode 100644 papers/handoff-response-authentication/experiments/transport_cut.py create mode 100644 papers/handoff-response-authentication/manifest.json create mode 100644 papers/handoff-response-authentication/paper.md diff --git a/papers/.gitattributes b/papers/.gitattributes new file mode 100644 index 0000000..8237688 --- /dev/null +++ b/papers/.gitattributes @@ -0,0 +1,3 @@ +# Published source packages: keep their bytes exactly as released, so each +# package manifest and SHA256SUMS still verifies after checkout. +* -text diff --git a/papers/README.md b/papers/README.md new file mode 100644 index 0000000..a1f210e --- /dev/null +++ b/papers/README.md @@ -0,0 +1,17 @@ +# Papers + +LaTeX or Markdown source for the technical reports about this project. Each folder holds the +published `source.zip` of the version listed, unpacked and unchanged, so the build and the +reproduction steps in its own README work from here. The report page carries the built PDF, +citation files and every earlier version. + +Paper text is licensed CC BY 4.0. Code inside a paper folder states its own license in that +folder; the rest of this repository keeps its existing license. + +| Report | Version and date | Page | DOI | +|---|---|---|---| +| [cA2A: Confidential Agent-to-Agent Delegation as a Profile on A2A](ca2a/) | 3, 2026-10-01 | [page](https://agentrust-io.com/research/ca2a/v3/) | [10.5281/zenodo.23091279](https://doi.org/10.5281/zenodo.23091279) | +| [Confidentiality Across Agent Handoffs: Conditions for preserving an inference guarantee through tools and delegation](confidential-handoffs/) | 3, 2026-10-01 | [page](https://agentrust-io.com/research/confidential-handoffs/v3/) | [10.5281/zenodo.23074704](https://doi.org/10.5281/zenodo.23074704) | +| [Authenticating the Answer in an Agent Handoff: Single-use, request-bound response authentication and the outcome a caller cannot know](handoff-response-authentication/) | 1, 2026-10-01 | [page](https://agentrust-io.com/research/handoff-response-authentication/v1/) | [10.5281/zenodo.23091302](https://doi.org/10.5281/zenodo.23091302) | + +These sources previously lived in a private papers repository, which has been retired. diff --git a/papers/ca2a/README.md b/papers/ca2a/README.md new file mode 100644 index 0000000..ea0ac89 --- /dev/null +++ b/papers/ca2a/README.md @@ -0,0 +1,21 @@ +# cA2A: Confidential Agent-to-Agent Delegation as a Profile on A2A + +Technical report, version 3, October 1, 2026. Not peer reviewed. Patent Pending. +Version 3 adds the caller-attestation relay that ca2a 0.4.0 fixed (GHSA-xxj2-p57x-x954), which +affects the evaluated release, corrects four statements and restores bold and italic type. +Results are unchanged. + +Build: `tectonic paper.tex` (prepared with Tectonic 0.16.9; set SOURCE_DATE_EPOCH=1790812800 +to reproduce the published PDF byte for byte). + +Paper text is licensed CC BY 4.0: https://creativecommons.org/licenses/by/4.0/ +Project code retains its existing project license; see https://github.com/agentrust-io/ca2a. + +This package contains the manuscript source, figures, the three scale and cost experiment +scripts and their results. The evaluated code is ca2a 0.3.1, tag v0.3.1, commit +7d90823142dda1be589def3b22a3099581c7df7a. `experiments/results/rerun-2026-09-27.txt` records the +September 27 rerun and `experiments/results/rerun-2026-10-01/` the October 1 rerun at the same +commit: correctness results reproduced; timings were taken on a contended host. +`experiments/results/original-2026-07-01/` keeps the July 2026 results unchanged. Run the scripts +with CA2A_ROOT set to a ca2a checkout at that tag. No hardware run was repeated. +Current implementation guidance: https://ca2a.agentrust-io.com/ diff --git a/papers/ca2a/experiments/c1_attenuation_scale.py b/papers/ca2a/experiments/c1_attenuation_scale.py new file mode 100644 index 0000000..ec46a00 --- /dev/null +++ b/papers/ca2a/experiments/c1_attenuation_scale.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +"""C1 at scale: attenuation soundness over many narrowing chains and escalation +attempts, swept across delegation-chain depth. + +Extends experiments/claim1-attenuation-soundness/run.py from the shipped +ca2a_runtime package. For each depth d in a sweep, we generate TRIALS narrowing +chains (each hop a strict subset of its parent) and TRIALS escalation chains +(one interior hop adds a capability no ancestor held). We assert: + + * every narrowing chain is accepted by verify_chain; + * every escalation chain is rejected, and rejected specifically with + ScopeEscalation (not by some unrelated continuity error). + +Writes a JSON summary to results/c1_attenuation_scale.json. Real numbers only: +every count is what verify_chain returned on this run. + +Run from the paper directory, with CA2A_ROOT set to a ca2a checkout: + PYTHONUTF8=1 python experiments/c1_attenuation_scale.py +""" +# ruff: noqa: T201 +from __future__ import annotations + +import json +import os +import sys +from pathlib import Path + +# CA2A_ROOT points at a ca2a checkout. This edition's rerun used the v0.3.1 +# release, commit 7d90823142dda1be589def3b22a3099581c7df7a. +_CA2A_ROOT = Path(os.environ.get("CA2A_ROOT", "ca2a")) +_CODE_SRC = _CA2A_ROOT / "src" +if not (_CODE_SRC / "ca2a_runtime").is_dir(): + raise SystemExit("Set CA2A_ROOT to a ca2a checkout containing src/ca2a_runtime.") +sys.path.insert(0, str(_CODE_SRC)) + +from ca2a_runtime.delegation import ( # noqa: E402 + DelegationCredential, + new_keypair, + verify_chain, +) +from ca2a_runtime.errors import ScopeEscalation # noqa: E402 + +TRIALS = 500 +DEPTHS = [2, 3, 4, 6, 8] +# Root capability universe; wide enough to narrow across the deepest chain. +ROOT_CAPS = [f"cap:{i}" for i in range(16)] + + +def build_signed_chain(scopes: list[frozenset[str]]) -> list[DelegationCredential]: + chain: list[DelegationCredential] = [] + priv, pub = new_keypair() + parent_id: str | None = None + for depth, scope in enumerate(scopes): + next_priv, next_pub = new_keypair() + cred = DelegationCredential( + credential_id=f"cred-{depth}", + issuer=pub, + subject=next_pub, + scope=scope, + depth=depth, + parent_id=parent_id, + ).sign(priv) + chain.append(cred) + parent_id = cred.credential_id + priv, pub = next_priv, next_pub + return chain + + +def narrowing_scopes(depth: int) -> list[frozenset[str]]: + """A `depth`-hop strictly narrowing ladder: each hop drops one capability.""" + return [frozenset(ROOT_CAPS[: len(ROOT_CAPS) - i]) for i in range(depth)] + + +def escalating_scopes(depth: int, trial: int) -> list[frozenset[str]]: + """Narrowing ladder whose penultimate hop adds a never-held capability.""" + ladder = narrowing_scopes(depth) + inject = depth - 1 if depth > 1 else 1 + escalated = list(ladder) + escalated[inject] = frozenset(ladder[inject] | {f"cap:escalate-{trial}"}) + return escalated + + +def main() -> int: + print("=" * 64) + print("C1 at scale: attenuation soundness vs delegation-chain depth") + print("=" * 64) + + rows = [] + all_ok = True + for depth in DEPTHS: + accepted = 0 + rejected_escalation = 0 + rejected_other = 0 + for trial in range(TRIALS): + good = build_signed_chain(narrowing_scopes(depth)) + try: + verify_chain(good, max_depth=64, trusted_root_issuers={good[0].issuer}) + accepted += 1 + except Exception as exc: # noqa: BLE001 + print(f" depth={depth} trial={trial} UNEXPECTED reject: {type(exc).__name__}") + for trial in range(TRIALS): + bad = build_signed_chain(escalating_scopes(depth, trial)) + try: + verify_chain(bad, max_depth=64, trusted_root_issuers={bad[0].issuer}) + print(f" depth={depth} trial={trial} ESCALATION NOT CAUGHT") + except ScopeEscalation: + rejected_escalation += 1 + except Exception: # noqa: BLE001 + rejected_other += 1 + ok = accepted == TRIALS and rejected_escalation == TRIALS and rejected_other == 0 + all_ok = all_ok and ok + rows.append( + { + "depth": depth, + "trials": TRIALS, + "narrowing_accepted": accepted, + "escalation_rejected": rejected_escalation, + "rejected_other_error": rejected_other, + } + ) + print( + f" depth={depth:>2}: {accepted}/{TRIALS} narrowing accepted; " + f"{rejected_escalation}/{TRIALS} escalations -> ScopeEscalation; " + f"{rejected_other} wrong-error" + ) + + total_narrow = sum(r["narrowing_accepted"] for r in rows) + total_escal = sum(r["escalation_rejected"] for r in rows) + n = len(DEPTHS) * TRIALS + summary = { + "trials_per_depth": TRIALS, + "depths": DEPTHS, + "root_universe": len(ROOT_CAPS), + "rows": rows, + "total_narrowing_accepted": total_narrow, + "total_narrowing": n, + "total_escalation_rejected": total_escal, + "total_escalation": n, + "all_ok": all_ok, + } + out = Path(__file__).parent / "results" / "c1_attenuation_scale.json" + out.parent.mkdir(parents=True, exist_ok=True) + out.write_text(json.dumps(summary, indent=2)) + + print("\n" + "=" * 64) + print( + f"KEY RESULT: {total_narrow}/{n} narrowing chains accepted; " + f"{total_escal}/{n} escalation attempts rejected with ScopeEscalation " + f"across depths {DEPTHS}" + ) + print(f"wrote {out}") + return 0 if all_ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/papers/ca2a/experiments/results/c1_attenuation_scale.json b/papers/ca2a/experiments/results/c1_attenuation_scale.json new file mode 100644 index 0000000..21c8f32 --- /dev/null +++ b/papers/ca2a/experiments/results/c1_attenuation_scale.json @@ -0,0 +1,53 @@ +{ + "trials_per_depth": 500, + "depths": [ + 2, + 3, + 4, + 6, + 8 + ], + "root_universe": 16, + "rows": [ + { + "depth": 2, + "trials": 500, + "narrowing_accepted": 500, + "escalation_rejected": 500, + "rejected_other_error": 0 + }, + { + "depth": 3, + "trials": 500, + "narrowing_accepted": 500, + "escalation_rejected": 500, + "rejected_other_error": 0 + }, + { + "depth": 4, + "trials": 500, + "narrowing_accepted": 500, + "escalation_rejected": 500, + "rejected_other_error": 0 + }, + { + "depth": 6, + "trials": 500, + "narrowing_accepted": 500, + "escalation_rejected": 500, + "rejected_other_error": 0 + }, + { + "depth": 8, + "trials": 500, + "narrowing_accepted": 500, + "escalation_rejected": 500, + "rejected_other_error": 0 + } + ], + "total_narrowing_accepted": 2500, + "total_narrowing": 2500, + "total_escalation_rejected": 2500, + "total_escalation": 2500, + "all_ok": true +} \ No newline at end of file diff --git a/papers/ca2a/experiments/results/original-2026-07-01/c1_attenuation_scale.json b/papers/ca2a/experiments/results/original-2026-07-01/c1_attenuation_scale.json new file mode 100644 index 0000000..21c8f32 --- /dev/null +++ b/papers/ca2a/experiments/results/original-2026-07-01/c1_attenuation_scale.json @@ -0,0 +1,53 @@ +{ + "trials_per_depth": 500, + "depths": [ + 2, + 3, + 4, + 6, + 8 + ], + "root_universe": 16, + "rows": [ + { + "depth": 2, + "trials": 500, + "narrowing_accepted": 500, + "escalation_rejected": 500, + "rejected_other_error": 0 + }, + { + "depth": 3, + "trials": 500, + "narrowing_accepted": 500, + "escalation_rejected": 500, + "rejected_other_error": 0 + }, + { + "depth": 4, + "trials": 500, + "narrowing_accepted": 500, + "escalation_rejected": 500, + "rejected_other_error": 0 + }, + { + "depth": 6, + "trials": 500, + "narrowing_accepted": 500, + "escalation_rejected": 500, + "rejected_other_error": 0 + }, + { + "depth": 8, + "trials": 500, + "narrowing_accepted": 500, + "escalation_rejected": 500, + "rejected_other_error": 0 + } + ], + "total_narrowing_accepted": 2500, + "total_narrowing": 2500, + "total_escalation_rejected": 2500, + "total_escalation": 2500, + "all_ok": true +} \ No newline at end of file diff --git a/papers/ca2a/experiments/results/original-2026-07-01/sealed_channel_throughput.json b/papers/ca2a/experiments/results/original-2026-07-01/sealed_channel_throughput.json new file mode 100644 index 0000000..2497cb5 --- /dev/null +++ b/papers/ca2a/experiments/results/original-2026-07-01/sealed_channel_throughput.json @@ -0,0 +1,92 @@ +{ + "repeats": 2000, + "sizes": [ + 64, + 256, + 1024, + 4096, + 16384, + 65536, + 262144 + ], + "all_roundtrips_ok": true, + "rows": [ + { + "size_bytes": 64, + "repeats": 2000, + "seal_mean_ms": 0.12678360025165603, + "seal_p99_ms": 0.7144000264815986, + "open_mean_ms": 0.08346869971137494, + "open_p99_ms": 0.3543999628163874, + "roundtrip_mean_ms": 0.21025229996303096, + "throughput_mib_s": 0.2902948327353942, + "roundtrip_ok": true + }, + { + "size_bytes": 256, + "repeats": 2000, + "seal_mean_ms": 0.12101729930145666, + "seal_p99_ms": 0.5374000174924731, + "open_mean_ms": 0.081439300411148, + "open_p99_ms": 0.35479996586218476, + "roundtrip_mean_ms": 0.20245659971260466, + "throughput_mib_s": 1.2058911655464306, + "roundtrip_ok": true + }, + { + "size_bytes": 1024, + "repeats": 2000, + "seal_mean_ms": 0.12355444935383275, + "seal_p99_ms": 0.5643999902531505, + "open_mean_ms": 0.08077815035358071, + "open_p99_ms": 0.33359997905790806, + "roundtrip_mean_ms": 0.20433259970741346, + "throughput_mib_s": 4.779278986311302, + "roundtrip_ok": true + }, + { + "size_bytes": 4096, + "repeats": 2000, + "seal_mean_ms": 0.1298232511326205, + "seal_p99_ms": 0.6504000048153102, + "open_mean_ms": 0.08663019890082069, + "open_p99_ms": 0.32839999767020345, + "roundtrip_mean_ms": 0.21645345003344119, + "throughput_mib_s": 18.046605398973778, + "roundtrip_ok": true + }, + { + "size_bytes": 16384, + "repeats": 2000, + "seal_mean_ms": 0.14076784942881204, + "seal_p99_ms": 0.61039999127388, + "open_mean_ms": 0.10039080059505068, + "open_p99_ms": 0.38430001586675644, + "roundtrip_mean_ms": 0.24115865002386272, + "throughput_mib_s": 64.7913728097827, + "roundtrip_ok": true + }, + { + "size_bytes": 65536, + "repeats": 2000, + "seal_mean_ms": 0.21593040053267032, + "seal_p99_ms": 0.7789000519551337, + "open_mean_ms": 0.18400519955321215, + "open_p99_ms": 0.9960000170394778, + "roundtrip_mean_ms": 0.39993560008588247, + "throughput_mib_s": 156.2751602672498, + "roundtrip_ok": true + }, + { + "size_bytes": 262144, + "repeats": 2000, + "seal_mean_ms": 0.49358985002618283, + "seal_p99_ms": 1.6650999896228313, + "open_mean_ms": 0.4467086501244921, + "open_p99_ms": 1.5171000268310308, + "roundtrip_mean_ms": 0.940298500150675, + "throughput_mib_s": 265.87301794051524, + "roundtrip_ok": true + } + ] +} \ No newline at end of file diff --git a/papers/ca2a/experiments/results/original-2026-07-01/verify_latency_vs_depth.json b/papers/ca2a/experiments/results/original-2026-07-01/verify_latency_vs_depth.json new file mode 100644 index 0000000..ccfdb87 --- /dev/null +++ b/papers/ca2a/experiments/results/original-2026-07-01/verify_latency_vs_depth.json @@ -0,0 +1,61 @@ +{ + "repeats": 2000, + "rows": [ + { + "depth": 1, + "repeats": 2000, + "mean_ms": 0.14344099978916347, + "p50_ms": 0.1317000132985413, + "p99_ms": 0.3510999958962202, + "per_hop_us": 143.44099978916347 + }, + { + "depth": 2, + "repeats": 2000, + "mean_ms": 0.288724400132196, + "p50_ms": 0.2668000524863601, + "p99_ms": 0.6686999695375562, + "per_hop_us": 144.362200066098 + }, + { + "depth": 4, + "repeats": 2000, + "mean_ms": 0.6391397499828599, + "p50_ms": 0.5367000121623278, + "p99_ms": 1.492600014898926, + "per_hop_us": 159.78493749571498 + }, + { + "depth": 8, + "repeats": 2000, + "mean_ms": 1.7328267006960232, + "p50_ms": 1.745399960782379, + "p99_ms": 3.3074000384658575, + "per_hop_us": 216.6033375870029 + }, + { + "depth": 16, + "repeats": 2000, + "mean_ms": 4.120213499554666, + "p50_ms": 4.146500024944544, + "p99_ms": 8.252000028733164, + "per_hop_us": 257.5133437221666 + }, + { + "depth": 32, + "repeats": 2000, + "mean_ms": 7.827341200754745, + "p50_ms": 8.166000014171004, + "p99_ms": 11.59060001373291, + "per_hop_us": 244.60441252358578 + }, + { + "depth": 64, + "repeats": 2000, + "mean_ms": 12.449884450237732, + "p50_ms": 10.950100026093423, + "p99_ms": 22.152499994263053, + "per_hop_us": 194.52944453496457 + } + ] +} \ No newline at end of file diff --git a/papers/ca2a/experiments/results/rerun-2026-09-27.txt b/papers/ca2a/experiments/results/rerun-2026-09-27.txt new file mode 100644 index 0000000..fa9d5e6 --- /dev/null +++ b/papers/ca2a/experiments/results/rerun-2026-09-27.txt @@ -0,0 +1,76 @@ +cA2A software rerun, 2026-09-27 +Repository: https://github.com/agentrust-io/ca2a +Commit: 7d90823142dda1be589def3b22a3099581c7df7a (tag v0.3.1, ca2a-runtime 0.3.1, main as of 2026-09-27) +Source obtained with git archive of that tag, so tests that read committed blobs through git were skipped (see pytest). +Python: 3.12.10, x86-64 build (MSC v.1943 AMD64), running under Windows 11 on an ARM64 Snapdragon X1E80100 laptop (x86-64 emulation). +Dependencies: the project's requirements/runtime.txt lock at the commit above, installed without hashes, except cbor2 5.9.0 instead of the + locked 6.1.4 (the local application-control policy blocks the cbor2 6.x native extension; 5.x is pure Python). Resolved versions: + agent-manifest==0.12.0 agentrust-trace==0.10.0 cedarpy==4.8.7 cryptography==50.0.1 pydantic==2.13.5 rfc8785==0.1.4 cbor2==5.9.0 + test-only: pytest==9.1.1 pytest-asyncio==1.4.0 a2a-sdk==1.1.4 uvicorn==0.53.0 aiohttp==3.14.3 agentrust-trace-tests==0.5.1 matplotlib==3.11.2 +The July 1, 2026 results this report originally cited were produced against an unpinned ca2a checkout of that date and are kept +unchanged under results/original-2026-07-01/. + +## Paper experiments (this directory), CA2A_ROOT at the commit above +Change needed to run at 0.3.1: verify_chain now fails closed without an explicit trusted root (ca2a #132, commit 23c980a), +so each script passes the chain's own root issuer as trusted_root_issuers. No other logic changed. + +### experiments/c1_attenuation_scale.py (exit 0) +C1 at scale: attenuation soundness vs delegation-chain depth + depth= 2: 500/500 narrowing accepted; 500/500 escalations -> ScopeEscalation; 0 wrong-error + depth= 3: 500/500 narrowing accepted; 500/500 escalations -> ScopeEscalation; 0 wrong-error + depth= 4: 500/500 narrowing accepted; 500/500 escalations -> ScopeEscalation; 0 wrong-error + depth= 6: 500/500 narrowing accepted; 500/500 escalations -> ScopeEscalation; 0 wrong-error + depth= 8: 500/500 narrowing accepted; 500/500 escalations -> ScopeEscalation; 0 wrong-error +KEY RESULT: 2500/2500 narrowing chains accepted; 2500/2500 escalation attempts rejected with ScopeEscalation across depths [2, 3, 4, 6, 8] + +### experiments/verify_latency_vs_depth.py (exit 0) +Verification latency vs delegation-chain depth + depth= 1: mean=0.2456 ms p50=0.2705 ms p99=0.4357 ms per-hop=245.59 us + depth= 2: mean=0.5322 ms p50=0.5433 ms p99=1.0974 ms per-hop=266.11 us + depth= 4: mean=0.9483 ms p50=0.9627 ms p99=2.2214 ms per-hop=237.07 us + depth= 8: mean=2.0740 ms p50=2.1049 ms p99=4.3023 ms per-hop=259.25 us + depth= 16: mean=3.8187 ms p50=3.9466 ms p99=6.6624 ms per-hop=238.67 us + depth= 32: mean=7.6170 ms p50=7.7715 ms p99=12.9805 ms per-hop=238.03 us + depth= 64: mean=13.8701 ms p50=13.7088 ms p99=26.0845 ms per-hop=216.72 us +KEY RESULT: verify latency is linear in depth at ~216.3 us/hop; a 64-hop chain verifies in 13.870 ms mean (p99 26.085 ms) + +### experiments/sealed_channel_throughput.py (exit 0) +Sealed peer channel: throughput and latency vs payload size + size= 64B: seal=0.1006 ms open=0.0661 ms rt=0.1667 ms thr= 0.37 MiB/s ok=True + size= 256B: seal=0.0984 ms open=0.0646 ms rt=0.1630 ms thr= 1.50 MiB/s ok=True + size= 1024B: seal=0.1060 ms open=0.0673 ms rt=0.1732 ms thr= 5.64 MiB/s ok=True + size= 4096B: seal=0.1030 ms open=0.0691 ms rt=0.1721 ms thr= 22.70 MiB/s ok=True + size= 16384B: seal=0.1210 ms open=0.0864 ms rt=0.2074 ms thr= 75.34 MiB/s ok=True + size= 65536B: seal=0.2449 ms open=0.2035 ms rt=0.4484 ms thr= 139.39 MiB/s ok=True + size= 262144B: seal=0.6199 ms open=0.5604 ms rt=1.1803 ms thr= 211.81 MiB/s ok=True +KEY RESULT: a 64-byte task envelope seals+opens in 0.1667 ms (setup-dominated); at 262144 bytes the AEAD reaches 211.8 MiB/s; all round-trips verified + +## Repository claim experiments at the commit above (experiments/claim*/run.py) +claim1 (exit 0): KEY RESULT: 200/200 narrowing chains accepted; 200/200 escalation attempts rejected (ScopeEscalation) +claim2 (exit 0): KEY RESULT: 2/2 replay attacks rejected (1 CredentialReplay, 1 BrokenDelegationLink); 2/2 control chains valid +claim3 (exit 0): KEY RESULT: effective scope ['read']; 1/1 allowed, 3/3 denied; capability granted only when delegated AND locally permitted +claim4 (exit 0): KEY RESULT: 4/4 encryption checks passed; matching key opens; wrong key and tamper rejected (software keys; no hardware assurance) +claim5 (exit 0): KEY RESULT: tamper flips ~50% of hash bits (138/256), ProvenanceLinkBroken raised; reparent detected; provenance bound to authority +claim6 (exit 0): KEY RESULT: 4/4 two operators, independent keys, mutual attestation, sealed cross-operator delegation, binary-swap detected (synthetic vectors; real hardware end-to-end pending) +Claim 5 bit counts vary with random key material: 119/256 and 138/256 in two runs on this date. +Claim 6 uses synthetic SEV-SNP report and certificate vectors; both synthetic VCEKs chain to one synthetic root standing in for the AMD ARK. + +## pytest -q at the commit above +SKIPPED [5] tests/conformance/test_action_fixture_bundles.py:84: git or the committed source archive is unavailable; cannot read HEAD blobs +SKIPPED [2] tests/unit/test_committed_examples_verify.py:35: git is unavailable or the example is not committed yet +SKIPPED [2] tests/unit/test_committed_examples_verify.py:69: git is unavailable or the example is not committed yet +SKIPPED [1] tests/unit/test_sev_snp.py:331: set CA2A_SNP_FIXTURE_DIR to a dir with snp_report.bin + vcek.der + cert_chain.pem (a real SEV-SNP capture) to run the hardware test +SKIPPED [1] tests/unit/test_tdx.py:186: set CA2A_TDX_QUOTE to a real DCAP v4 quote file to run the hardware test +779 passed, 11 skipped, 100 warnings in 50.54s +The 9 git-dependent skips follow from the archive checkout; the 2 hardware skips need a capture path: the TDX one is rerun below against a public quote; no SEV-SNP capture is published, because a report carries a per-CPU CHIP_ID. + +## Public hardware evidence re-appraised at the commit above +tests/unit/test_tdx.py with CA2A_TDX_QUOTE set to the genuine GCP C3 Intel TDX quote published in agentrust-io/agent-manifest +at commit e1cd860caca4f7a036bd839abd008207731e6518, path python/tests/fixtures/hardware/gcp-tdx-2026-07-21/tdx_quote.bin +(SHA-256 f9efbac112efe510aa8ccd20703b063591b8c2c54c474d0ff1d6500299bae0ba): 17 passed, including +test_real_tdx_quote_verifies_to_the_intel_root (PCK chain to the pinned Intel SGX Root CA, quote signature, version 4, tee_type 0x81, +non-zero MRTD). This checks appraisal of one captured quote offline; it is not a live attested call and checks no TCB currency. + +Not rerun: no hardware run was repeated. The hardware observations cited in the report (2026-07-27 one-directional +SEV-SNP to TDX run; 2026-09-17 same-operator mutual SEV-SNP diagnostic) are the project's recorded transcripts, not new measurements. +Timings are single runs on a laptop under emulation and vary from run to run; compare shapes, not absolute values. diff --git a/papers/ca2a/experiments/results/rerun-2026-10-01/README.md b/papers/ca2a/experiments/results/rerun-2026-10-01/README.md new file mode 100644 index 0000000..55fa10e --- /dev/null +++ b/papers/ca2a/experiments/results/rerun-2026-10-01/README.md @@ -0,0 +1,62 @@ +# cA2A rerun, 2026-10-01 + +Audit rerun of every software experiment the report cites, against the same pin as version 2. + +- ca2a: tag v0.3.1, commit 7d90823142dda1be589def3b22a3099581c7df7a, from a git clone (not a source archive). +- Python 3.12.10, x86-64 build (MSC v.1943 AMD64) under emulation on Windows 11, Snapdragon X1E80100, + 64 GB. Same machine type as the 2026-09-27 rerun. +- Fresh venv. Runtime lock `requirements/runtime.txt` at the pin, installed without hashes. No deviation + this time: cbor2 6.1.4 loaded. Resolved: agent-manifest 0.12.0, agentrust-trace 0.10.0, cedarpy 4.8.7, + cryptography 50.0.1, pydantic 2.13.5, rfc8785 0.1.4, cbor2 6.1.4; test-only a2a-sdk 1.1.4, pytest 9.1.1, + agentrust-trace-tests 0.5.1, matplotlib 3.11.2. +- Paper scripts run from a copy of this directory with `CA2A_ROOT` set to the pin and `PYTHONUTF8=1`. + Repository claim scripts run from the pin as `python experiments/claimN-*/run.py`. +- No hardware run was repeated. The 2026-07-27 SEV-SNP to TDX run and the 2026-09-17 mutual SEV-SNP + diagnostic used deleted cloud VMs and unpublished raw evidence; they cannot be repeated here. + +Files: `rerun.log` (all output, local paths replaced), `c1_attenuation_scale.json`, and three runs each +of `verify_latency_vs_depth.runN.json` and `sealed_channel_throughput.runN.json`. + +## Correctness results: all reproduced + +| Item | Paper (v2) | This rerun | +|---|---|---| +| C1 repo experiment | 200/200 accepted, 200/200 ScopeEscalation | same | +| C1 at scale, depths 2,3,4,6,8 | 2500/2500 and 2500/2500, 0 wrong-error | same | +| C2 | CredentialReplay, BrokenDelegationLink, 2/2 controls | same | +| C3 | effective scope {read}, 1/1 allowed, 3/3 denied | same | +| C4 | 4/4 | 4/4 | +| C5 hash bits changed | 119 and 138 of 256 | 128 of 256 (random key material; expected near 128) | +| C6 synthetic | 4/4 | 4/4 | +| pytest | 779 passed, 11 skipped (archive) | 788 passed, 2 skipped (git clone runs the 9 git-dependent tests) | +| TDX tests with public GCP quote (SHA-256 f9efbac1...) | 17 passed | 17 passed | + +The six claim scripts were also run against ca2a main 19407fb (0.4.0): all six reproduce. The delegation, +channel and provenance modules are unchanged between the pin and main. + +## Timing results: shape reproduced, absolute values not + +All three timing runs overlapped with an unrelated CPU-heavy job on the same host, so they are contended. +The paper's own caveat applies: single runs under emulation, compare shape. + +| Measurement | July 2026 | Paper (2026-09-27) | Run 1 | Run 2 | Run 3 | +|---|---|---|---|---|---| +| verify_chain depth 1, mean (ms) | 0.14 | 0.25 | 0.45 | 0.28 | 0.32 | +| verify_chain depth 1, p50 (ms) | | 0.27 | 0.28 | 0.18 | 0.21 | +| verify_chain depth 8, mean (ms) | 1.73 | 2.07 | 3.98 | 2.27 | 3.31 | +| verify_chain depth 64, mean / p99 (ms) | 12.45 / 22.15 | 13.87 / 26.08 | 29.21 / 392.3 | 22.85 / 49.1 | 26.08 / 64.8 | +| verify_chain depth 64, p50 (ms) | | 13.71 | 18.63 | 20.75 | 23.49 | +| Slope depth 1 to 64, mean (us per hop) | 195 | 216 | 457 | 358 | 409 | +| Slope depth 1 to 64, p50 (us per hop) | | 213 | 291 | 327 | 370 | +| Seal and open, 64 B, round trip (ms) | 0.21 | 0.17 | 0.43 | 0.44 | 0.33 | +| Seal and open, 256 KiB, throughput (MiB/s) | 266 | 212 | 161 | 110 | 157 | + +Holds: verification cost grows linearly with depth; small sealed payloads are dominated by fixed X25519 +and HKDF setup and large ones by the AEAD; every round trip verified. Does not hold as a number: the +abstract's "about 0.22 ms per hop" and the 212 MiB/s figure were not reproduced on a contended host. The +depth 1 p50 matched the paper (0.27 vs 0.28 ms in run 1), which points at contention in the tail and +mean rather than a code regression. A quiet-host rerun with repeated trials and confidence intervals is +needed before any of these numbers is quoted. + +Note: `verify_chain` defaults to `max_depth=8`; the scale and latency scripts raise it to 64 and 128, so +the 16 to 64 hop points are outside the default configuration. diff --git a/papers/ca2a/experiments/results/rerun-2026-10-01/c1_attenuation_scale.json b/papers/ca2a/experiments/results/rerun-2026-10-01/c1_attenuation_scale.json new file mode 100644 index 0000000..21c8f32 --- /dev/null +++ b/papers/ca2a/experiments/results/rerun-2026-10-01/c1_attenuation_scale.json @@ -0,0 +1,53 @@ +{ + "trials_per_depth": 500, + "depths": [ + 2, + 3, + 4, + 6, + 8 + ], + "root_universe": 16, + "rows": [ + { + "depth": 2, + "trials": 500, + "narrowing_accepted": 500, + "escalation_rejected": 500, + "rejected_other_error": 0 + }, + { + "depth": 3, + "trials": 500, + "narrowing_accepted": 500, + "escalation_rejected": 500, + "rejected_other_error": 0 + }, + { + "depth": 4, + "trials": 500, + "narrowing_accepted": 500, + "escalation_rejected": 500, + "rejected_other_error": 0 + }, + { + "depth": 6, + "trials": 500, + "narrowing_accepted": 500, + "escalation_rejected": 500, + "rejected_other_error": 0 + }, + { + "depth": 8, + "trials": 500, + "narrowing_accepted": 500, + "escalation_rejected": 500, + "rejected_other_error": 0 + } + ], + "total_narrowing_accepted": 2500, + "total_narrowing": 2500, + "total_escalation_rejected": 2500, + "total_escalation": 2500, + "all_ok": true +} \ No newline at end of file diff --git a/papers/ca2a/experiments/results/rerun-2026-10-01/sealed_channel_throughput.run1.json b/papers/ca2a/experiments/results/rerun-2026-10-01/sealed_channel_throughput.run1.json new file mode 100644 index 0000000..270d109 --- /dev/null +++ b/papers/ca2a/experiments/results/rerun-2026-10-01/sealed_channel_throughput.run1.json @@ -0,0 +1,92 @@ +{ + "repeats": 2000, + "sizes": [ + 64, + 256, + 1024, + 4096, + 16384, + 65536, + 262144 + ], + "all_roundtrips_ok": true, + "rows": [ + { + "size_bytes": 64, + "repeats": 2000, + "seal_mean_ms": 0.2480455491458997, + "seal_p99_ms": 1.662899972870946, + "open_mean_ms": 0.178902201121673, + "open_p99_ms": 1.2950999662280083, + "roundtrip_mean_ms": 0.4269477502675727, + "throughput_mib_s": 0.14295696888377704, + "roundtrip_ok": true + }, + { + "size_bytes": 256, + "repeats": 2000, + "seal_mean_ms": 0.23997164657339454, + "seal_p99_ms": 1.3892999850213528, + "open_mean_ms": 0.16089669975917786, + "open_p99_ms": 1.0665999725461006, + "roundtrip_mean_ms": 0.4008683463325724, + "throughput_mib_s": 0.6090294412955559, + "roundtrip_ok": true + }, + { + "size_bytes": 1024, + "repeats": 2000, + "seal_mean_ms": 0.24405259755440056, + "seal_p99_ms": 1.4466000720858574, + "open_mean_ms": 0.163385552004911, + "open_p99_ms": 1.1024000123143196, + "roundtrip_mean_ms": 0.40743814955931157, + "throughput_mib_s": 2.396836184967603, + "roundtrip_ok": true + }, + { + "size_bytes": 4096, + "repeats": 2000, + "seal_mean_ms": 0.26745400251820683, + "seal_p99_ms": 1.6079999040812254, + "open_mean_ms": 0.17694469878915697, + "open_p99_ms": 1.3313000090420246, + "roundtrip_mean_ms": 0.4443987013073638, + "throughput_mib_s": 8.789967181515866, + "roundtrip_ok": true + }, + { + "size_bytes": 16384, + "repeats": 2000, + "seal_mean_ms": 0.24274314555805176, + "seal_p99_ms": 1.299300231039524, + "open_mean_ms": 0.18382335104979575, + "open_p99_ms": 1.2344999704509974, + "roundtrip_mean_ms": 0.4265664966078475, + "throughput_mib_s": 36.62969343409176, + "roundtrip_ok": true + }, + { + "size_bytes": 65536, + "repeats": 2000, + "seal_mean_ms": 0.37697749957442284, + "seal_p99_ms": 2.1524999756366014, + "open_mean_ms": 0.2795622998382896, + "open_p99_ms": 1.4589999336749315, + "roundtrip_mean_ms": 0.6565397994127125, + "throughput_mib_s": 95.19605674462912, + "roundtrip_ok": true + }, + { + "size_bytes": 262144, + "repeats": 2000, + "seal_mean_ms": 0.8236059030750766, + "seal_p99_ms": 4.384699976071715, + "open_mean_ms": 0.7287730464013293, + "open_p99_ms": 3.4165000542998314, + "roundtrip_mean_ms": 1.552378949476406, + "throughput_mib_s": 161.04315256550035, + "roundtrip_ok": true + } + ] +} \ No newline at end of file diff --git a/papers/ca2a/experiments/results/rerun-2026-10-01/sealed_channel_throughput.run2.json b/papers/ca2a/experiments/results/rerun-2026-10-01/sealed_channel_throughput.run2.json new file mode 100644 index 0000000..3d5554f --- /dev/null +++ b/papers/ca2a/experiments/results/rerun-2026-10-01/sealed_channel_throughput.run2.json @@ -0,0 +1,92 @@ +{ + "repeats": 2000, + "sizes": [ + 64, + 256, + 1024, + 4096, + 16384, + 65536, + 262144 + ], + "all_roundtrips_ok": true, + "rows": [ + { + "size_bytes": 64, + "repeats": 2000, + "seal_mean_ms": 0.2646917008096352, + "seal_p99_ms": 1.7608997877687216, + "open_mean_ms": 0.17205189855303615, + "open_p99_ms": 1.4170000795274973, + "roundtrip_mean_ms": 0.4367435993626714, + "throughput_mib_s": 0.13975054548954358, + "roundtrip_ok": true + }, + { + "size_bytes": 256, + "repeats": 2000, + "seal_mean_ms": 0.30305464670527726, + "seal_p99_ms": 2.8423999901860952, + "open_mean_ms": 0.20340245123952627, + "open_p99_ms": 2.0200000144541264, + "roundtrip_mean_ms": 0.5064570979448035, + "throughput_mib_s": 0.48205588586026255, + "roundtrip_ok": true + }, + { + "size_bytes": 1024, + "repeats": 2000, + "seal_mean_ms": 0.2814050489105284, + "seal_p99_ms": 2.3749000392854214, + "open_mean_ms": 0.19786739745177329, + "open_p99_ms": 2.0393000449985266, + "roundtrip_mean_ms": 0.4792724463623017, + "throughput_mib_s": 2.0375936639215357, + "roundtrip_ok": true + }, + { + "size_bytes": 4096, + "repeats": 2000, + "seal_mean_ms": 0.24173735035583377, + "seal_p99_ms": 1.613600179553032, + "open_mean_ms": 0.18069609720259905, + "open_p99_ms": 1.9667001906782389, + "roundtrip_mean_ms": 0.4224334475584328, + "throughput_mib_s": 9.247018725854256, + "roundtrip_ok": true + }, + { + "size_bytes": 16384, + "repeats": 2000, + "seal_mean_ms": 0.3290918483398855, + "seal_p99_ms": 2.848600037395954, + "open_mean_ms": 0.2503298989031464, + "open_p99_ms": 2.3667002096772194, + "roundtrip_mean_ms": 0.5794217472430319, + "throughput_mib_s": 26.966540476511096, + "roundtrip_ok": true + }, + { + "size_bytes": 65536, + "repeats": 2000, + "seal_mean_ms": 0.4457912972429767, + "seal_p99_ms": 2.3032999597489834, + "open_mean_ms": 0.3576522544026375, + "open_p99_ms": 2.5475001893937588, + "roundtrip_mean_ms": 0.8034435516456142, + "throughput_mib_s": 77.7901569711866, + "roundtrip_ok": true + }, + { + "size_bytes": 262144, + "repeats": 2000, + "seal_mean_ms": 1.2488190038129687, + "seal_p99_ms": 7.47309997677803, + "open_mean_ms": 1.0299007464200258, + "open_p99_ms": 5.625699879601598, + "roundtrip_mean_ms": 2.2787197502329946, + "throughput_mib_s": 109.71072681247354, + "roundtrip_ok": true + } + ] +} \ No newline at end of file diff --git a/papers/ca2a/experiments/results/rerun-2026-10-01/sealed_channel_throughput.run3.json b/papers/ca2a/experiments/results/rerun-2026-10-01/sealed_channel_throughput.run3.json new file mode 100644 index 0000000..7c87ccb --- /dev/null +++ b/papers/ca2a/experiments/results/rerun-2026-10-01/sealed_channel_throughput.run3.json @@ -0,0 +1,92 @@ +{ + "repeats": 2000, + "sizes": [ + 64, + 256, + 1024, + 4096, + 16384, + 65536, + 262144 + ], + "all_roundtrips_ok": true, + "rows": [ + { + "size_bytes": 64, + "repeats": 2000, + "seal_mean_ms": 0.19837245042435825, + "seal_p99_ms": 1.3856999576091766, + "open_mean_ms": 0.1313249971717596, + "open_p99_ms": 0.8549999911338091, + "roundtrip_mean_ms": 0.32969744759611785, + "throughput_mib_s": 0.18512474602099008, + "roundtrip_ok": true + }, + { + "size_bytes": 256, + "repeats": 2000, + "seal_mean_ms": 0.2160840508295223, + "seal_p99_ms": 1.3613998889923096, + "open_mean_ms": 0.14989964861888438, + "open_p99_ms": 1.128799980506301, + "roundtrip_mean_ms": 0.3659836994484067, + "throughput_mib_s": 0.6670805977642097, + "roundtrip_ok": true + }, + { + "size_bytes": 1024, + "repeats": 2000, + "seal_mean_ms": 0.21697639778722078, + "seal_p99_ms": 1.4708999078720808, + "open_mean_ms": 0.14808119763620198, + "open_p99_ms": 1.2137999292463064, + "roundtrip_mean_ms": 0.36505759542342275, + "throughput_mib_s": 2.67509158073346, + "roundtrip_ok": true + }, + { + "size_bytes": 4096, + "repeats": 2000, + "seal_mean_ms": 0.24012244830373675, + "seal_p99_ms": 1.790700014680624, + "open_mean_ms": 0.15696765133179724, + "open_p99_ms": 1.3879998587071896, + "roundtrip_mean_ms": 0.397090099635534, + "throughput_mib_s": 9.837188093043167, + "roundtrip_ok": true + }, + { + "size_bytes": 16384, + "repeats": 2000, + "seal_mean_ms": 0.2471354512963444, + "seal_p99_ms": 1.3705999590456486, + "open_mean_ms": 0.1771439970470965, + "open_p99_ms": 1.115499995648861, + "roundtrip_mean_ms": 0.4242794483434409, + "throughput_mib_s": 36.82714319773522, + "roundtrip_ok": true + }, + { + "size_bytes": 65536, + "repeats": 2000, + "seal_mean_ms": 0.35773854656144977, + "seal_p99_ms": 1.731199910864234, + "open_mean_ms": 0.2774520048405975, + "open_p99_ms": 1.6132998280227184, + "roundtrip_mean_ms": 0.6351905514020473, + "throughput_mib_s": 98.39567018439524, + "roundtrip_ok": true + }, + { + "size_bytes": 262144, + "repeats": 2000, + "seal_mean_ms": 0.8574513531057164, + "seal_p99_ms": 3.6641000770032406, + "open_mean_ms": 0.7334537490969524, + "open_p99_ms": 3.175599966198206, + "roundtrip_mean_ms": 1.5909051022026688, + "throughput_mib_s": 157.14325113035684, + "roundtrip_ok": true + } + ] +} \ No newline at end of file diff --git a/papers/ca2a/experiments/results/rerun-2026-10-01/verify_latency_vs_depth.run1.json b/papers/ca2a/experiments/results/rerun-2026-10-01/verify_latency_vs_depth.run1.json new file mode 100644 index 0000000..253f833 --- /dev/null +++ b/papers/ca2a/experiments/results/rerun-2026-10-01/verify_latency_vs_depth.run1.json @@ -0,0 +1,61 @@ +{ + "repeats": 2000, + "rows": [ + { + "depth": 1, + "repeats": 2000, + "mean_ms": 0.4458980035269633, + "p50_ms": 0.2842999529093504, + "p99_ms": 2.8275998774915934, + "per_hop_us": 445.8980035269633 + }, + { + "depth": 2, + "repeats": 2000, + "mean_ms": 0.8351094491081312, + "p50_ms": 0.5846000276505947, + "p99_ms": 4.961299942806363, + "per_hop_us": 417.5547245540656 + }, + { + "depth": 4, + "repeats": 2000, + "mean_ms": 1.3544009515317157, + "p50_ms": 1.0355000849813223, + "p99_ms": 4.494299879297614, + "per_hop_us": 338.6002378829289 + }, + { + "depth": 8, + "repeats": 2000, + "mean_ms": 3.9765912536531687, + "p50_ms": 2.2581000812351704, + "p99_ms": 43.18900010548532, + "per_hop_us": 497.0739067066461 + }, + { + "depth": 16, + "repeats": 2000, + "mean_ms": 4.853529348503798, + "p50_ms": 4.337799968197942, + "p99_ms": 11.696400120854378, + "per_hop_us": 303.3455842814874 + }, + { + "depth": 32, + "repeats": 2000, + "mean_ms": 12.261276451172307, + "p50_ms": 11.174499988555908, + "p99_ms": 29.772300040349364, + "per_hop_us": 383.1648890991346 + }, + { + "depth": 64, + "repeats": 2000, + "mean_ms": 29.20993050187826, + "p50_ms": 18.630099948495626, + "p99_ms": 392.317800084129, + "per_hop_us": 456.40516409184784 + } + ] +} \ No newline at end of file diff --git a/papers/ca2a/experiments/results/rerun-2026-10-01/verify_latency_vs_depth.run2.json b/papers/ca2a/experiments/results/rerun-2026-10-01/verify_latency_vs_depth.run2.json new file mode 100644 index 0000000..130c63e --- /dev/null +++ b/papers/ca2a/experiments/results/rerun-2026-10-01/verify_latency_vs_depth.run2.json @@ -0,0 +1,61 @@ +{ + "repeats": 2000, + "rows": [ + { + "depth": 1, + "repeats": 2000, + "mean_ms": 0.2749636978842318, + "p50_ms": 0.18150010146200657, + "p99_ms": 1.787200104445219, + "per_hop_us": 274.9636978842318 + }, + { + "depth": 2, + "repeats": 2000, + "mean_ms": 0.6422678008675575, + "p50_ms": 0.380899989977479, + "p99_ms": 4.2801001109182835, + "per_hop_us": 321.13390043377876 + }, + { + "depth": 4, + "repeats": 2000, + "mean_ms": 1.0205815976951271, + "p50_ms": 0.7603999692946672, + "p99_ms": 4.016299964860082, + "per_hop_us": 255.14539942378178 + }, + { + "depth": 8, + "repeats": 2000, + "mean_ms": 2.2657326000044122, + "p50_ms": 1.7943000420928001, + "p99_ms": 7.830500137060881, + "per_hop_us": 283.21657500055153 + }, + { + "depth": 16, + "repeats": 2000, + "mean_ms": 9.120262495358475, + "p50_ms": 4.314100136980414, + "p99_ms": 149.4685998186469, + "per_hop_us": 570.0164059599047 + }, + { + "depth": 32, + "repeats": 2000, + "mean_ms": 13.623301348066889, + "p50_ms": 8.341999957337976, + "p99_ms": 155.99010000005364, + "per_hop_us": 425.7281671270903 + }, + { + "depth": 64, + "repeats": 2000, + "mean_ms": 22.847176202456467, + "p50_ms": 20.753900054842234, + "p99_ms": 49.10359997302294, + "per_hop_us": 356.9871281633823 + } + ] +} \ No newline at end of file diff --git a/papers/ca2a/experiments/results/rerun-2026-10-01/verify_latency_vs_depth.run3.json b/papers/ca2a/experiments/results/rerun-2026-10-01/verify_latency_vs_depth.run3.json new file mode 100644 index 0000000..b1b1d4b --- /dev/null +++ b/papers/ca2a/experiments/results/rerun-2026-10-01/verify_latency_vs_depth.run3.json @@ -0,0 +1,61 @@ +{ + "repeats": 2000, + "rows": [ + { + "depth": 1, + "repeats": 2000, + "mean_ms": 0.32295984867960215, + "p50_ms": 0.20759995095431805, + "p99_ms": 2.0094001665711403, + "per_hop_us": 322.95984867960215 + }, + { + "depth": 2, + "repeats": 2000, + "mean_ms": 0.6906866482459009, + "p50_ms": 0.43080002069473267, + "p99_ms": 4.335900070145726, + "per_hop_us": 345.34332412295043 + }, + { + "depth": 4, + "repeats": 2000, + "mean_ms": 1.144661401049234, + "p50_ms": 0.8427000138908625, + "p99_ms": 5.143600050359964, + "per_hop_us": 286.1653502623085 + }, + { + "depth": 8, + "repeats": 2000, + "mean_ms": 3.3125396999530494, + "p50_ms": 2.603100147098303, + "p99_ms": 12.309999903663993, + "per_hop_us": 414.0674624941312 + }, + { + "depth": 16, + "repeats": 2000, + "mean_ms": 7.119648399180733, + "p50_ms": 6.1437999829649925, + "p99_ms": 18.975799903273582, + "per_hop_us": 444.9780249487958 + }, + { + "depth": 32, + "repeats": 2000, + "mean_ms": 12.686731901601888, + "p50_ms": 11.104400036856532, + "p99_ms": 36.28979995846748, + "per_hop_us": 396.460371925059 + }, + { + "depth": 64, + "repeats": 2000, + "mean_ms": 26.076142547419295, + "p50_ms": 23.49080005660653, + "p99_ms": 64.77579986676574, + "per_hop_us": 407.4397273034265 + } + ] +} \ No newline at end of file diff --git a/papers/ca2a/experiments/results/sealed_channel_throughput.json b/papers/ca2a/experiments/results/sealed_channel_throughput.json new file mode 100644 index 0000000..47aef36 --- /dev/null +++ b/papers/ca2a/experiments/results/sealed_channel_throughput.json @@ -0,0 +1,92 @@ +{ + "repeats": 2000, + "sizes": [ + 64, + 256, + 1024, + 4096, + 16384, + 65536, + 262144 + ], + "all_roundtrips_ok": true, + "rows": [ + { + "size_bytes": 64, + "repeats": 2000, + "seal_mean_ms": 0.10058899928117171, + "seal_p99_ms": 0.2986999461427331, + "open_mean_ms": 0.06608955061528832, + "open_p99_ms": 0.24500000290572643, + "roundtrip_mean_ms": 0.16667854989646003, + "throughput_mib_s": 0.3661848287491988, + "roundtrip_ok": true + }, + { + "size_bytes": 256, + "repeats": 2000, + "seal_mean_ms": 0.09840209881076589, + "seal_p99_ms": 0.31489995308220387, + "open_mean_ms": 0.0645865008700639, + "open_p99_ms": 0.18170010298490524, + "roundtrip_mean_ms": 0.1629885996808298, + "throughput_mib_s": 1.4979000094367647, + "roundtrip_ok": true + }, + { + "size_bytes": 1024, + "repeats": 2000, + "seal_mean_ms": 0.10597345017595217, + "seal_p99_ms": 0.3386999014765024, + "open_mean_ms": 0.0672563502448611, + "open_p99_ms": 0.2300000051036477, + "roundtrip_mean_ms": 0.17322980042081326, + "throughput_mib_s": 5.637381660821147, + "roundtrip_ok": true + }, + { + "size_bytes": 4096, + "repeats": 2000, + "seal_mean_ms": 0.10297664842801169, + "seal_p99_ms": 0.2633000258356333, + "open_mean_ms": 0.06910005182726309, + "open_p99_ms": 0.19689998589456081, + "roundtrip_mean_ms": 0.17207670025527477, + "throughput_mib_s": 22.700632881762033, + "roundtrip_ok": true + }, + { + "size_bytes": 16384, + "repeats": 2000, + "seal_mean_ms": 0.12098974973196164, + "seal_p99_ms": 0.3429000498726964, + "open_mean_ms": 0.08641620026901364, + "open_p99_ms": 0.29200001154094934, + "roundtrip_mean_ms": 0.20740595000097528, + "throughput_mib_s": 75.33535079358391, + "roundtrip_ok": true + }, + { + "size_bytes": 65536, + "repeats": 2000, + "seal_mean_ms": 0.24487830116413534, + "seal_p99_ms": 0.7004999788478017, + "open_mean_ms": 0.20350434962892905, + "open_p99_ms": 0.7053000153973699, + "roundtrip_mean_ms": 0.4483826507930644, + "throughput_mib_s": 139.3898713285513, + "roundtrip_ok": true + }, + { + "size_bytes": 262144, + "repeats": 2000, + "seal_mean_ms": 0.6198881007730961, + "seal_p99_ms": 1.4959999825805426, + "open_mean_ms": 0.5604395996197127, + "open_p99_ms": 1.4138000551611185, + "roundtrip_mean_ms": 1.1803277003928088, + "throughput_mib_s": 211.80558578503317, + "roundtrip_ok": true + } + ] +} \ No newline at end of file diff --git a/papers/ca2a/experiments/results/verify_latency_vs_depth.json b/papers/ca2a/experiments/results/verify_latency_vs_depth.json new file mode 100644 index 0000000..8298e67 --- /dev/null +++ b/papers/ca2a/experiments/results/verify_latency_vs_depth.json @@ -0,0 +1,61 @@ +{ + "repeats": 2000, + "rows": [ + { + "depth": 1, + "repeats": 2000, + "mean_ms": 0.24558830063324422, + "p50_ms": 0.2704999642446637, + "p99_ms": 0.43569994159042835, + "per_hop_us": 245.58830063324422 + }, + { + "depth": 2, + "repeats": 2000, + "mean_ms": 0.5322252998594195, + "p50_ms": 0.5432999460026622, + "p99_ms": 1.0974000906571746, + "per_hop_us": 266.11264992970973 + }, + { + "depth": 4, + "repeats": 2000, + "mean_ms": 0.948279001226183, + "p50_ms": 0.9626999963074923, + "p99_ms": 2.2214000346139073, + "per_hop_us": 237.06975030654576 + }, + { + "depth": 8, + "repeats": 2000, + "mean_ms": 2.0739633006160147, + "p50_ms": 2.1048999624326825, + "p99_ms": 4.302299930714071, + "per_hop_us": 259.24541257700184 + }, + { + "depth": 16, + "repeats": 2000, + "mean_ms": 3.8186680995859206, + "p50_ms": 3.946600016206503, + "p99_ms": 6.662399973720312, + "per_hop_us": 238.66675622412004 + }, + { + "depth": 32, + "repeats": 2000, + "mean_ms": 7.616980401042383, + "p50_ms": 7.7714999206364155, + "p99_ms": 12.980500003322959, + "per_hop_us": 238.03063753257447 + }, + { + "depth": 64, + "repeats": 2000, + "mean_ms": 13.870091651158873, + "p50_ms": 13.708800077438354, + "p99_ms": 26.084500015713274, + "per_hop_us": 216.7201820493574 + } + ] +} \ No newline at end of file diff --git a/papers/ca2a/experiments/sealed_channel_throughput.py b/papers/ca2a/experiments/sealed_channel_throughput.py new file mode 100644 index 0000000..aae5f4a --- /dev/null +++ b/papers/ca2a/experiments/sealed_channel_throughput.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +"""Sealed peer channel throughput and latency vs payload size. + +Measures the cost of the C4 sealed channel (HPKE-style X25519 -> HKDF-SHA256 -> +ChaCha20-Poly1305) as implemented in ca2a_runtime.channel. For a range of +payload sizes we time seal (sender side: ephemeral keygen + ECDH + HKDF + AEAD +encrypt) and open (receiver side: ECDH + HKDF + AEAD decrypt), then report mean +seal/open latency (ms), the round-trip mean, and end-to-end throughput (MiB/s) +derived from the round trip. A correctness pass first confirms every opened +payload equals the original, so the timing only covers genuinely valid +seal/open pairs. + +Every number is what this run measured; nothing is synthesized. Payloads are +random bytes so the AEAD does real work on every iteration. + +Writes results/sealed_channel_throughput.json and the plot +figures/sealed_channel_throughput.pdf. + +Run from the paper directory, with CA2A_ROOT set to a ca2a checkout: + PYTHONUTF8=1 python experiments/sealed_channel_throughput.py +""" +# ruff: noqa: T201 +from __future__ import annotations + +import json +import os +import sys +import time +from pathlib import Path + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt # noqa: E402 + +# CA2A_ROOT points at a ca2a checkout. This edition's rerun used the v0.3.1 +# release, commit 7d90823142dda1be589def3b22a3099581c7df7a. +_CA2A_ROOT = Path(os.environ.get("CA2A_ROOT", "ca2a")) +_CODE_SRC = _CA2A_ROOT / "src" +if not (_CODE_SRC / "ca2a_runtime").is_dir(): + raise SystemExit("Set CA2A_ROOT to a ca2a checkout containing src/ca2a_runtime.") +sys.path.insert(0, str(_CODE_SRC)) + +from ca2a_runtime.channel import ( # noqa: E402 + SealedChannel, + generate_channel_keypair, + open_sealed, +) + +# Payload sizes in bytes: from a small task envelope to a large document blob. +SIZES = [64, 256, 1024, 4096, 16384, 65536, 262144] +REPEATS = 2000 +WARMUP = 200 + + +def percentile(sorted_vals: list[float], q: float) -> float: + idx = min(len(sorted_vals) - 1, int(len(sorted_vals) * q)) + return sorted_vals[idx] + + +def main() -> int: + print("=" * 64) + print("Sealed peer channel: throughput and latency vs payload size") + print("=" * 64) + + priv, pub_hex = generate_channel_keypair() + channel = SealedChannel(pub_hex) + + rows = [] + all_ok = True + for size in SIZES: + payload = os.urandom(size) + + # Correctness gate: seal/open must round-trip before we time it. + blob = channel.seal(payload) + recovered = open_sealed(blob, priv) + ok = recovered == payload + all_ok = all_ok and ok + if not ok: + print(f" size={size} ROUND-TRIP MISMATCH") + + for _ in range(WARMUP): + b = channel.seal(payload) + open_sealed(b, priv) + + seal_ms: list[float] = [] + open_ms: list[float] = [] + for _ in range(REPEATS): + t0 = time.perf_counter() + b = channel.seal(payload) + t1 = time.perf_counter() + open_sealed(b, priv) + t2 = time.perf_counter() + seal_ms.append((t1 - t0) * 1000.0) + open_ms.append((t2 - t1) * 1000.0) + + seal_ms.sort() + open_ms.sort() + seal_mean = sum(seal_ms) / len(seal_ms) + open_mean = sum(open_ms) / len(open_ms) + rt_mean = seal_mean + open_mean + # End-to-end throughput from the round-trip mean (seal + open). + mib_per_s = (size / (1024 * 1024)) / (rt_mean / 1000.0) + row = { + "size_bytes": size, + "repeats": REPEATS, + "seal_mean_ms": seal_mean, + "seal_p99_ms": percentile(seal_ms, 0.99), + "open_mean_ms": open_mean, + "open_p99_ms": percentile(open_ms, 0.99), + "roundtrip_mean_ms": rt_mean, + "throughput_mib_s": mib_per_s, + "roundtrip_ok": ok, + } + rows.append(row) + print( + f" size={size:>7}B: seal={seal_mean:.4f} ms open={open_mean:.4f} ms " + f"rt={rt_mean:.4f} ms thr={mib_per_s:8.2f} MiB/s ok={ok}" + ) + + out = Path(__file__).parent / "results" / "sealed_channel_throughput.json" + out.parent.mkdir(parents=True, exist_ok=True) + out.write_text( + json.dumps( + {"repeats": REPEATS, "sizes": SIZES, "all_roundtrips_ok": all_ok, "rows": rows}, + indent=2, + ) + ) + + # Plot: left axis round-trip latency (ms), right axis throughput (MiB/s), vs size. + sizes = [r["size_bytes"] for r in rows] + rt = [r["roundtrip_mean_ms"] for r in rows] + thr = [r["throughput_mib_s"] for r in rows] + + fig, ax1 = plt.subplots(figsize=(5.4, 3.2)) + color1 = "tab:blue" + ax1.plot(sizes, rt, marker="o", color=color1, label="round-trip latency") + ax1.set_xlabel("payload size (bytes)") + ax1.set_ylabel("seal+open round-trip (ms)", color=color1) + ax1.tick_params(axis="y", labelcolor=color1) + ax1.set_xscale("log", base=2) + ax1.set_xticks(sizes) + ax1.get_xaxis().set_major_formatter(matplotlib.ticker.ScalarFormatter()) + ax1.tick_params(axis="x", labelrotation=45) + ax1.grid(True, which="both", linewidth=0.3, alpha=0.5) + + ax2 = ax1.twinx() + color2 = "tab:red" + ax2.plot(sizes, thr, marker="s", linestyle="--", color=color2, label="throughput") + ax2.set_ylabel("throughput (MiB/s)", color=color2) + ax2.tick_params(axis="y", labelcolor=color2) + + fig.tight_layout() + fig_path = Path(__file__).parent.parent / "figures" / "sealed_channel_throughput.pdf" + fig_path.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(fig_path, metadata={"CreationDate": None, "ModDate": None}) + + print(f"\nwrote {out}") + print(f"wrote {fig_path}") + + small = rows[0] + large = rows[-1] + print( + f"KEY RESULT: a {small['size_bytes']}-byte task envelope seals+opens in " + f"{small['roundtrip_mean_ms']:.4f} ms (setup-dominated); at " + f"{large['size_bytes']} bytes the AEAD reaches {large['throughput_mib_s']:.1f} MiB/s; " + f"{'all round-trips verified' if all_ok else 'ROUND-TRIP FAILURE'}" + ) + return 0 if all_ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/papers/ca2a/experiments/verify_latency_vs_depth.py b/papers/ca2a/experiments/verify_latency_vs_depth.py new file mode 100644 index 0000000..dce1fde --- /dev/null +++ b/papers/ca2a/experiments/verify_latency_vs_depth.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +"""Verification latency vs delegation-chain depth. + +Measures the wall-clock cost of verify_chain (signature + continuity + +attenuation + anti-replay over every hop) as the chain deepens. This is the +per-inbound-call cost a callee pays before it acts, so its scaling in depth +matters for A2A workloads with long delegation chains. + +For each depth we build one signed chain, then time verify_chain over REPEATS +iterations and report mean / p50 / p99 in milliseconds. Chains are signed once +up front so the measurement isolates verification, not key generation. + +Writes results/verify_latency_vs_depth.json and the plot +figures/verify_latency_vs_depth.pdf. + +Run from the paper directory, with CA2A_ROOT set to a ca2a checkout: + PYTHONUTF8=1 python experiments/verify_latency_vs_depth.py +""" +# ruff: noqa: T201 +from __future__ import annotations + +import json +import os +import sys +import time +from pathlib import Path + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt # noqa: E402 + +# CA2A_ROOT points at a ca2a checkout. This edition's rerun used the v0.3.1 +# release, commit 7d90823142dda1be589def3b22a3099581c7df7a. +_CA2A_ROOT = Path(os.environ.get("CA2A_ROOT", "ca2a")) +_CODE_SRC = _CA2A_ROOT / "src" +if not (_CODE_SRC / "ca2a_runtime").is_dir(): + raise SystemExit("Set CA2A_ROOT to a ca2a checkout containing src/ca2a_runtime.") +sys.path.insert(0, str(_CODE_SRC)) + +from ca2a_runtime.delegation import ( # noqa: E402 + DelegationCredential, + new_keypair, + verify_chain, +) + +DEPTHS = [1, 2, 4, 8, 16, 32, 64] +REPEATS = 2000 +WARMUP = 200 +ROOT_CAPS = frozenset(f"cap:{i}" for i in range(16)) + + +def build_chain(depth: int) -> list[DelegationCredential]: + """A fixed-scope, correctly signed chain of `depth` hops.""" + chain: list[DelegationCredential] = [] + priv, pub = new_keypair() + parent_id: str | None = None + for d in range(depth): + next_priv, next_pub = new_keypair() + cred = DelegationCredential( + credential_id=f"cred-{d}", + issuer=pub, + subject=next_pub, + scope=ROOT_CAPS, + depth=d, + parent_id=parent_id, + ).sign(priv) + chain.append(cred) + parent_id = cred.credential_id + priv, pub = next_priv, next_pub + return chain + + +def percentile(sorted_vals: list[float], q: float) -> float: + idx = min(len(sorted_vals) - 1, int(len(sorted_vals) * q)) + return sorted_vals[idx] + + +def main() -> int: + print("=" * 64) + print("Verification latency vs delegation-chain depth") + print("=" * 64) + + rows = [] + for depth in DEPTHS: + chain = build_chain(depth) + roots = {chain[0].issuer} + for _ in range(WARMUP): + verify_chain(chain, max_depth=128, trusted_root_issuers=roots) + samples: list[float] = [] + for _ in range(REPEATS): + start = time.perf_counter() + verify_chain(chain, max_depth=128, trusted_root_issuers=roots) + samples.append((time.perf_counter() - start) * 1000.0) + samples.sort() + mean = sum(samples) / len(samples) + row = { + "depth": depth, + "repeats": REPEATS, + "mean_ms": mean, + "p50_ms": percentile(samples, 0.50), + "p99_ms": percentile(samples, 0.99), + "per_hop_us": (mean / depth) * 1000.0, + } + rows.append(row) + print( + f" depth={depth:>3}: mean={row['mean_ms']:.4f} ms " + f"p50={row['p50_ms']:.4f} ms p99={row['p99_ms']:.4f} ms " + f"per-hop={row['per_hop_us']:.2f} us" + ) + + out = Path(__file__).parent / "results" / "verify_latency_vs_depth.json" + out.parent.mkdir(parents=True, exist_ok=True) + out.write_text(json.dumps({"repeats": REPEATS, "rows": rows}, indent=2)) + + # Plot: mean and p99 latency vs depth. + depths = [r["depth"] for r in rows] + means = [r["mean_ms"] for r in rows] + p99s = [r["p99_ms"] for r in rows] + fig, ax = plt.subplots(figsize=(5.2, 3.2)) + ax.plot(depths, means, marker="o", label="mean") + ax.plot(depths, p99s, marker="s", linestyle="--", label="p99") + ax.set_xlabel("delegation-chain depth (hops)") + ax.set_ylabel("verify_chain latency (ms)") + ax.set_xscale("log", base=2) + ax.set_xticks(depths) + ax.get_xaxis().set_major_formatter(matplotlib.ticker.ScalarFormatter()) + ax.grid(True, which="both", linewidth=0.3, alpha=0.5) + ax.legend(frameon=False) + fig.tight_layout() + fig_path = Path(__file__).parent.parent / "figures" / "verify_latency_vs_depth.pdf" + fig_path.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(fig_path, metadata={"CreationDate": None, "ModDate": None}) + print(f"\nwrote {out}") + print(f"wrote {fig_path}") + + slope = (rows[-1]["mean_ms"] - rows[0]["mean_ms"]) / (rows[-1]["depth"] - rows[0]["depth"]) + print( + f"KEY RESULT: verify latency is linear in depth at ~{slope*1000:.1f} us/hop; " + f"a {rows[-1]['depth']}-hop chain verifies in {rows[-1]['mean_ms']:.3f} ms mean " + f"(p99 {rows[-1]['p99_ms']:.3f} ms)" + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/papers/ca2a/figures/sealed_channel_throughput.pdf b/papers/ca2a/figures/sealed_channel_throughput.pdf new file mode 100644 index 0000000000000000000000000000000000000000..665bb9858e3fa2f9af16e09fa866dff401d5e8f7 GIT binary patch literal 15647 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 literal 0 HcmV?d00001 diff --git a/papers/ca2a/paper.tex b/papers/ca2a/paper.tex new file mode 100644 index 0000000..3e94baf --- /dev/null +++ b/papers/ca2a/paper.tex @@ -0,0 +1,124 @@ +\documentclass[10pt,letterpaper]{article} + +\usepackage[margin=1in]{geometry} +\usepackage{iftex} +% mathptmx has no TU-encoded faces, so under XeTeX (Tectonic) earlier versions fell back +% to Latin Modern regular and lost every bold and italic. Use Termes there. +\ifPDFTeX\usepackage{mathptmx}\else\usepackage{fontspec}\setmainfont{texgyretermes}[Extension=.otf,UprightFont=*-regular,BoldFont=*-bold,ItalicFont=*-italic,BoldItalicFont=*-bolditalic]\fi +\usepackage{amsmath} +\usepackage{graphicx} +\usepackage{booktabs} +\usepackage{array} +\usepackage{listings} +\usepackage{xcolor} +\usepackage{tikz} +\usetikzlibrary{positioning,arrows.meta,fit,backgrounds,calc} +\usepackage{natbib} +\usepackage[colorlinks=true,linkcolor=blue,citecolor=blue,urlcolor=blue]{hyperref} +\usepackage{microtype} +\usepackage{parskip} +\usepackage{titlesec} +\usepackage{enumitem} +\usepackage{caption} +\usepackage{needspace} + +\setlength{\parskip}{6pt} +\setlength{\parindent}{0pt} +\setlength{\emergencystretch}{3em} + +\lstset{ + basicstyle=\small\ttfamily, + breaklines=true, + frame=single, + numbers=left, + numberstyle=\tiny, + xleftmargin=2em, + framexleftmargin=1.5em, + backgroundcolor=\color{gray!8}, +} + +\definecolor{claimcolor}{RGB}{0,70,140} +\newcounter{claim} +\newenvironment{property}[1][]{% + \refstepcounter{claim}% + \needspace{6\baselineskip}% + \vspace{4pt}% + \noindent\fbox{\parbox{\dimexpr\linewidth-2\fboxsep-2\fboxrule}{% + \textbf{\textcolor{claimcolor}{Property \theclaim} (#1).} +}}\par\vspace{2pt}% +}{\vspace{4pt}} + +\title{\textbf{cA2A: Confidential Agent-to-Agent Delegation as a Profile on A2A}} + +\author{ + Rishabh Poddar \quad Aaron Fulkerson \quad Imran Siddique \\ + OPAQUE Systems \\[4pt] + \normalsize \url{https://github.com/agentrust-io/ca2a} +} + +\date{October 1, 2026 \ \small Technical report, version 3} + +\begin{document} + +\maketitle + +\begin{abstract} +The Agent2Agent (A2A) protocol moves tasks between agents, and its Signed Agent Card lets a +client check that a domain owner issued a card. The card does not bound the authority a +delegating agent passes on, establish what code a peer runs, keep a task payload from the +peer's host, or leave an offline record of who delegated what to whom. This technical report +describes cA2A (Confidential A2A), a trust profile layered on A2A rather than a new transport. +It composes four mechanisms: signed delegation credentials whose scope can only narrow at each +hop, appraisal of a peer's attestation evidence before a task is sent, a payload sealed to the +channel key that evidence vouches for, and a signed per-hop provenance record linked to its +parent. Attenuated delegation and provenance binding are covered by prior capability-token work +and IETF drafts; the contribution here is their composition on A2A with an open implementation. +We state six properties and report software experiments rerun against ca2a 0.3.1: attenuation +checks over 5,400 generated chains, rejection of in-chain replay and cross-chain splicing, +intersection of delegated scope with local policy, sealed-payload behavior at the cryptographic layer, structural checks on linked +provenance records, and a cross-operator attestation protocol exercised with synthetic +evidence. Chain verification cost about 0.22\,ms per hop in this environment. These results do +not show that a peer's key is confined to attested code or that attestation works across +independent operators. Recorded hardware runs cover one-directional appraisal of an Intel TDX +peer by an AMD SEV-SNP peer in another cloud and a same-operator mutual SEV-SNP diagnostic; +mutual attestation between independent operators has not been demonstrated. +\end{abstract} + +\paragraph{Scope of this edition.} +This is an author-prepared technical report, not peer reviewed and not a normative +specification. Version 3 adds the caller-attestation relay that ca2a 0.4.0 fixed +(GHSA-xxj2-p57x-x954), which affects the release evaluated here, and corrects four statements: +0.3.1 was no longer the current release when version 2 appeared, a transparency log does not +make omission detectable, a wrong depth increment raises a broken-link error, and scope may stay +equal across a hop. It also restores bold and italic type that the Tectonic build had dropped. Results are unchanged. Version 2 (October 1, 2026) narrowed a design claim +of version 1 (September 27, 2026): the chain checks do not bind a delegation chain to a task or +execution, so a delegate can reuse a valid chain across tasks. Version 1 revised a draft dated +July 2026. Every software experiment was rerun on September 27, 2026 against ca2a 0.3.1 (commit +\texttt{7d90823}), then the current release; the July +results came from an unpinned earlier checkout and are retained beside the rerun. Correctness +counts are unchanged. Timings changed: chain verification rose from about 195 to about 216 +microseconds per hop, and sealed-channel throughput at 256\,KiB fell from 266 to 212\,MiB/s. +Text, tables, and figures report the rerun. Status statements for C3 to C6 were revised to +match the 0.3.1 code, security fixes made after the original evaluation are reported in +Section~\ref{sec:after}, and the related work was corrected and extended. No hardware run was +repeated. Current behavior, requirements, and limitations are maintained in the project +repository. + +\noindent\textbf{Patent Pending.} + +\tableofcontents +\newpage + +\input{sections/01-intro} +\input{sections/02-background} +\input{sections/03-design} +\input{sections/04-claims} +\input{sections/05-evaluation} +\input{sections/05b-after} +\input{sections/06-related} +\input{sections/07-conclusion} + +\bibliographystyle{plainnat} +\bibliography{refs} + +\end{document} diff --git a/papers/ca2a/refs.bib b/papers/ca2a/refs.bib new file mode 100644 index 0000000..53a0b43 --- /dev/null +++ b/papers/ca2a/refs.bib @@ -0,0 +1,342 @@ +@misc{a2a2026, + author = {{A2A Project}}, + title = {{Agent2Agent (A2A)} Protocol Specification, Version 1.0}, + year = {2026}, + howpublished = {\url{https://a2a-protocol.org/latest/specification/}}, + note = {Accessed September 2026} +} + +@misc{aaifa2a2026, + author = {{Agentic AI Foundation}}, + title = {{A2A} Joins {AAIF}'s Open Agentic Stack}, + year = {2026}, + month = aug, + howpublished = {\url{https://aaif.io/blog/a2a-joins-aaif}}, + note = {Published August 17, 2026. Accessed September 2026} +} + +@misc{mcp2024, + author = {{Model Context Protocol Project}}, + title = {Model Context Protocol Specification, Revision 2026-07-28}, + year = {2026}, + howpublished = {\url{https://modelcontextprotocol.io/specification/2026-07-28}}, + note = {Accessed September 2026} +} + +@techreport{cmcp2026, + author = {Poddar, Rishabh and Fulkerson, Aaron and Siddique, Imran}, + title = {{cMCP}: Verifiable Policy Enforcement for {AI} Agent Tool Calls}, + institution = {OPAQUE Systems}, + type = {Technical report, version 1}, + year = {2026}, + month = sep, + doi = {10.5281/zenodo.23001694}, + url = {https://doi.org/10.5281/zenodo.23001694} +} + +@techreport{trace2025, + author = {Poddar, Rishabh and Fulkerson, Aaron and Siddique, Imran}, + title = {{TRACE}: Trust, Runtime Attestation, and Compliance Evidence ({A} Portable + Attestation Format for {AI} Agent Runtime Governance)}, + institution = {OPAQUE Systems}, + type = {Technical report, version 2}, + year = {2026}, + month = sep, + doi = {10.5281/zenodo.23024103}, + url = {https://doi.org/10.5281/zenodo.23024103} +} + +@techreport{agentmanifest2026, + author = {Siddique, Imran}, + title = {Agent Manifest: Portable, Hardware-Attestable Identity and Provenance for + {AI} Agents}, + institution = {OPAQUE Systems}, + type = {Technical report, version 1}, + year = {2026}, + month = sep, + doi = {10.5281/zenodo.23001698}, + url = {https://doi.org/10.5281/zenodo.23001698} +} + +@misc{ca2arepo2026, + author = {{cA2A Contributors}}, + title = {{ca2a-runtime} 0.3.1: Confidential Agent-to-Agent Runtime}, + year = {2026}, + howpublished = {\url{https://github.com/agentrust-io/ca2a}}, + note = {Tag v0.3.1, commit 7d90823142dda1be589def3b22a3099581c7df7a, September 26, 2026} +} + +@article{cedar2023, + author = {Cutler, Joseph W. and Disselkoen, Craig and Eline, Aaron and + He, Shaobo and Headley, Kyle and Hicks, Michael and Hietala, Kesha and + Ioannidis, Eleftherios and Kastner, John and Mamat, Anwar and + McAdams, Darin and McCutchen, Matt and Rungta, Neha and + Torlak, Emina and Wells, Andrew M.}, + title = {{Cedar}: A New Language for Expressive, Fast, Safe, and Analyzable + Authorization}, + journal = {Proceedings of the {ACM} on Programming Languages}, + volume = {8}, + number = {{OOPSLA1}}, + pages = {670 to 697}, + year = {2024}, + doi = {10.1145/3649835} +} + +@inproceedings{macaroons2014, + author = {Birgisson, Arnar and Politz, Joe Gibbs and Erlingsson, {\'U}lfar and + Taly, Ankur and Vrable, Michael and Lentczner, Mark}, + title = {Macaroons: Cookies with Contextual Caveats for Decentralized + Authorization in the Cloud}, + booktitle = {Proceedings of the Network and Distributed System Security Symposium + ({NDSS})}, + year = {2014}, + url = {https://www.ndss-symposium.org/ndss2014/macaroons-cookies-contextual-caveats-decentralized-authorization-cloud} +} + +@article{confuseddeputy1988, + author = {Hardy, Norm}, + title = {The Confused Deputy (or Why Capabilities Might Have Been Invented)}, + journal = {{ACM SIGOPS} Operating Systems Review}, + volume = {22}, + number = {4}, + pages = {36 to 38}, + year = {1988}, + doi = {10.1145/54289.871709} +} + +@misc{tomasev2026, + author = {Toma{\v{s}}ev, Nenad and Franklin, Matija and Osindero, Simon}, + title = {Intelligent {AI} Delegation}, + year = {2026}, + howpublished = {arXiv:2602.11865}, + url = {https://arxiv.org/abs/2602.11865} +} + +@misc{aip2026, + author = {Prakash, Sunil}, + title = {{AIP}: Agent Identity Protocol for Verifiable Delegation Across {MCP} and {A2A}}, + year = {2026}, + howpublished = {arXiv:2603.24775}, + url = {https://arxiv.org/abs/2603.24775} +} + +@misc{pedigree2026, + author = {Rampalli, Karthik}, + title = {{PEDIGREE}: Verifiable Delegation Identity for Agentic {AI} Systems}, + year = {2026}, + month = apr, + howpublished = {Internet-Draft draft-rampalli-pedigree-00, individual submission}, + url = {https://datatracker.ietf.org/doc/draft-rampalli-pedigree/}, + note = {Work in progress} +} + +@misc{capsule2026, + author = {Rampalli, Karthik}, + title = {Binding Per-Action Authorization and Memory Provenance into Agent Action + Capsules}, + year = {2026}, + month = jul, + howpublished = {Internet-Draft draft-rampalli-scitt-capsule-provenance-binding-00, individual + submission}, + url = {https://datatracker.ietf.org/doc/draft-rampalli-scitt-capsule-provenance-binding/}, + note = {Work in progress} +} + +@misc{secretssurvey2026, + author = {Forough, Javad and Kogias, Marios and Haddadi, Hamed}, + title = {When Agents Handle Secrets: A Survey of Confidential Computing for Agentic {AI}}, + year = {2026}, + howpublished = {arXiv:2605.03213}, + url = {https://arxiv.org/abs/2605.03213} +} + +@misc{crossprotocol2026, + author = {Anbiaee, Zeynab and Rabbani, Mahdi and Mirani, Mansur and Piya, Gunjan and + Opushnyev, Igor and Ghorbani, Ali and Dadkhah, Sajjad}, + title = {Security Threat Modeling for Emerging {AI}-Agent Protocols: A Comparative + Analysis of {MCP}, {A2A}, {Agora}, and {ANP}}, + year = {2026}, + howpublished = {arXiv:2602.11327}, + url = {https://arxiv.org/abs/2602.11327} +} + +@misc{agntcy2026, + author = {{AGNTCY Project}}, + title = {{AGNTCY}: Building the Internet of Agents}, + year = {2026}, + howpublished = {\url{https://agntcy.org/}}, + note = {Linux Foundation project since July 2025. Accessed September 2026} +} + +@techreport{eqtyvc2024, + author = {{EQTY Lab}}, + title = {Verifiable Compute}, + institution = {EQTY Lab}, + type = {White paper, version 1.08}, + year = {2024}, + month = dec, + url = {https://a.storyblok.com/f/257174/x/7b61f19b05/verifiable-compute-white-paper-december-2024.pdf} +} + +@misc{eqty2026, + author = {{EQTY Lab}}, + title = {Verifiable Knowledge: The Third Pillar of Trust for Agentic {AI}}, + year = {2026}, + month = jun, + howpublished = {\url{https://www.eqtylab.io/blog/verifiable-knowledge-the-third-pillar-of-trust-for-the-agentic-ai}}, + note = {Published June 1, 2026. Accessed September 2026} +} + +@misc{wimse2025, + author = {Salowey, Joseph A. and Rosomakho, Yaroslav and Tschofenig, Hannes}, + title = {Workload Identity in a Multi System Environment ({WIMSE}) Architecture}, + year = {2026}, + month = jul, + howpublished = {Internet-Draft draft-ietf-wimse-arch-08}, + url = {https://datatracker.ietf.org/doc/draft-ietf-wimse-arch/}, + note = {Work in progress} +} + +@techreport{scitt2024, + author = {Birkholz, Henk and Delignat-Lavaud, Antoine and Fournet, C{\'e}dric and + Deshpande, Yogesh and Lasker, Steve}, + title = {An Architecture for Trustworthy and Transparent Digital Supply Chains}, + institution = {{RFC Editor}}, + type = {{RFC}}, + number = {9943}, + year = {2026}, + month = jun, + url = {https://www.rfc-editor.org/rfc/rfc9943} +} + +@techreport{amdsevsnp2023, + author = {{Advanced Micro Devices, Inc.}}, + title = {{AMD SEV-SNP}: Strengthening {VM} Isolation with Integrity Protection + and More}, + institution = {{AMD}}, + type = {White paper}, + year = {2020}, + month = jan, + url = {https://www.amd.com/content/dam/amd/en/documents/epyc-business-docs/white-papers/SEV-SNP-strengthening-vm-isolation-with-integrity-protection-and-more.pdf} +} + +@techreport{inteltdx2023, + author = {{Intel Corporation}}, + title = {{Intel TDX} Module Base Architecture Specification}, + institution = {Intel Corporation}, + year = {2026}, + month = may, + url = {https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/documentation.html} +} + +@techreport{rats2021, + author = {Birkholz, Henk and Thaler, Dave and Richardson, Michael and + Smith, Ned and Pan, Wei}, + title = {Remote {ATtestation} {procedureS} ({RATS}) Architecture}, + institution = {{RFC Editor}}, + type = {{RFC}}, + number = {9334}, + year = {2023}, + month = jan, + url = {https://www.rfc-editor.org/rfc/rfc9334} +} + +@techreport{eat2023, + author = {Lundblade, Laurence and Mandyam, Giridhar and O'Donoghue, Jeremy and + Wallace, Carl}, + title = {The Entity Attestation Token ({EAT})}, + institution = {{RFC Editor}}, + type = {{RFC}}, + number = {9711}, + year = {2025}, + month = apr, + url = {https://www.rfc-editor.org/rfc/rfc9711} +} + +@techreport{jcs2020, + author = {Rundgren, Anders and Jordan, Bret and Erdtman, Samuel}, + title = {{JSON} Canonicalization Scheme ({JCS})}, + institution = {{RFC Editor}}, + type = {{RFC}}, + number = {8785}, + year = {2020}, + month = jun, + url = {https://www.rfc-editor.org/rfc/rfc8785} +} + +@techreport{eddsa2017, + author = {Josefsson, Simon and Liusvaara, Ilari}, + title = {{Edwards-Curve} Digital Signature Algorithm ({EdDSA})}, + institution = {{RFC Editor}}, + type = {{RFC}}, + number = {8032}, + year = {2017}, + month = jan, + url = {https://www.rfc-editor.org/rfc/rfc8032} +} + +@techreport{ct2013, + author = {Laurie, Ben and Langley, Adam and Kasper, Emilia}, + title = {Certificate Transparency}, + institution = {{RFC Editor}}, + type = {{RFC}}, + number = {6962}, + year = {2013}, + month = jun, + url = {https://www.rfc-editor.org/rfc/rfc6962} +} + +@misc{prov2013, + author = {Moreau, Luc and Missier, Paolo}, + title = {{PROV-DM}: The {PROV} Data Model}, + year = {2013}, + month = apr, + howpublished = {W3C Recommendation}, + url = {https://www.w3.org/TR/prov-dm/} +} + +@article{confai2024, + author = {Mo, Fan and Tarkhani, Zahra and Haddadi, Hamed}, + title = {Machine Learning with Confidential Computing: A Systematization of Knowledge}, + journal = {{ACM} Computing Surveys}, + volume = {56}, + number = {11}, + pages = {1 to 40}, + year = {2024}, + doi = {10.1145/3670007} +} + +@inproceedings{avalanche1985, + author = {Webster, A. F. and Tavares, S. E.}, + title = {On the Design of {S}-Boxes}, + booktitle = {Advances in Cryptology: {CRYPTO} '85 Proceedings}, + series = {Lecture Notes in Computer Science}, + volume = {218}, + publisher = {Springer}, + year = {1986}, + doi = {10.1007/3-540-39799-X_41} +} + +@inproceedings{abadi2003history, + author = {Mart{\'\i}n Abadi and C{\'e}dric Fournet}, + title = {Access Control Based on Execution History}, + booktitle = {Proceedings of the 10th Network and Distributed System Security Symposium (NDSS)}, + year = {2003}, + url = {https://www.ndss-symposium.org/ndss2003/access-control-based-execution-history/} +} + +@inproceedings{park2012pbac, + author = {Jaehong Park and Dang Nguyen and Ravi Sandhu}, + title = {A Provenance-Based Access Control Model}, + booktitle = {Tenth Annual International Conference on Privacy, Security and Trust (PST)}, + year = {2012} +} + +@misc{gallo2026pic, + author = {Nicola Gallo}, + title = {Proof-of-Continuity: A Temporal Model for Authority Propagation in Distributed Systems and {AI} Agents}, + howpublished = {arXiv:2607.08906}, + year = {2026}, + url = {https://arxiv.org/abs/2607.08906}, + note = {Model and drafts at \url{https://github.com/ngallo/pic-model}} +} diff --git a/papers/ca2a/sections/01-intro.tex b/papers/ca2a/sections/01-intro.tex new file mode 100644 index 0000000..0b0dc56 --- /dev/null +++ b/papers/ca2a/sections/01-intro.tex @@ -0,0 +1,82 @@ +\section{Introduction}\label{sec:intro} + +The Agent2Agent (A2A) protocol~\citep{a2a2026} defines how one agent discovers another and hands +it a task. It was contributed to the Linux Foundation, and on August 17, 2026 it joined the +Agentic AI Foundation as a hosted project, which reported backing from more than 150 +organizations~\citep{aaifa2a2026}. Teams that need agents from different vendors to exchange work +increasingly have a common transport to build on. + +A2A's trust anchor is the Signed Agent Card, which lets a client check that the owner of a +domain issued the card. It does not establish that a peer runs attested, unmodified code; that a +delegating agent actually holds the authority it passes on; that a task payload is confidential +to the intended peer; or that there is an auditable record of who delegated what to whom and +under which policy. + +This gap matters once one agent delegates to another. Agent A hands a task to B, and B hands +part of it to C. Nothing in the transport constrains B or C to the authority A actually held. A +card-verified peer can request more than it was granted, and the request will look valid at every +hop. This is the confused deputy problem~\citep{confuseddeputy1988}, now across trust domains +that never negotiated a shared authority model. Recent work treats verifiable delegation for +agents as open: Prakash reports finding no prior implemented protocol that jointly combined +verifiable delegation, holder-side attenuation, chained policy, transport bindings, and provenance +records, and proposes one~\citep{aip2026}; Forough et al.\ list compound attestation for multi-hop +agent chains among the open challenges for confidential agentic AI~\citep{secretssurvey2026}. +Mutual TLS and OAuth scopes secure the connection and assert an identity; they do not attenuate +authority per hop, appraise runtime integrity, or seal a payload to an appraised peer. + +This report describes cA2A (Confidential A2A), a trust \emph{profile} layered on A2A rather than a +competing transport. cA2A does not move tasks between agents; A2A does that. It defines the trust +envelope around a delegated task, in the way TRACE~\citep{trace2025} binds to +RATS~\citep{rats2021} and EAT~\citep{eat2023} rather than replacing them. cA2A composes four +mechanisms: + +\begin{itemize}[leftmargin=1.5em] + \item \textbf{Attenuated delegation.} Each hop carries a signed delegation credential whose + scope is a subset of its parent's. Depth is bounded, the root issuer must be trusted by the + verifier, and a credential identifier cannot repeat within a chain. + \item \textbf{Runtime attestation.} A caller appraises a peer's attestation evidence, which + binds the peer's channel key to a measurement, before it sends a delegated task. + \item \textbf{Sealed peer channel.} The task payload is sealed to the channel key the appraised + evidence vouches for, so only the holder of the matching private key can open it. + \item \textbf{Provenance record.} Each hop emits a signed TRACE record referencing its parent + record's hash and the delegation credential it acted under, so an auditor can check the + path offline. +\end{itemize} + +None of these mechanisms is new in isolation. Macaroons introduced offline attenuation of bearer +credentials~\citep{macaroons2014}, and recent agent-specific work specifies per-hop attenuation +with Cedar policies and parent-swap resistance~\citep{pedigree2026} or implements attenuated, +provenance-bound capability tokens across MCP and A2A~\citep{aip2026}. Section~\ref{sec:related} +compares them. + +\subsection*{Contributions} + +\begin{itemize}[leftmargin=1.5em] + \item \textbf{The cA2A profile.} A binding on A2A, and on the IETF RATS, EAT, and + SCITT~\citep{scitt2024} work, that composes attenuated delegation, attestation-gated sealing, + and signed provenance for agent-to-agent calls without changing the A2A transport. + \item \textbf{An open reference implementation.} The delegation credential model and offline + chain verifier, the inbound decision path, the sealed channel, attestation verifiers for AMD + SEV-SNP, Intel TDX, and TPM~2.0 evidence, signed TRACE provenance, and a CLI, released under + the MIT license as ca2a-runtime~\citep{ca2arepo2026}. Delegation semantics mirror those of + agent-manifest~\citep{agentmanifest2026}, and hardware-evidence parsing and certificate-chain + checks are shared with it. + \item \textbf{Six properties with their evidence stated.} We state six properties + (Section~\ref{sec:claims}) and report software experiments rerun against ca2a 0.3.1 + (Section~\ref{sec:evaluation}). C1 (attenuation soundness) and C2 (in-chain replay and splice + rejection) are validated in software. C3 (scope-policy intersection) is validated in software, + including the inbound path of the reference transport. C4 (sealed-payload confidentiality) is + validated at the cryptographic layer only. C5 (provenance integrity) is validated for + structural consistency by the experiment, while authenticity rests on signed records covered by + the project's tests. C6 (cross-operator attestation) is exercised with synthetic evidence and + is not validated on hardware across independent operators. +\end{itemize} + +\subsection*{Paper structure} + +Section~\ref{sec:background} covers A2A and its Signed Agent Card, TEE attestation, the TRACE +record format, Cedar policy, and provenance. Section~\ref{sec:design} describes the cA2A profile +and how its mechanisms compose. Section~\ref{sec:claims} states the six properties and the status +of each. Section~\ref{sec:evaluation} presents the rerun experiments. Section~\ref{sec:after} +reports findings made after the original evaluation. Section~\ref{sec:related} surveys related +work, and Section~\ref{sec:conclusion} concludes. diff --git a/papers/ca2a/sections/02-background.tex b/papers/ca2a/sections/02-background.tex new file mode 100644 index 0000000..0b00437 --- /dev/null +++ b/papers/ca2a/sections/02-background.tex @@ -0,0 +1,137 @@ +\section{Background}\label{sec:background} + +This section grounds cA2A in the protocols and mechanisms it composes. We describe A2A +and its Signed Agent Card, the tool-side analog in MCP and cMCP, capability attenuation +and how agent-manifest realizes a signed delegation chain, trusted execution environments +and remote attestation, the Cedar policy language, and the provenance and transparency +work that the TRACE record format draws on. None of this section is a novel claim; it +fixes terminology and states what each cited system provides. + +\subsection{A2A and the Signed Agent Card} + +The Agent2Agent (A2A) protocol~\citep{a2a2026} is a protocol for agent-to-agent +communication. An agent publishes an \emph{Agent Card}, a JSON document +that advertises the agent's identity, endpoint, and the skills it offers. A client agent +reads a peer's card, then sends it a \emph{task}, a unit of work the peer executes and +reports back on. A2A defines how tasks are moved and how their state transitions; it does +not define an authorization model for what a peer is permitted to do with a task once it +holds it. + +The Signed Agent Card is A2A's trust anchor. The card carries a signature that lets a +client verify that the claimed domain owner issued it. This answers one question: did this +domain publish this card. It does not establish that the peer is running attested code, +that a delegating agent holds the authority it forwards, that the task payload is +confidential to the peer, or that there is an unbroken record of who delegated what to +whom. cA2A adds these guarantees as a profile on A2A rather than as a competing transport. + +\subsection{MCP and cMCP as the Tool-Side Analog} + +The Model Context Protocol (MCP)~\citep{mcp2024} standardizes how an agent discovers and +invokes tools hosted on external servers. Where A2A governs agent-to-agent calls, MCP +governs the agent-to-tool call. MCP, like A2A, defines discovery and invocation but not +authorization: it specifies no policy model, requires no audit record, and gives a client +no way to confirm that a cached tool definition is still the definition the server will +execute. + +cMCP~\citep{cmcp2026} closes those gaps for the tool call. It interposes a gateway that +evaluates a Cedar policy per call, maintains a tamper-evident audit chain, and emits a +signed TRACE Claim that any verifier can check offline. cA2A is the agent-to-agent +counterpart of the same program: it takes the tool-side pattern of attested, policy-bound, +provable calls and applies it to the delegation edge between agents. cA2A reuses cMCP's +TEE provider pattern and runs the same Cedar engine (\texttt{cedarpy}) for local policy. + +\subsection{Capability Attenuation and Macaroons} + +The confused deputy problem~\citep{confuseddeputy1988} arises when a component with +authority is induced to exercise it on behalf of a caller who should not have that +authority. In a multi-agent workflow the deputy is any agent that accepts a narrow task +and then acts with broader authority than the task granted. The capability discipline +answers this by making authority a value that is passed explicitly and can only be +narrowed as it is passed on. + +Macaroons~\citep{macaroons2014} are the reference construction for attenuable bearer +credentials. A macaroon is a token whose holder can append \emph{caveats} that restrict +its use, producing a strictly less powerful token without contacting the issuer; the chain +of caveats is bound by a keyed-HMAC construction so that no caveat can be dropped. The +essential property for delegation is monotone attenuation: a derived credential can only +be a subset of the one it derives from. + +agent-manifest~\citep{agentmanifest2026} implements this discipline as a signed A2A +delegation chain. Each hop is a \texttt{DelegationCredential} whose signed body names the +issuer, the subject, the granted scope, a depth, and the parent hop's identifier. The body +is signed with Ed25519~\citep{eddsa2017} over a canonical JSON encoding aligned with RFC +8785~\citep{jcs2020}, so the exact bytes that were signed are reconstructable by any +verifier. Verification enforces signature validity at every hop, continuity of the +issuer-to-subject and parent-to-child links, a bounded depth, non-repetition of credential +identifiers to defeat replay, and the subset check on scope that enforces attenuation. cA2A +implements the same semantics in its own verifier, which the runtime calls on every inbound +peer request, and signs over RFC 8785 canonical bytes so that credentials are +cross-verifiable with agent-manifest. The attenuation subset check is what forecloses the +confused-deputy escalation. + +\subsection{Trusted Execution Environments and Remote Attestation} + +A Trusted Execution Environment (TEE) is a CPU-enforced isolation boundary that provides +two properties cA2A depends on: \emph{confidentiality}, so that memory inside the TEE is +inaccessible to software outside it including the hypervisor and host OS, and +\emph{remote attestation}, so that the TEE can produce a hardware-signed measurement report +proving to a remote verifier which code is running inside. AMD SEV-SNP~\citep{amdsevsnp2023} +and Intel TDX~\citep{inteltdx2023} are the two mainstream VM-level implementations; a survey +of confidential machine learning provides broader context~\citep{confai2024}. + +The measurement report is the primitive of interest. Both platforms let the enclave place +application-chosen bytes into a report field bound to the hardware measurement, which is how +a public key can be tied to a specific attested measurement. The IETF RATS +architecture~\citep{rats2021} gives the vendor-neutral roles for this exchange, an attester +producing evidence and a verifier appraising it, and the Entity Attestation Token +(EAT)~\citep{eat2023} gives a portable token format for carrying attestation claims. cA2A +binds a peer's channel key to its attested measurement through this mechanism, so that a +caller can seal a payload to a key the appraised evidence vouches for. Whether that key is in +fact confined to the measured code depends on the platform and on what the measurement +covers; Sections~\ref{sec:claims} and~\ref{sec:after} state what has and has not been shown. + +\subsection{Cedar Policy Language} + +Cedar~\citep{cedar2023} is a policy language and evaluation engine that expresses access +control as \texttt{permit} and \texttt{forbid} rules over a principal, action, and +resource, with optional conditions over a context object. Cedar's structural typing keeps +evaluation decidable in bounded time, which matters when a policy check sits on the +critical path of accepting a delegated task. cA2A uses Cedar to model the intersection of a +forwarded delegation scope with a peer's local policy, so that a peer never acts beyond +either the authority delegated to it or its own operator's rules. Property~\ref{prop:c3} +states this intersection and Section~\ref{sec:eval-c3} evaluates it. + +\subsection{Provenance and Transparency} + +An offline-verifiable record of who delegated what to whom is a provenance problem. The +W3C PROV data model~\citep{prov2013} gives the vocabulary for describing entities, +activities, and the agents responsible for them, and the relationships that link a +derived artifact to what it was derived from. cA2A's per-hop records form a delegation DAG +in exactly this shape: each record names its parent, so the set of records across a +multi-hop workflow reconstructs the delegation tree. + +A transparency log makes a registered record non-repudiable and lets anyone check that it was +included; it cannot reveal a record that was never registered. Certificate +Transparency~\citep{ct2013} established the pattern of an append-only, cryptographically +verifiable log for security-critical statements, and the IETF SCITT +architecture~\citep{scitt2024} generalizes it to supply-chain claims, giving neutral roles +for registering and auditing signed statements. cA2A follows the same profile-over-standard +posture toward SCITT that it takes toward A2A: it binds to the transparency architecture +rather than reinventing it. + +\subsection{TRACE as the Record Format} + +TRACE (Trust, Runtime Attestation, and Compliance Evidence)~\citep{trace2025} is the open +record format that cA2A emits per delegation hop. A TRACE record is a signed JSON document +that carries a subject, a runtime measurement, and profile-specific context. cA2A defines +an A2A profile of TRACE that adds a delegation-link block to each record: the hash of the +parent hop's record and the identifier of the delegation credential the hop acted under. A +root hop omits the block; every subsequent hop references its parent. Because each record +is signed and references its parent by hash, a verifier can rebuild the parent links, +confirm each hop acted under a credential whose chain verifies, and confirm that each record +was signed by the key the chain names as that hop's delegate. These checks run offline, given +the recipient's trusted keys and the delegation chain. They establish what the signers +declared, not that the declared work completed or that the submitted path is the complete +history, and the current verifiers accept one ordered root-to-leaf path rather than an +arbitrary branching graph. This is the record substrate for the provenance property in +Section~\ref{sec:claims}. diff --git a/papers/ca2a/sections/03-design.tex b/papers/ca2a/sections/03-design.tex new file mode 100644 index 0000000..49c7ed4 --- /dev/null +++ b/papers/ca2a/sections/03-design.tex @@ -0,0 +1,352 @@ +\section{Design}\label{sec:design} + +\subsection{Profile, not protocol} + +cA2A is a trust profile layered on the Agent2Agent (A2A) protocol \citep{a2a2026}, not a +competing transport. A2A moves tasks between agents and, through the Signed Agent Card, +answers exactly one question: did the domain owner issue this card. It does not establish +that a peer runs attested code, that a delegating agent actually holds the authority it +passes on, that a task payload is confidential to the intended peer, or that there is an +unbroken record of who delegated what to whom. cA2A adds that trust envelope around a +delegated task without redefining how the task is carried. It binds to A2A the way TRACE +\citep{trace2025} binds to the IETF RATS \citep{rats2021}, EAT \citep{eat2023}, and SCITT +\citep{scitt2024} work: as an overlay, not a replacement. The profile is written to be +independent of any one organization, cloud, or TEE vendor; the implementation currently +appraises AMD SEV-SNP, Intel TDX, and TPM~2.0 evidence. + +Four design tenets constrain every primitive below. First, \emph{profile, not protocol}: +reuse A2A and the RATS, EAT, and SCITT stack rather than reinvent them. Second, \emph{neutral by +construction}: no coupling to a single silicon vendor, cloud, or model platform. Third, +\emph{fail closed}: absence of evidence is denial, not a warning; a missing attestation or an +unverifiable chain denies the call. Fourth, \emph{offline verifiable}: a third party can +verify a delegation chain and its provenance DAG without contacting or trusting any operator. + +\subsection{Where cA2A attaches to A2A} + +cA2A interposes on the trust decision that A2A leaves undefined: the moment one agent +receives a delegated task from a peer and must decide whether to accept it. On an inbound +peer call, the caller appraises the callee's attestation offer and seals the payload to the +channel key it vouches for. The callee then, in order, (i) verifies the delegation chain +(trusted root, signature, validity window, continuity, attenuation, in-chain replay, and +revocation when a snapshot is supplied), (ii) checks a holder proof showing that the caller +holds the key the chain delegates to, (iii) intersects the delegated scope with local policy, +(iv) appraises the caller's own attestation when the callee requires it, (v) decides the +requested capability, and (vi) only then opens the sealed payload and emits a linked +provenance record for the allow or deny decision. The A2A transport, agent identity issuance beyond delegation, and model governance +are all out of scope: cA2A governs only the delegation path between agents. + +\subsection{Threat model} + +cA2A defends the delegation path between agents against a capable adversary who may operate a +peer agent in a trust domain cA2A does not control; present a valid A2A Signed Agent Card +while running tampered or unmeasured code; sit on the network between two agents or operate +the host a peer runs on; and attempt to widen a delegated grant, replay a credential into +another chain, or reparent a provenance record. The assets and their defenses are summarized +in Table~\ref{tab:primitives}. + +Out of adversary scope is breaking the underlying cryptographic primitives (Ed25519 +\citep{eddsa2017}, the hash function) or compromising TEE firmware or hardware microcode +\citep{amdsevsnp2023,inteltdx2023}. Those are the trust anchors. The central attack cA2A +addresses is the confused deputy \citep{confuseddeputy1988} at machine speed: a delegate that +accepts a narrow task and then acts with authority it was never granted. + +What ca2a 0.3.1 defends is narrower than this threat model. Attenuated delegation, +scope-policy intersection on the inbound path, the sealed channel's cryptography, and signed +provenance ship and are tested in software (Section~\ref{sec:evaluation}). Attestation +verifiers for SEV-SNP, TDX, and TPM evidence ship, and the live path can require hardware +assurance, but this is opt in: the reference client and server default to software assurance. +Hardware runs so far are one-directional across two clouds or mutual within one operator +(Section~\ref{subsec:attestation}). Confidentiality against a hosting operator additionally +depends on platform properties the recorded hosts lacked (Section~\ref{sec:after}). cA2A +should therefore not be described as attested across independent trust domains, and task +payloads should not be assumed protected from a hosting operator. + +\subsection{Primitive 1: attenuated delegation}\label{subsec:delegation} + +A delegation chain is a root-to-leaf list of signed credentials that lets Agent~A hand a +bounded slice of its authority to~B, and B a still-smaller slice to~C, with each grant +provably within the one above it. The semantics mirror the signed delegation chain +implemented and tested in agent-manifest \citep{agentmanifest2026}, and follow the +capability-attenuation model of macaroons \citep{macaroons2014}; cA2A implements that verifier +and its runtime calls it on every inbound peer request. + +Each hop carries a \texttt{DelegationCredential} with a signed body and a detached Ed25519 +signature. The body has \texttt{credential\_id} (a unique id for the hop), \texttt{issuer} +and \texttt{subject} (the delegator's and delegate's public keys), \texttt{scope} (the set of +capabilities granted at this hop), \texttt{depth} (0 at the root, $+1$ per hop), +and \texttt{parent\_id} (the parent hop's \texttt{credential\_id}, null at the root), with +optional \texttt{not\_before} and \texttt{not\_after} bounds. The signed bytes are the RFC~8785 +\citep{jcs2020} canonical form of the body, with \texttt{scope} as a sorted array; floats are +refused and integers are bounded to the range RFC~8785 can represent exactly. + +The verifier, \texttt{verify\_chain}, raises the specific error for the first invariant that +fails. The root issuer must be in the verifier's trusted set (else +\texttt{UNTRUSTED\_DELEGATION\_ROOT}); every hop's signature must verify against its issuer +(else \texttt{INVALID\_CREDENTIAL}) and its validity window must contain the evaluation time; +the root must have no parent and depth 0, each hop's \texttt{parent\_id} must equal the +previous hop's \texttt{credential\_id}, and each hop's \texttt{issuer} must equal the previous +hop's \texttt{subject}, and each hop's depth must be the previous depth $+1$ (else +\texttt{BROKEN\_DELEGATION\_LINK}); no depth may exceed \texttt{max\_depth} (else +\texttt{DELEGATION\_DEPTH\_EXCEEDED}); +each hop's scope must be a subset of its parent's scope (else \texttt{SCOPE\_ESCALATION}); +and no \texttt{credential\_id} may repeat (else \texttt{CREDENTIAL\_REPLAY}). Given a signed +revocation snapshot, a hop revoked by its issuer or any issuer above it is refused +(\texttt{CREDENTIAL\_REVOKED}). The verifier keeps no global history of used credentials, so +replay of a whole valid chain by a different party is addressed by the holder proof in the +inbound path rather than by \texttt{verify\_chain}. + +Consider the chain +\[ + \text{A} \xrightarrow{\ S_A\ } \text{B} \xrightarrow{\ S_B \subseteq S_A\ } \text{C}, + \qquad S_C \subseteq S_B \subseteq S_A . +\] +A issues B a child credential whose scope $S_B$ is a subset of A's grant $S_A$, signed over +the canonical body. B in turn issues C a credential with $S_C \subseteq S_B$. The subset +check at every hop is the confused-deputy defense within a chain: it forecloses B accepting a narrow task +from A and then acting with authority A never granted. Attenuation is the whole point. +The \texttt{parent\_id}, \texttt{issuer}/\texttt{subject} continuity, and unique-id checks +close the complementary attack of replaying a credential within a chain or splicing one from +another chain into it. They do not bind a chain to a particular task or execution: a credential +carries no task or execution identifier, so the delegate can present the same valid chain +with a different request. Making execution lineage part of the authorization decision is the +subject of history-based access control \citep{abadi2003history}, provenance-based access control +\citep{park2012pbac}, and, for agent delegation, the PIC model \citep{gallo2026pic}. cA2A does not +provide it; authenticated lineage records (Section~\ref{sec:after}) say which hop acted under +which credential, which is not the same as refusing authority outside its lineage. This primitive is implemented and validated (Properties~\ref{prop:c1} +and~\ref{prop:c2}). + +\subsection{Primitive 2: runtime peer attestation}\label{subsec:attestation} + +Before a peer is trusted with a delegated task, it presents evidence of the code it runs. cA2A +follows the TEE provider pattern of cMCP \citep{cmcp2026}. A provider +implements \texttt{detect()}, returning whether it is available on the current host, and +\texttt{attest(public\_key, nonce)}, returning an \texttt{AttestationReport} that binds a +public key to the host's hardware measurement under a nonce. The report carries +\texttt{platform}, \texttt{measurement}, the bound \texttt{public\_key}, and the \texttt{nonce}, +following the RATS evidence model \citep{rats2021} and EAT claims format \citep{eat2023}. + +The providers are a \texttt{software-only} backend (for development and CI, which reports +\texttt{platform:\ software-only} and never a hardware platform string) and three hardware +backends: \texttt{tpm} (TPM 2.0 or vTPM), \texttt{sev-snp} (AMD SEV-SNP \citep{amdsevsnp2023}), +and \texttt{tdx} (Intel TDX \citep{inteltdx2023}). + +\textbf{Implementation status at 0.3.1.} The SEV-SNP and TDX verifiers check certificate chains +to pinned AMD and Intel roots, report signatures, and key binding, and the project records runs +against genuine evidence from both platforms; for this edition the TDX verifier was re-run +against a public GCP TDX quote (Section~\ref{sec:evaluation}). The TPM verifier checks quote +structure, binding, and attestation-key signature; its certificate chain depends on a +caller-supplied vendor root, and on one Azure host generation no chain to a pinnable root could +be assembled. SEV-SNP and TDX collectors use the kernel configfs-TSM interface and have run on +GCP confidential VMs; an Azure SEV-SNP guest is paravisor-mediated, cannot set +\texttt{REPORT\_DATA} directly, and roots its channel key through the vTPM instead. The project +records two live runs. On July 27, 2026 an Azure SEV-SNP caller appraised a GCP TDX peer's quote +against the Intel root, sealed a delegated task to the bound key, and the peer allowed an +in-scope capability and refused an out-of-scope one; the callee did not appraise the caller, and +one operator ran both sides. On September 17, 2026 two GCP SEV-SNP guests in one project +appraised each other and exchanged sealed non-secret payloads in both directions. Neither run is +mutual attestation between independent operators, so Property~\ref{prop:c6} is not validated on +hardware. Verification fails closed when required evidence is absent. In 0.3.1 a required caller +appraisal could be satisfied by an offer relayed from another node (Section~\ref{sec:after}). + +\subsection{Primitive 3: sealed peer channel}\label{subsec:sealed} + +When A sends B a task payload, that payload crosses a network and lands in B's memory. If B is +in another trust domain, mTLS protects the pipe but not the endpoint: the operator hosting B, +or a connectivity provider between them, can read plaintext. The sealed channel encrypts the +payload to B's channel public key, and B's attestation evidence binds that key to a +measurement, extending the attestation-gated key pattern cMCP uses for the agent-to-gateway +case to the peer-to-peer case. The connectivity path sees ciphertext. The interface is +\texttt{SealedChannel(peer\_public\_key).seal} on the sender and \texttt{open\_sealed} with the +private key on the receiver. + +\textbf{Cryptographic layer: implemented and validated.} The channel ships over +X25519 ECDH, HKDF-SHA256 key derivation, and ChaCha20-Poly1305 AEAD. A payload sealed to a +peer's channel key decrypts only with that key, the connectivity path sees only ciphertext, and +a tampered blob fails AEAD authentication so \texttt{open} fails closed rather than returning +plaintext. Property~\ref{prop:c4} validates exactly this at the cryptographic layer +(Section~\ref{sec:evaluation}). +\textbf{Binding to attestation.} On the live path the caller seals only to a channel key that +its verifier has appraised (\texttt{ca2a\_runtime.attestation}). In software mode that appraisal +has assurance \texttt{none}; with a hardware verifier configured and +\texttt{require\_hardware} set, a software offer is refused before sealing. Even with hardware +appraisal, the channel key is an ordinary key held by a process in the guest. In the +September 17 diagnostic the launch measurements did not cover application code installed after +boot, and the SEV-SNP hosts reported that ciphertext hiding was not enabled. Confidentiality +against the hosting operator therefore remains the dependency that Property~\ref{prop:c4} +names. + +Figure~\ref{fig:sealed-channel} shows the seal-and-open path. The sender derives a +one-time symmetric key from an ephemeral X25519 ECDH to the peer's channel public key, +runs it through HKDF-SHA256, and encrypts the payload under ChaCha20-Poly1305. The +connectivity path sees only the sealed blob; only the holder of the peer's channel private key +recovers the plaintext. + +\begin{figure}[h] +\centering +\begin{tikzpicture}[ + font=\small, + node distance=6mm, + cryptobox/.style={draw, rounded corners, minimum height=8mm, align=center, + fill=blue!6, inner sep=3pt}, + enclavebox/.style={draw, rounded corners, minimum height=8mm, align=center, + fill=green!8, inner sep=3pt}, + flow/.style={-{Stealth[length=2mm]}, thick}, +] + % Sender-side derivation pipeline. + \node[cryptobox] (eph) {ephemeral\\X25519}; + \node[cryptobox, right=of eph] (ecdh) {ECDH\\to peer key}; + \node[cryptobox, right=of ecdh] (hkdf) {HKDF-\\SHA256}; + \node[cryptobox, right=of hkdf] (aead) {ChaCha20-\\Poly1305}; + \draw[flow] (eph) -- (ecdh); + \draw[flow] (ecdh) -- (hkdf); + \draw[flow] (hkdf) -- (aead); + + % Untrusted path carrying ciphertext. + \node[draw, dashed, rounded corners, below=10mm of hkdf, minimum width=32mm, + minimum height=7mm, align=center, fill=red!5] (wire) + {untrusted path / host\\(sees ciphertext only)}; + \draw[flow] (aead.south) |- (wire.east) node[midway, above right, font=\scriptsize] + {sealed blob}; + + % Peer enclave that opens the blob. + \node[enclavebox, below=10mm of eph, minimum width=26mm] (peer) + {peer TEE guest\\open with\\private key}; + \draw[flow] (wire.west) -- (wire.west -| peer.east); + \node[below=1mm of peer, font=\scriptsize, align=center] + {key bound to appraised\\evidence (hardware opt-in)}; + + % Grouping label for the sender pipeline. + \begin{scope}[on background layer] + \node[draw=blue!30, rounded corners, fit=(eph)(aead), inner sep=5mm, + label=above:{sender: seal to peer public key}] {}; + \end{scope} +\end{tikzpicture} +\caption{Sealed peer channel. An ephemeral X25519 ECDH to the peer's channel key is run +through HKDF-SHA256 to derive a symmetric key, and the payload is sealed under +ChaCha20-Poly1305 AEAD. The untrusted connectivity path sees only ciphertext; only the +private key matching the appraised channel key opens the blob. Whether the peer's host can +reach that key depends on the platform (Section~\ref{sec:after}).} +\label{fig:sealed-channel} +\end{figure} + +\subsection{Primitive 4: provenance DAG}\label{subsec:provenance} + +cA2A emits a signed TRACE record per delegation hop \citep{trace2025}. The A2A profile adds a +delegation-link block to the base TRACE record, which otherwise carries a subject but no notion +of a parent. The block has \texttt{delegation.parent\_record\_hash}, the hash of the parent +hop's TRACE record, and \texttt{delegation.credential\_id}, the id of the delegation credential +this hop acted under. A root hop omits the block; each subsequent hop references its parent, so +the set of records across A~to~B~to~C reconstructs the delegation tree. The construction follows +the W3C PROV data model \citep{prov2013} for lineage and the SCITT \citep{scitt2024} approach to +transparency over signed statements. + +Given the records for a workflow and the recipient's trusted signing keys, a verifier +(\texttt{verify\_trace\_dag}) checks each record's signature and rebuilds the parent links from +\texttt{parent\_record\_hash}; a cross-check (\texttt{cross\_check\_trace\_dag}) then aligns the +path with a separately verified delegation chain, requiring matching path length and credential +identifiers and that each record was signed by the key the chain names as that hop's delegate. +Tampering any signed record breaks its signature, and altering a parent link breaks the chain of +hashes. The runtime also keeps an unsigned \texttt{DelegationRecord} form whose hash links give +only structural consistency: a party able to rewrite the whole path can recompute every link, +so since ca2a 0.3.0 the CLI reports such a path as structurally consistent but not +authenticated. Property~\ref{prop:c5} and Section~\ref{sec:eval-c5} distinguish the two. Both +verifiers accept one ordered root-to-leaf path. The A2A profile's delegation-link block is +defined in trace-spec. + +Figure~\ref{fig:delegation-dag} shows both halves together: the attenuated delegation chain +on top, where each hop's scope is a subset of its parent's (strictly smaller in this example), and the hash-linked +provenance path below, where each record carries the hash of its parent record and names the +credential the hop acted under. The two are bound: record $i$ must match credential $i$ and, for +signed records, must be signed by that credential's delegate key. + +\begin{figure}[h] +\centering +\begin{tikzpicture}[ + font=\small, + credbox/.style={draw, rounded corners, minimum width=30mm, minimum height=11mm, + align=center, fill=blue!6, inner sep=2pt}, + recbox/.style={draw, rounded corners, minimum width=30mm, minimum height=10mm, + align=center, fill=orange!8, inner sep=2pt}, + deleg/.style={-{Stealth[length=2mm]}, thick}, + hashlink/.style={-{Stealth[length=2mm]}, thick, densely dashed}, + bind/.style={dotted, thick, gray}, +] + % Attenuated delegation chain (top row): scope narrows per hop. + \node[credbox] (a) {A $\to$ B\\scope $\{r,w,x\}$}; + \node[credbox, right=14mm of a] (b) {B $\to$ C\\scope $\{r,w\}$}; + \node[credbox, right=14mm of b] (c) {C $\to$ D\\scope $\{r\}$}; + \draw[deleg] (a) -- node[above, font=\scriptsize] {$\subseteq$} (b); + \draw[deleg] (b) -- node[above, font=\scriptsize] {$\subseteq$} (c); + + % Provenance records (bottom row), hash-linked to parent. + \node[recbox, below=16mm of a] (r0) {record 0\\parent: none}; + \node[recbox, below=16mm of b] (r1) {record 1\\parent hash $h_0$}; + \node[recbox, below=16mm of c] (r2) {record 2\\parent hash $h_1$}; + \draw[hashlink] (r1) -- node[below, font=\scriptsize] {$h_0$} (r0); + \draw[hashlink] (r2) -- node[below, font=\scriptsize] {$h_1$} (r1); + + % Bind record i to credential i. + \draw[bind] (a) -- (r0); + \draw[bind] (b) -- (r1); + \draw[bind] (c) -- (r2); + + \node[right=3mm of c, align=left, font=\scriptsize] {narrowing\\scope}; + \node[left=2mm of r0, align=right, font=\scriptsize] {hash-\\linked\\DAG}; +\end{tikzpicture} +\caption{Attenuated delegation chain (top, solid) and its hash-linked provenance DAG +(bottom, dashed). Each delegation hop's scope is a provable subset ($\subseteq$) of its +parent's; each provenance record stores the SHA-256 hash of its parent record. The dotted +lines bind record $i$ to credential $i$. With signed records and trusted keys, tampering, +reparenting, or attributing a record to the wrong delegate is detected offline; hash links +alone give only structural consistency.} +\label{fig:delegation-dag} +\end{figure} + +\subsection{Scope-policy intersection}\label{subsec:cedar} + +Verifying that a delegated grant is well-formed is not the same as deciding that the local peer +is willing to act on it. On an inbound call, the cA2A runtime is specified to intersect the +delegated scope $S_B$ with the peer's local authorization policy, expressed in Cedar +\citep{cedar2023} or as a simple allow set, and to act only on the intersection +$S_B \cap P_{\text{local}}$. A grant is +thus bounded from above by both the delegator's authority (attenuation) and the delegate's own +policy, so neither a broad grant nor a permissive local default alone can widen what B actually +does. + +\textbf{Implementation status.} The intersection runs against either a Cedar engine +(\texttt{cedarpy} 4.8.7) or the set-based \texttt{LocalPolicy}, inside the inbound decision +path (\texttt{handle\_peer\_request}). That path is served by the reference HTTP transport +(since ca2a commit \texttt{7d33445}, July 2026) and by a bridge to the official \texttt{a2a-sdk} +(commit \texttt{f26e2bc}), which the project tests over a local JSON-RPC loopback with +\texttt{a2a-sdk} 1.1.4. Property~\ref{prop:c3} is evaluated in Section~\ref{sec:eval-c3}. + +\subsection{Primitives to properties} + +Table~\ref{tab:primitives} maps each primitive to the asset it protects, the security property +it supports, and its status at ca2a 0.3.1. The software results need no hardware TEE and can be +rerun from the open-source codebase; the hardware dependencies are stated per row. + +\begin{table}[h] +\centering +\caption{cA2A primitives, the assets they protect, the properties they support, and status. +``Validated'' means backed by the rerun software experiments of Section~\ref{sec:evaluation}, +at the layer stated.} +\label{tab:primitives} +\small +\begin{tabular}{@{}>{\raggedright\arraybackslash}p{3.0cm}>{\raggedright\arraybackslash}p{3.4cm}>{\raggedright\arraybackslash}p{3.4cm}>{\raggedright\arraybackslash}p{3.4cm}@{}} +\toprule +\textbf{Primitive} & \textbf{Asset protected} & \textbf{Property} & \textbf{Status} \\ +\midrule +Attenuated delegation & Bounded authority (no delegate exceeds its grant) & + C1 attenuation soundness; C2 in-chain replay and splice rejection & Validated (software) \\ +Scope-policy intersection & Bounded authority under local policy & + C3 scope-policy intersection & Validated (software), including the reference inbound path \\ +Runtime peer attestation & Peer integrity (only appraised code trusted) & + C6 cross-operator attestation & Synthetic evidence only; hardware runs one-directional or same-operator \\ +Sealed peer channel & Task confidentiality to the intended peer & + C4 sealed-payload confidentiality & Validated (cryptographic layer); key confinement not shown \\ +Provenance record & Provenance (signed record of who delegated what) & + C5 provenance integrity & Structural checks validated; signed path covered by project tests \\ +\bottomrule +\end{tabular} +\end{table} diff --git a/papers/ca2a/sections/04-claims.tex b/papers/ca2a/sections/04-claims.tex new file mode 100644 index 0000000..e929634 --- /dev/null +++ b/papers/ca2a/sections/04-claims.tex @@ -0,0 +1,103 @@ +\section{Security Properties}\label{sec:claims} + +We state six security properties of the cA2A profile. Each is an informal invariant over the +runtime; formal proofs are left to future work. Each carries a status line for ca2a 0.3.1 that +names the layer at which it has been checked and what that check does not cover. This section +states the guarantees and mechanisms; results appear in Section~\ref{sec:evaluation}. + +\begin{property}[Attenuation Soundness] +\label{prop:c1} +\emph{Status: validated in software.} For any delegation chain of signed credentials rooted at a +trusted issuer, the scope granted at each hop is a subset of the scope of its parent, and any +attempt to grant a capability the parent does not hold is rejected. This is the confused-deputy +defense \citep{confuseddeputy1988} within a chain: without it, a delegate could accept a narrow task and then +act with authority the delegator never conferred. The mechanism is a per-hop subset check in +\texttt{verify\_chain}: each credential carries a \texttt{scope} set, a \texttt{depth}, a +\texttt{parent\_id}, and a detached Ed25519 signature \citep{eddsa2017} over the canonical body. +A hop whose \texttt{scope} is not contained in its parent's \texttt{scope} raises +\texttt{ScopeEscalation}; a monotone-narrowing chain verifies. The semantics mirror +agent-manifest \citep{agentmanifest2026} and follow the capability-attenuation model of +macaroons \citep{macaroons2014}. +\end{property} + +\begin{property}[In-Chain Replay and Splice Rejection] +\label{prop:c2} +\emph{Status: validated in software.} A credential presented twice within a chain, or lifted +from one chain and spliced into another, is rejected. This forecloses splicing a legitimately +issued grant into an unrelated delegation to inherit authority out of context. The mechanism +combines two checks in \texttt{verify\_chain}. First, each hop's \texttt{credential\_id} must be +unique within the chain; a repeat raises \texttt{CredentialReplay}. Second, structural +continuity binds each hop to the previous one: a hop's \texttt{parent\_id} must equal the +previous hop's \texttt{credential\_id} and its \texttt{issuer} must equal the previous hop's +\texttt{subject}. A credential spliced from a foreign chain breaks one of these links and raises +\texttt{BrokenDelegationLink}. The property does not cover presentation of a whole valid chain +by a party other than its delegate, since the verifier keeps no global history; on the live path +that case is refused by the holder proof (Section~\ref{sec:after}). Nor does it cover the delegate +itself presenting a valid chain with a request from a different task, since nothing binds a +chain to an execution (Section~\ref{sec:design}). +\end{property} + +\begin{property}[Scope-Policy Intersection] +\label{prop:c3} +\emph{Status: validated in software, including the inbound path of the reference transport.} +The effective permission at an inbound peer call is the delegated scope intersected with the +receiving agent's local policy \citep{cedar2023}: a capability is exercised only if the upstream +chain granted it \emph{and} the local policy independently permits it. This makes each domain the +final authority over what runs on its own resources, so an over-broad delegated grant cannot force +action the receiver's policy forbids. The mechanism is the intersection of the verified chain +scope (Property~\ref{prop:c1}) with the local policy decision, evaluated in +\texttt{effective\_scope} and \texttt{enforce\_peer\_call} in \texttt{ca2a\_runtime.peer}; a +capability outside the intersection raises \texttt{ScopeNotPermitted}. Local policy is either a +Cedar policy set evaluated by \texttt{cedarpy} or a set-based \texttt{LocalPolicy}. The same +decision runs inside \texttt{handle\_peer\_request}, which the reference HTTP transport and the +\texttt{a2a-sdk} bridge call on each inbound request. The check does not cover third-party A2A +servers or a production deployment. +\end{property} + +\begin{property}[Sealed-Payload Confidentiality] +\label{prop:c4} +\emph{Status: validated at the cryptographic layer.} A task payload sent across a trust boundary +decrypts only with the private key matching the channel key the caller appraised, so no party on +the connectivity path can read plaintext. The mechanism seals the payload to the peer's channel +public key with an HPKE-style X25519, HKDF-SHA256, and ChaCha20-Poly1305 construction +(\texttt{ca2a\_runtime.channel}); only the peer's private key opens it, an unrelated key fails, +and a tampered ciphertext fails closed under AEAD authentication. What software cannot establish +is that the private key is confined to the attested code, so that the operator hosting the peer +cannot read it. That depends on hardware appraisal being required on the call, on the +measurement covering the code that holds the key, and on platform protections such as SEV-SNP +ciphertext hiding. Section~\ref{sec:after} records that the measured hosts did not meet the last +two conditions. Until they are met, confidential payloads should not be sent across a trust +boundary in the belief that a compromised host cannot read them. +\end{property} + +\begin{property}[Provenance Integrity] +\label{prop:c5} +\emph{Status: structural checks validated in software; authenticated lineage covered by the +project's tests.} The per-hop records for a workflow form a linked path that is bound to the +delegation authority. With signed TRACE records and trusted signing keys, tampering with any +record, reparenting a record, or attributing a record to a hop that did not sign it is detected +offline \citep{prov2013}. The mechanism adds a delegation-link block to each record: +\texttt{parent\_record\_hash} links a hop to its parent, and \texttt{credential\_id} names the +credential the hop acted under. Altering a record changes its hash and breaks the child's +\texttt{parent\_record\_hash} link, raising \texttt{ProvenanceLinkBroken}. A cross-check binds the +provenance path to the delegation chain of Property~\ref{prop:c1}: record $i$ must correspond to +credential $i$, and a signed record must carry the delegate key that credential names. Hash links +without signatures detect only partial edits: a writer who rewrites the whole path can recompute +every link. The property does not establish that declared work completed or that the submitted +path is complete. +\end{property} + +\begin{property}[Cross-Operator Attestation] +\label{prop:c6} +\emph{Status: exercised with synthetic evidence; not validated on hardware across independent +operators.} Two agents in independent trust domains, holding independently generated keys, each +appraise the other's runtime attestation before a delegated task is accepted, and a peer that +loads a different binary than the one expected is detected. The mechanism follows the IETF RATS +architecture \citep{rats2021}: each peer produces a hardware-signed attestation report binding its +channel key and a nonce, and the counterpart verifies the report's certificate chain and +signature and compares its measurement with an expected value, so a binary swap changes the +measurement and is rejected. The SEV-SNP, TDX, and TPM verifiers that this requires ship in ca2a +0.3.1 (Section~\ref{subsec:attestation}). The recorded hardware runs are one-directional across +two clouds, or mutual within one operator's project; neither is mutual attestation between +independent operators. +\end{property} diff --git a/papers/ca2a/sections/05-evaluation.tex b/papers/ca2a/sections/05-evaluation.tex new file mode 100644 index 0000000..6f05365 --- /dev/null +++ b/papers/ca2a/sections/05-evaluation.tex @@ -0,0 +1,248 @@ +\section{Evaluation}\label{sec:evaluation} + +We evaluate the six properties of Section~\ref{sec:claims} against ca2a 0.3.1, then the current +release (commit \texttt{7d90823}, September 26, 2026); 0.4.0 followed on October 1, 2026 +(Section~\ref{sec:after}). Every experiment in this section was rerun +on September 27, 2026. The original July 2026 results came from an earlier, unpinned checkout; +where numbers changed, both are reported. + +\subsection{Environment and reproducibility} + +The experiments ran under CPython 3.12.10 (an x86-64 build running under emulation on a Windows +11 laptop with an ARM64 Snapdragon X1E80100 processor), with dependencies from the project's +runtime lock at that commit: \texttt{cryptography} 50.0.1, \texttt{cedarpy} 4.8.7, +\texttt{agent-manifest} 0.12.0, \texttt{agentrust-trace} 0.10.0, and \texttt{rfc8785} 0.1.4. The +one deviation from the lock is \texttt{cbor2} 5.9.0 in place of 6.1.4, because the host's +application-control policy blocks the 6.x native extension. Timings are single runs under +emulation and vary from run to run; they show how cost scales, not what a server would measure. +No hardware TEE is needed. The rerun record, with every command's output, is committed with the +paper as \texttt{experiments/results/rerun-2026-09-27.txt}. + +Two sets of experiments are used. The repository's own per-claim experiments live under +\texttt{experiments/} in ca2a~\citep{ca2arepo2026}; the three scale and cost experiments below +ship with this report and import the runtime from a ca2a checkout named by \texttt{CA2A\_ROOT}: + +\begin{lstlisting}[language=bash,caption={Rerunning the experiments}] +# in a ca2a checkout at tag v0.3.1 +pip install -e ".[dev]" +python experiments/claim1-attenuation-soundness/run.py # C1 .. claim6 likewise +# in this report's directory +CA2A_ROOT=/path/to/ca2a python experiments/c1_attenuation_scale.py +CA2A_ROOT=/path/to/ca2a python experiments/verify_latency_vs_depth.py +CA2A_ROOT=/path/to/ca2a python experiments/sealed_channel_throughput.py +\end{lstlisting} + +At 0.3.1, \texttt{verify\_chain} refuses a chain whose root issuer the caller has not named as +trusted (ca2a commit \texttt{23c980a}), so the three report scripts now pass each chain's root as +the trusted set; no other logic changed. The project's test suite at the same commit produced 779 +passes and 11 skips: nine skips need a git checkout rather than the source archive used here, and +two need hardware captures. Setting \texttt{CA2A\_TDX\_QUOTE} to a genuine GCP Intel TDX quote +published in the agent-manifest repository ran one of those two: the TDX verifier appraised it to +the pinned Intel SGX Root CA, and all 17 TDX tests passed. That is offline appraisal of one +captured quote, not a live attested call. Table~\ref{tab:ca2a-results} summarizes the outcomes. + +\begin{table}[h] +\centering +\caption{Outcomes at ca2a 0.3.1, rerun September 27, 2026. The Status column names the layer at +which each property was checked.} +\label{tab:ca2a-results} +\small +\begin{tabular}{>{\raggedright\arraybackslash}p{3.3cm}>{\raggedright\arraybackslash}p{2.6cm}>{\raggedright\arraybackslash}p{8.2cm}} +\toprule +Property & Status & Result \\ +\midrule +C1 Attenuation soundness & Validated (software) & 200/200 narrowing chains accepted and 200/200 escalations rejected with \texttt{ScopeEscalation}; across depths 2 to 8, 2500/2500 accepted and 2500/2500 rejected \\ +C2 In-chain replay and splice & Validated (software) & Duplicate \texttt{credential\_id} raises \texttt{CredentialReplay}; cross-chain splice raises \texttt{BrokenDelegationLink}; 2/2 controls valid \\ +C3 Scope-policy intersection & Validated (software) & Effective scope is delegated $\cap$ local policy; 1/1 allowed, 3/3 denied; the inbound transport tests exercise the same decision \\ +C4 Sealed-payload confidentiality & Cryptographic layer & 4/4: only the peer's key opens the payload; the path sees ciphertext; wrong key and tamper fail closed. Key confinement to attested code not shown \\ +C5 Provenance integrity & Structural (experiment) & Tampering one record changed 119 and 138 of 256 hash bits in two runs and raised \texttt{ProvenanceLinkBroken}; reparenting detected; record $i$ bound to credential $i$ \\ +C6 Cross-operator attestation & Synthetic evidence & 4/4 with synthetic SEV-SNP vectors: independent keys, mutual appraisal, sealed delegation, binary swap rejected. No hardware run across independent operators \\ +\bottomrule +\end{tabular} +\end{table} + +\subsection{C1: Attenuation Soundness} +\label{sec:eval-c1} + +\textbf{Setup.} Property~\ref{prop:c1} states that a child grant can never exceed its parent. +The repository experiment generates 200 independent four-hop delegation chains. In the narrowing +case, each hop drops exactly one capability from a six-capability root, so every hop is a strict +subset of the one above it. In the escalating case, the third hop adds a fresh capability that no +ancestor held. Each chain is signed hop by hop, so issuer continuity, depth, and +\texttt{parent\_id} linkage hold; only the scope relationship differs. + +\textbf{Results.} All 200 narrowing chains are accepted, and all 200 escalating chains are +rejected with \texttt{ScopeEscalation}; none is rejected by an unrelated error. A correctly +signed credential that widens its scope is still rejected, so attenuation is enforced +independently of signature validity. + +\textbf{Across chain depth.} For each depth in $\{2,3,4,6,8\}$ we generated 500 narrowing chains +and 500 escalation chains (each escalation injects a never-held capability at an interior hop) +over a 16-capability root universe, 2500 of each in total. Every narrowing chain was accepted and +every escalation was rejected with \texttt{ScopeEscalation}, with zero rejections by an unrelated +error, matching the July result. The per-depth counts are in +\texttt{experiments/results/c1\_attenuation\_scale.json}. + +\subsection{Verification Latency vs Chain Depth} +\label{sec:eval-latency} + +\textbf{Setup.} Chain verification is the per-call cost a callee pays before it acts: trusted +root, signature, validity, continuity, attenuation, and in-chain replay over every hop. We built +one correctly signed chain at each depth in $\{1,2,4,8,16,32,64\}$, ran 200 warm-up iterations, +then timed \texttt{verify\_chain} over 2000 iterations per depth, reporting mean, p50, and p99. +Chains are signed once up front, so the measurement excludes key generation. + +\textbf{Results.} Cost grows linearly with depth, at about 216 microseconds per hop between depth +1 and depth 64 (July: about 195). A single hop verifies in 0.25\,ms mean; a 64-hop chain verifies +in 13.87\,ms mean with p99 26.08\,ms (July: 0.14\,ms, 12.45\,ms, and 22.15\,ms). +Table~\ref{tab:rerun-compare} lists both runs, and Figure~\ref{fig:latency-depth} plots the rerun. +The two runs differ in code version (the verifier gained trusted-root and validity-window checks +after July) and in environment, so the difference is not attributed to either alone. At realistic depths of a few +hops the cost stays near a millisecond or below. Raw values are in +\texttt{experiments/results/verify\_latency\_vs\_depth.json}. + +\begin{table}[h] +\centering +\caption{Original (July 1, 2026, unpinned checkout) and rerun (September 27, 2026, ca2a 0.3.1) +timings. Both are single runs on developer hardware.} +\label{tab:rerun-compare} +\small +\begin{tabular}{lrr} +\toprule +Measurement & July 2026 & Rerun \\ +\midrule +\texttt{verify\_chain}, depth 1, mean (ms) & 0.14 & 0.25 \\ +\texttt{verify\_chain}, depth 8, mean (ms) & 1.73 & 2.07 \\ +\texttt{verify\_chain}, depth 64, mean / p99 (ms) & 12.45 / 22.15 & 13.87 / 26.08 \\ +Slope, depth 1 to 64 ($\mu$s per hop) & 195 & 216 \\ +Seal and open, 64 bytes, round trip (ms) & 0.21 & 0.17 \\ +Seal and open, 256\,KiB, throughput (MiB/s) & 266 & 212 \\ +\bottomrule +\end{tabular} +\end{table} + +\begin{figure}[h] +\centering +\includegraphics[width=0.62\linewidth]{figures/verify_latency_vs_depth.pdf} +\caption{Chain-verification latency (mean and p99) versus delegation-chain depth, log-scale +x-axis, 2000 timed iterations per depth, ca2a 0.3.1. Cost is linear in depth at about 216 +microseconds per hop; a 64-hop chain verifies in 13.87\,ms mean.} +\label{fig:latency-depth} +\end{figure} + +\subsection{C2: In-Chain Replay and Splice Rejection} +\label{sec:eval-c2} + +\textbf{Setup.} Property~\ref{prop:c2} states that a credential replayed within a chain or +spliced from another chain is rejected. We build two independent, well-formed chains, A and B, +each with its own key material and credential-id namespace. Three cases are checked: (1) a +control in which both chains verify unmodified; (2) an intra-chain replay in which one hop's +\texttt{credential\_id} is duplicated inside the chain; and (3) a cross-chain splice in which a +validly signed credential minted for chain A is inserted into chain B. + +\textbf{Results.} Both control chains verify (2/2). The duplicated \texttt{credential\_id} raises +\texttt{CredentialReplay}. The spliced credential is rejected with \texttt{BrokenDelegationLink} +because its \texttt{parent\_id} does not match chain B's preceding credential. A valid signature is +therefore necessary but not sufficient; continuity is enforced over the chain, not per +credential. Replay of a whole valid chain by a different party is outside this experiment; the +project covers it with holder-proof tests. + +\subsection{C3: Scope-Policy Intersection} +\label{sec:eval-c3} + +\textbf{Setup.} Property~\ref{prop:c3} states that the capability a caller may exercise against a +peer is the intersection of its verified delegated scope with the callee's local policy. We build +a two-hop chain delegating a leaf scope of \{\texttt{read}, \texttt{write}\} (the root also held +\texttt{admin}) and set the callee's local policy to allow \{\texttt{read}, \texttt{audit}\}. We +then request four capabilities through \texttt{effective\_scope} and \texttt{enforce\_peer\_call}: +\texttt{read} (delegated and allowed), \texttt{write} (delegated but not locally allowed), +\texttt{audit} (locally allowed but never delegated), and \texttt{admin} (neither). + +\textbf{Results.} The effective scope is exactly \{\texttt{read}\}. Only \texttt{read} is allowed +(1/1); \texttt{write}, \texttt{audit}, and \texttt{admin} are each denied with +\texttt{ScopeNotPermitted} (3/3). A capability is exercised only when the chain granted it +\emph{and} the local policy permits it. The experiment uses the set-based \texttt{LocalPolicy}; +the project's unit tests exercise the Cedar engine, and its live-call and \texttt{a2a-sdk} +loopback tests, which passed in the rerun, drive the same decision through an inbound HTTP and +JSON-RPC request on the local host, including a denial for an out-of-scope capability. None of +these is a deployment across a real network boundary. + +\subsection{C4: Sealed-Payload Confidentiality} +\label{sec:eval-c4} + +\textbf{Setup.} Property~\ref{prop:c4} states that a sealed task payload decrypts only with the +private key matching the peer's appraised channel key. The peer generates a channel keypair; we +seal a secret payload to its public key with \texttt{ca2a\_runtime.channel} and check four +sub-properties: (1) the sealed blob does not contain the plaintext; (2) the peer's private key +recovers it; (3) an unrelated key cannot open it; and (4) a tampered ciphertext fails closed +under AEAD authentication. + +\textbf{Results.} All four checks pass (4/4). A different key and a flipped byte both raise +\texttt{SealedChannelError} rather than returning corrupted plaintext. This validates payload +confidentiality at the cryptographic layer. It says nothing about whether the peer's host can read +the private key; that is the hardware dependency Property~\ref{prop:c4} names, and +Section~\ref{sec:after} reports platform state that bears on it. + +\textbf{Throughput and latency.} We measured the sealed channel across payload sizes from a +64-byte task envelope to a 256\,KiB document, timing \texttt{seal} (ephemeral key generation, +ECDH, HKDF, AEAD encryption) and \texttt{open} (ECDH, HKDF, AEAD decryption) over 2000 iterations +each after warm-up, with a round-trip check confirming every opened payload equalled the +original. A 64-byte envelope seals and opens in 0.17\,ms round trip, a cost dominated by the fixed +X25519 and HKDF setup. As payloads grow the setup amortizes and the AEAD dominates: end-to-end +throughput rises from 0.37\,MiB/s at 64 bytes to 212\,MiB/s at 256\,KiB (July: 0.21\,ms and +266\,MiB/s). Figure~\ref{fig:sealed-throughput} plots the rerun. Raw measurements are in +\texttt{experiments/results/sealed\_channel\_throughput.json}. + +\begin{figure}[h] +\centering +\includegraphics[width=0.64\linewidth]{figures/sealed_channel_throughput.pdf} +\caption{Sealed-channel round-trip latency (left axis) and end-to-end throughput (right axis) +versus payload size, log-scale x-axis, 2000 timed \texttt{seal} and \texttt{open} iterations per +size, ca2a 0.3.1. Small payloads are setup-dominated (0.17\,ms round trip at 64 bytes); large +payloads are AEAD-dominated, reaching 212\,MiB/s at 256\,KiB.} +\label{fig:sealed-throughput} +\end{figure} + +\subsection{C5: Provenance Integrity} +\label{sec:eval-c5} + +\textbf{Setup.} From a signed three-hop chain the repository experiment emits one unsigned +\texttt{DelegationRecord} per hop, each carrying the SHA-256 \texttt{record\_hash} of its parent. +It checks four sub-properties: (1) a valid record set verifies with \texttt{verify\_dag}; (2) +changing a record's scope changes its \texttt{record\_hash} and breaks its child's parent link; +(3) pointing a record at the wrong ancestor is detected; and (4) \texttt{cross\_check\_chain} +binds record $i$ to credential $i$. + +\textbf{Results.} The valid record set verifies. Injecting one capability into a record's scope +changed 119 and 138 of the 256 bits of its \texttt{record\_hash} in two runs, as expected of +SHA-256~\citep{avalanche1985}, and \texttt{verify\_dag} raised \texttt{ProvenanceLinkBroken} +because the child's stored parent hash no longer matched. Reparenting the leaf record was likewise +detected, and \texttt{cross\_check\_chain} raised \texttt{ProvenanceLinkBroken} when a record's +\texttt{credential\_id} was forged. + +\textbf{What this shows.} These records are unsigned, so the experiment shows structural +consistency against a single edit, not authenticity: a writer who rewrites every record can +recompute every link and pass all four checks, and an edit to the leaf has no child link to +break. Authenticity comes from the signed TRACE path (\texttt{verify\_trace\_dag} and +\texttt{cross\_check\_trace\_dag}), whose unit tests, including those binding each record's +signing key to the chain's delegate, passed in the rerun. That path is not measured by a separate +experiment in this report. + +\subsection{C6: Cross-Operator Attestation} +\label{sec:eval-c6} + +\textbf{Setup.} The repository experiment builds two operators, each with its own channel keypair +and its own synthetic VCEK key, and binds each channel key into a synthetic SEV-SNP report. Both +synthetic VCEK certificates chain to one synthetic root standing in for the AMD ARK. Each side +verifies the other's report chain and signature with the shipped verifier, checks the +measurement against an expected value, seals a delegated task to the key the report binds, and +finally presents a validly signed report with a different measurement. + +\textbf{Results.} All four checks pass (4/4): the keys are independent, each report's channel key +is recovered after verification, the sealed task opens only with the peer's key, and the report +with the swapped measurement is rejected with \texttt{AttestationFailed}. This exercises the +protocol logic and the verifier code. It does not show that genuine hardware produces such +reports for this protocol between independent operators. The recorded hardware runs +(Section~\ref{subsec:attestation}) were one-directional across two clouds or mutual within one +operator, their raw evidence is not published, and they were not repeated for this edition. C6 +therefore remains unvalidated on hardware. diff --git a/papers/ca2a/sections/05b-after.tex b/papers/ca2a/sections/05b-after.tex new file mode 100644 index 0000000..c879518 --- /dev/null +++ b/papers/ca2a/sections/05b-after.tex @@ -0,0 +1,64 @@ +\section{Findings after the evaluation}\label{sec:after} + +The July 2026 draft was evaluated against an early checkout. Later work in the ca2a repository +found gaps in that code and in the draft's claims. They are listed here with the commit that +addressed each and the first release containing it, so that the July results can be read against +them. All are recorded in the project's changelog. + +\textbf{A delegation chain was a bearer credential.} The inbound path verified a chain and then +granted its leaf authority without asking the caller to show any relationship to the leaf +subject, so anyone holding a copy of a published chain could exercise it, and the provenance +record would name the legitimate delegate. Property~\ref{prop:c2} as tested does not catch this, +because nothing inside the chain changes. Since commit \texttt{cbfeb40} (0.2.0) the callee issues +a challenge and requires a signature under the leaf subject's key over the request, the channel +key, and the payload digest. + +\textbf{Untrusted roots were accepted.} A chain that was internally consistent verified whoever +minted its root, so an attacker could grant itself any locally allowed capability (GHSA-95qh-qjph-8622). The runtime +required a trusted root set from commit \texttt{e647e90} (0.2.0), and \texttt{verify\_chain} +itself from commit \texttt{23c980a} (0.3.0). The July C1 and C2 results were produced without a +trust set; the rerun supplies one. + +\textbf{Unsigned provenance did not authenticate lineage.} The July draft described the C5 records +as verifiable ``from signed records alone'', but the experiment used unsigned records whose hash +links a writer can recompute. Since commit \texttt{550fba9} (0.3.0) the CLI separates +authenticated lineage over signed TRACE records from structural diagnostics, and commit +\texttt{e8b5cb7} (0.3.0) requires each signed record's key to match the delegate named by the +chain, which the cross-check had not compared. + +\textbf{Grants could not be revoked early.} Until commit \texttt{f018c0e} (0.3.0) a credential +stayed valid for its whole window. Issuers can now sign revocation statements that a verifier +consults through a supplied snapshot. cA2A does not publish or fetch revocation data, and a +verifier without a snapshot reports revocation as not checked. + +\textbf{The TDX parser rejected every genuine quote.} Real DCAP v4 quotes nest the quoting-enclave +data under a type-6 header that the parser and its synthetic fixtures both omitted, so the TDX +path had never accepted real evidence. The failure was closed rather than an unsound accept. It +was fixed in commit \texttt{82be440} (0.2.0) when a genuine quote was first appraised, which is +why this edition re-appraised a public genuine quote rather than relying on synthetic vectors. + +\textbf{Measured hosts lacked platform protections.} The GCP SEV-SNP hosts used on August 24 and +September 17, 2026 reported \texttt{PLATFORM\_INFO} 0x25: SMT enabled and ciphertext hiding not +enabled. Stricter platform policies correctly refused them. cA2A can enforce such a policy, but +appraisal of platform state is opt in and off by default. In the September 17 diagnostic the +launch measurement also did not cover application code installed after boot. Both bear directly +on Property~\ref{prop:c4}: a hardware-appraised channel key on such a host is not shown to be out +of the host operator's reach. + +\textbf{Malformed input escaped as exceptions.} Before commit \texttt{66ad9f0} (0.3.1) some +malformed wire documents, TDX quotes, and MAC strings raised Python exceptions instead of the +documented error, and the canonicalizer coerced non-string keys so that two distinct objects could +serialize alike. Each now fails with a documented error. + +\textbf{Caller attestation could be relayed.} Through 0.3.1 a callee that required hardware +caller attestation appraised the caller's offer but never asked the caller to prove it held the +offered channel key, and any node attested its own key under whatever nonce reached its public +channel endpoint. A delegate with no TEE could fetch an honest hardware peer's offer under the +callee's challenge, present it as its own, and be recorded with hardware caller attestation +(GHSA-xxj2-p57x-x954, affecting 0.2.0 to 0.3.1). Since commit \texttt{19407fb} (0.4.0) caller and +callee offers are signed under separate role prefixes, and the caller sends a MAC keyed from +X25519 between its attested channel key and the callee's. The September 17 diagnostic predates +this fix; it ran honest nodes under one operator, so no relay was attempted, but its caller +appraisal did not include proof of possession. The software experiments of +Section~\ref{sec:evaluation} do not send caller offers, and the six repository experiments +produce the same results at 0.4.0. diff --git a/papers/ca2a/sections/06-related.tex b/papers/ca2a/sections/06-related.tex new file mode 100644 index 0000000..a9f346d --- /dev/null +++ b/papers/ca2a/sections/06-related.tex @@ -0,0 +1,66 @@ +\section{Related Work}\label{sec:related} + +cA2A draws on three lines of work: capability-based delegation, identity and transparency +standards, and confidential computing for agents. Several of its mechanisms appear in prior or +concurrent work, and we do not claim priority for attenuation or for binding provenance to +authority. What we did not find in the work below is the combination of per-hop attenuation with +appraisal of the delegate's TEE evidence and sealing of the task payload to the key that +evidence vouches for, on the A2A delegation edge. + +\paragraph{Capability delegation for agents.} +Macaroons~\citep{macaroons2014} introduced contextual caveats that attenuate a bearer credential +without a round trip to the issuer; a derived credential can only be narrower, and the check is +offline. Toma\v{s}ev, Franklin, and Osindero propose delegation capability tokens for AI agents based +on macaroons or Biscuit tokens, wrapping resource credentials in attenuating +caveats~\citep{tomasev2026}. Prakash's Agent Identity Protocol (AIP) implements invocation-bound +capability tokens that combine identity, attenuated authorization, and provenance in an +append-only token chain, with Biscuit and Datalog policies for multi-hop delegation, bindings for +MCP, A2A, and HTTP, and interoperable Python and Rust implementations~\citep{aip2026}. The PEDIGREE +Internet-Draft, an individual submission from Glyphzero Labs that extends the SPIFFE and WIMSE +workload-identity model, specifies per-hop delegation with scope attenuation enforced at mint and +at verification, Cedar-policy mandates with static-analysis proofs that a child mandate narrows +its parent, and cryptographic re-verification of each parent token to resist parent-swap +attacks~\citep{pedigree2026}. These cover the attenuation and chain-integrity ground of +Properties~\ref{prop:c1} and~\ref{prop:c2}, and PEDIGREE's narrowing proofs over Cedar policies go +further than cA2A's set-subset check. Neither AIP nor PEDIGREE addresses appraisal of the +delegate's runtime attestation or encryption of the task payload to the delegate. cA2A's +delegation semantics mirror agent-manifest~\citep{agentmanifest2026}. + +\paragraph{Identity and transparency standards.} +The IETF WIMSE architecture draft~\citep{wimse2025} describes how workloads present and verify +identity across systems, and the SCITT architecture, published as RFC~9943 in June +2026~\citep{scitt2024}, defines transparency services for signed supply-chain statements. The same +author as PEDIGREE has proposed binding per-action authorization and memory provenance into SCITT +agent action capsules~\citep{capsule2026}. cA2A's per-hop records follow the SCITT pattern of +signed statements that a verifier can appraise offline, and the TRACE record format it +emits~\citep{trace2025} has a transparency field that can carry a SCITT receipt; cA2A does not +itself operate a transparency service. From the adversary side, Anbiaee et al.\ compare the security of +MCP, A2A, Agora, and ANP and identify protocol-level and cross-protocol risks, including missing +validation and attestation of executable components~\citep{crossprotocol2026}. + +\paragraph{Agent identity and messaging infrastructure.} +AGNTCY, initiated by Cisco and hosted by the Linux Foundation since July 2025, provides agent +discovery, identity, and messaging components for multi-agent systems~\citep{agntcy2026}. It +addresses naming, discovery, and authenticated communication at a scale cA2A does not attempt. We +did not find in its published components a mechanism that intersects a delegated scope with the +receiver's local policy or seals a payload to an appraised TEE key. The two could compose: such a +layer can name the peers while cA2A governs the authority that flows between them. + +\paragraph{Hardware-attested provenance.} +EQTY Lab's Verifiable Compute uses TEE attestation to notarize AI computation, pairing Intel TDX +CPUs with NVIDIA H100 GPUs in its published design~\citep{eqtyvc2024}, and its Verifiable +Knowledge work extends that approach to the provenance of data an agent uses, on NVIDIA BlueField +hardware~\citep{eqty2026}. These are commercial systems built around particular hardware pairings. +cA2A's verifier follows the RATS role separation~\citep{rats2021} and appraises SEV-SNP, TDX, and +TPM evidence through one interface, but it has not been validated across independent operators +(Property~\ref{prop:c6}). + +\paragraph{Confidential agentic AI.} +Forough, Kogias, and Haddadi survey confidential computing for agentic AI across six TEE platforms +and list open challenges, among them compound attestation for multi-hop agent +chains~\citep{secretssurvey2026}; Mo, Tarkhani, and Haddadi systematize confidential computing for +machine learning more broadly~\citep{confai2024}. cA2A's sealed channel +(Property~\ref{prop:c4}) targets the delegation hop, and Section~\ref{sec:after} records why it is +not yet a confidentiality guarantee against a hosting operator. The sibling cMCP +profile~\citep{cmcp2026} applies confidential computing to the single-agent tool-call boundary; +cA2A applies the same TEE provider pattern and Cedar engine to the agent-to-agent boundary. diff --git a/papers/ca2a/sections/07-conclusion.tex b/papers/ca2a/sections/07-conclusion.tex new file mode 100644 index 0000000..0848d26 --- /dev/null +++ b/papers/ca2a/sections/07-conclusion.tex @@ -0,0 +1,30 @@ +\section{Conclusion}\label{sec:conclusion} + +A2A standardizes how agents discover each other and exchange tasks, but its trust model stops at +the Signed Agent Card. Card verification establishes that a domain owner issued a card. It does +not establish that the peer runs attested code, that a delegating agent holds the authority it +passes on, that the task payload is confidential to the peer, or that there is a record of who +delegated what to whom. cA2A is a trust profile layered on A2A that addresses these with +attenuated delegation, attestation-gated sealing, and signed per-hop provenance, reusing existing +standards rather than defining a new transport~\citep{a2a2026,trace2025}. + +Rerun against ca2a 0.3.1, the software results hold. Attenuation was exact across 2,700 +narrowing and 2,700 escalating chains; in-chain replay and cross-chain splices were rejected; +delegated scope was intersected with local policy, including on the reference inbound path; +sealed payloads opened only with the peer's key and failed closed on tamper; and edits to linked +provenance records were detected. Chain verification cost about 0.22\,ms per hop in our +environment, and the sealed channel reached 212\,MiB/s at 256\,KiB. + +The limits are as important as the results. The provenance experiment shows structural +consistency; authenticity depends on signed records, which the project's tests cover. The sealed +channel is validated at the cryptographic layer only: the measured hosts lacked SEV-SNP ciphertext +hiding, and the launch measurement in the recorded mutual run did not cover application code. +Cross-operator attestation has been exercised with synthetic evidence and, on hardware, only +one-directionally or within one operator. Section~\ref{sec:after} lists defects found after the +original evaluation, including chains that acted as bearer credentials and unsigned provenance +presented as authenticated; each was fixed in a released version. + +Until mutual attestation between independent operators is demonstrated on platforms that provide +the needed memory protections, cA2A should not be described as attested across trust domains or as +protecting payloads from a hosting operator. The specification, verifier, and experiments are +available at \url{https://github.com/agentrust-io/ca2a} under the MIT license. diff --git a/papers/confidential-handoffs/LICENSE.md b/papers/confidential-handoffs/LICENSE.md new file mode 100644 index 0000000..f9c0d63 --- /dev/null +++ b/papers/confidential-handoffs/LICENSE.md @@ -0,0 +1,13 @@ +# Licenses + +- `paper.md`, `experiments/` text and data, `build_paper.py` and + `experiments/compare_observations.py`: Creative Commons Attribution 4.0 + International (CC BY 4.0), https://creativecommons.org/licenses/by/4.0/, + as stated for agentrust-io papers in the papers repository. +- `launcher/composed-reproduction.zip`: Apache License 2.0, from + agentrust-io/weight-custody-manifest. The license text and NOTICE are in + `launcher/LICENSE` and `launcher/NOTICE` and inside the ZIP. +- Source fetched by the launcher (WCM, cMCP, cA2A) is governed by the license + in each repository at the pinned revision. + +Patent Pending. diff --git a/papers/confidential-handoffs/README.md b/papers/confidential-handoffs/README.md new file mode 100644 index 0000000..2b018b4 --- /dev/null +++ b/papers/confidential-handoffs/README.md @@ -0,0 +1,44 @@ +# Confidentiality Across Agent Handoffs: source package, version 3 + +Technical report, version 3, October 1, 2026. Imran Siddique, OPAQUE Systems. Not peer reviewed. +Patent Pending. Version 3 qualifies the section 7.6 mutual-appraisal result after advisory +GHSA-xxj2-p57x-x954, notes in section 8.3 that ca2a-runtime 0.3.1 is affected, and updates two +redirected links; results are unchanged. +The PDF is published separately at +https://agentrust-io.com/research/confidential-handoffs/v3/paper.pdf. + +Build: `python build_paper.py --source . --output paper.pdf` (ReportLab 5.0.1 and PyMuPDF +1.28.2). It reads Times New Roman, Arial and Courier New TrueType files from `--fonts` +(default `C:/Windows/Fonts`); fonts are not bundled. `manifest.json` lists the SHA-256 of +every other file in this package. + +Paper text is licensed CC BY 4.0: https://creativecommons.org/licenses/by/4.0/ +Project code retains its existing project license; see https://github.com/agentrust-io/weight-custody-manifest/tree/main/python/composed. + +## Contents + +- `paper.md`: manuscript source. +- `experiments/reference-2026-09-19/`: minimized evidence from the recorded run (WCM tested + merge 15e67c2, GitHub Actions run 35469947313). +- `experiments/rerun-2026-09-27.txt` and `experiments/rerun-2026-09-27/`: the September 27 + rerun at WCM main 94d5519. +- `experiments/results/rerun-2026-10-01/`: the October 1 rerun of the 32 portable composed + cases from the same launcher, plus four component counts. All matched. +- `experiments/compare_observations.py`: compares observation records across runs. +- `launcher/composed-reproduction.zip`: the WCM reproduction launcher built by hosted run + 36272971255 (SHA-256 a209faca6139cd3c02b554edae0d9702af4f1932ae19fca939ce4e4229829ef4), + with its Apache-2.0 LICENSE and NOTICE. + +## Reproduce + +Extract `launcher/composed-reproduction.zip` and run +`python reproduce.py --workspace ` with Python 3.12, Git and network access to +GitHub and PyPI. The portable profile runs 32 cases. Add `--confined` on native Linux with +Docker for all 36. Then compare with `experiments/compare_observations.py`. + +## Limits + +Attestation is synthetic, signed by test-owned roots. One operator runs every party. No +hardware protects the model key or computation, and no GPU path is exercised. The GCP and Azure +hardware diagnostics summarized in section 7 are not reproducible from this package; their raw +captures contain device-identifying data and are not included. diff --git a/papers/confidential-handoffs/build_paper.py b/papers/confidential-handoffs/build_paper.py new file mode 100644 index 0000000..c9b69d7 --- /dev/null +++ b/papers/confidential-handoffs/build_paper.py @@ -0,0 +1,123 @@ +"""Build paper.pdf from paper.md with ReportLab and embedded Times/Arial TrueType fonts. + +Usage: python build_paper.py --source . --output paper.pdf [--fonts DIR] +Requires reportlab and pymupdf; fonts are read from --fonts (default C:/Windows/Fonts). +""" +from html import escape +from pathlib import Path +import re + +from reportlab.lib import colors +from reportlab.lib.enums import TA_LEFT +from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet +from reportlab.lib.units import inch +from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle + +import argparse +from functools import partial +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfbase.ttfonts import TTFont +from reportlab.pdfgen.canvas import Canvas +p = argparse.ArgumentParser() +p.add_argument('--source', type=Path, required=True) +p.add_argument('--output', type=Path, required=True) +p.add_argument('--fonts', type=Path, default=Path('C:/Windows/Fonts')) +a = p.parse_args() +ROOT = a.source +for name, filename in [('SubmissionSerif', 'times.ttf'), ('SubmissionBold', 'timesbd.ttf'), ('SubmissionSans', 'arial.ttf')]: + pdfmetrics.registerFont(TTFont(name, str(a.fonts / filename))) +pdfmetrics.registerFontFamily('SubmissionSerif', normal='SubmissionSerif', bold='SubmissionBold', italic='SubmissionSerif', boldItalic='SubmissionBold') +styles = getSampleStyleSheet() +styles.add(ParagraphStyle(name='PaperTitle', fontName='SubmissionBold', fontSize=23, leading=27, + spaceAfter=12, textColor=colors.HexColor('#15334b'))) +styles.add(ParagraphStyle(name='PaperHeading', fontName='SubmissionBold', fontSize=13, leading=17, + spaceBefore=13, spaceAfter=6, keepWithNext=True)) +styles.add(ParagraphStyle(name='PaperBody', fontName='SubmissionSerif', fontSize=10.6, leading=14.3, + spaceAfter=6, alignment=TA_LEFT, splitLongWords=True)) +styles.add(ParagraphStyle(name='PaperCell', fontName='SubmissionSerif', fontSize=8.7, leading=11, + spaceAfter=2, splitLongWords=True)) +styles.add(ParagraphStyle(name='PaperBullet', parent=styles['PaperBody'], leftIndent=11, + firstLineIndent=-7)) + + +def markup(text): + text = escape(text) + text = re.sub(r'\[([^\]]+)\]\((https?://[^)]+)\)', r'\1', text) + text = re.sub(r'\*\*(.*?)\*\*', r'\1', text) + text = text.replace(chr(96), '') + return text + + +def footer(canvas, doc): + canvas.saveState() + canvas.setStrokeColor(colors.HexColor('#ccd5dc')) + canvas.line(0.8*inch, 0.57*inch, 7.7*inch, 0.57*inch) + canvas.setFont('SubmissionSans', 8) + canvas.setFillColor(colors.HexColor('#52616e')) + canvas.drawString(0.8*inch, 0.4*inch, 'OPAQUE Systems | Technical report, version 3 | Software evidence; hardware acceptance is future work') + canvas.drawRightString(7.7*inch, 0.4*inch, str(doc.page)) + canvas.restoreState() + + +def build(): + story = [] + lines = (ROOT/'paper.md').read_text(encoding='utf-8-sig').splitlines() + i = 0 + while i < len(lines): + line = lines[i].strip() + if not line: + i += 1 + continue + if line.startswith('|'): + rows = [] + cells_header = [v.strip() for v in lines[i].strip().strip('|').split('|')] + while i < len(lines) and lines[i].strip().startswith('|'): + cells = [v.strip() for v in lines[i].strip().strip('|').split('|')] + if not all(re.fullmatch(r'[-: ]+', c) for c in cells): + rows.append([Paragraph(markup(c), styles['PaperCell']) for c in cells]) + i += 1 + widths = {2: [2.3*inch, 4.6*inch], 3: [1.82*inch, 1.72*inch, 3.36*inch]}[len(rows[0])] + if cells_header[0] == 'Change under test': + widths = [1.75*inch, 2.45*inch, 2.7*inch] + table = Table(rows, colWidths=widths, repeatRows=1) + table.setStyle(TableStyle([ + ('BACKGROUND', (0,0), (-1,0), colors.HexColor('#e5edf3')), + ('VALIGN', (0,0), (-1,-1), 'TOP'), + ('LEFTPADDING', (0,0), (-1,-1), 6), ('RIGHTPADDING', (0,0), (-1,-1), 6), + ('TOPPADDING', (0,0), (-1,-1), 5), ('BOTTOMPADDING', (0,0), (-1,-1), 5), + ('LINEBELOW', (0,0), (-1,0), 0.7, colors.HexColor('#9aaebc')), + ('LINEBELOW', (0,1), (-1,-1), 0.25, colors.HexColor('#d9e0e5')), + ])) + story.extend([table, Spacer(1, 9)]) + continue + if line.startswith('# '): + story.append(Paragraph(markup(line[2:]), styles['PaperTitle'])) + elif line.startswith(('## ', '### ', '#### ')): + story.append(Paragraph(markup(line.lstrip('#').strip()), styles['PaperHeading'])) + elif re.match(r'^\d+\. ', line): + story.append(Paragraph(markup(line), styles['PaperBody'])) + elif line.startswith('- '): + story.append(Paragraph('• '+markup(line[2:]), styles['PaperBullet'])) + else: + paragraph = [line] + while i+1 < len(lines) and lines[i+1].strip() and not lines[i+1].startswith(('#','|','- ')) and not re.match(r'^\d+\. ', lines[i+1]): + i += 1 + paragraph.append(lines[i].strip()) + story.append(Paragraph(markup(' '.join(paragraph)), styles['PaperBody'])) + i += 1 + doc = SimpleDocTemplate(str(a.output), pagesize=(8.5*inch, 11*inch), + rightMargin=0.8*inch, leftMargin=0.8*inch, topMargin=0.65*inch, + bottomMargin=0.8*inch, title='Confidentiality Across Agent Handoffs', + author='Imran Siddique', pageCompression=1, invariant=1) + doc.build(story, onFirstPage=footer, onLaterPages=footer, canvasmaker=partial(Canvas, initialFontName='SubmissionSans')) + + +if __name__ == '__main__': + build() + # Remove ReportLab's unused base-font resource; retain embedded font streams. + import pymupdf + with pymupdf.open(a.output) as pdf: + for page in pdf: + page.clean_contents() + cleaned = pdf.tobytes(garbage=4, deflate=True) + a.output.write_bytes(cleaned) diff --git a/papers/confidential-handoffs/experiments/compare_observations.py b/papers/confidential-handoffs/experiments/compare_observations.py new file mode 100644 index 0000000..c86b1a2 --- /dev/null +++ b/papers/confidential-handoffs/experiments/compare_observations.py @@ -0,0 +1,41 @@ +"""Compare composed-harness observation records across runs. + +Usage: python compare_observations.py REFERENCE.jsonl OTHER.jsonl [OTHER.jsonl ...] + +Transaction identifiers and the locally built container image digest differ per +run by design and are removed before comparison. Exit status 1 means a record +in OTHER is absent from REFERENCE or an oracle did not pass. +""" +import collections +import json +import sys + + +def load(path): + records = [] + with open(path, encoding="utf-8") as handle: + for line in handle: + record = json.loads(line) + record.pop("transaction", None) + record.get("confinement", {}).pop("image", None) + records.append(json.dumps(record, sort_keys=True)) + return collections.Counter(records) + + +def main(reference, *others): + base = load(reference) + status = 0 + for path in others: + run = load(path) + failed = sum(1 for r in run.elements() if json.loads(r)["oracle"] != "passed") + extra = sum((run - base).values()) + missing = sum((base - run).values()) + print(f"{path}: {sum(run.values())} records, {failed} oracle failures, " + f"{extra} not in reference, {missing} reference records absent") + if failed or extra: + status = 1 + return status + + +if __name__ == "__main__": + sys.exit(main(*sys.argv[1:])) diff --git a/papers/confidential-handoffs/experiments/reference-2026-09-19/manifest.json b/papers/confidential-handoffs/experiments/reference-2026-09-19/manifest.json new file mode 100644 index 0000000..8556875 --- /dev/null +++ b/papers/confidential-handoffs/experiments/reference-2026-09-19/manifest.json @@ -0,0 +1,441 @@ +{ + "profile": "wcm-composed-software-v1", + "claim": "selected software admission gates prevent downstream delivery in the tested path", + "evidence_class": "synthetic-attestation/local-software", + "sources": { + "wcm": "15e67c2fb75b92b4e7d0f182e398915bfd9bc6a2", + "cmcp": "2cdb168ce52020406ff0ef6cbc34447f0ea55aee", + "ca2a": "fc22c644846111c7f375446399e9097a73f93214", + "confinement": "bad751becca0ec5c42d70062e5c9fe5ee1380e85" + }, + "wcm_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "harness_files": { + "agent.py": "c3d86cb0ad1116213ebb734f827897436268b6543c3481f876d6da362dc476df", + "confined.py": "02b0e9491ccabf6d8868a40c3e8c4904773e5d449e0d08c568e8d0afac5f7c26", + "gateway.py": "f2a4630e38c5db808276bbb2ab747253a829a27b9d0b7e9220a4f555b3d0ba9e", + "harness.py": "38f2f6bf28cd5b9ea2460f4e34fa400997b1285291e5d84dbdb4d4986c881c74", + "run.py": "768eba2d49e207a1985e89c82dfe279102aec567cad274dd8cdcb62e5fd55347", + "test_composed.py": "e5f327866a4c45e4a27ecdc46c7177c6a6e6c19eb97ea8dc941581ee6b18d892", + "test_confined.py": "b47659cb9cabb4b3ed8541a359514fab85ba5a4afb1139faa99f695ee2c5b405", + "upstream.py": "78d0dbe530849fdf323c1896471508f343a3730ac4678394eef50742e89adb74", + "Dockerfile": "83006eec5b6b02327b6be3426054cb6f3de0081e661d9523b85be1aa6e24bb6e" + }, + "dependencies": [ + { + "name": "agent-manifest", + "version": "0.12.0" + }, + { + "name": "agentrust-trace", + "version": "0.10.0" + }, + { + "name": "annotated-doc", + "version": "0.0.5" + }, + { + "name": "annotated-types", + "version": "0.8.0" + }, + { + "name": "anyio", + "version": "4.15.1" + }, + { + "name": "asn1crypto", + "version": "1.5.1" + }, + { + "name": "ast_serialize", + "version": "0.11.2" + }, + { + "name": "attrs", + "version": "26.1.0" + }, + { + "name": "bandit", + "version": "1.9.4" + }, + { + "name": "blake3", + "version": "1.0.9" + }, + { + "name": "boolean.py", + "version": "5.0" + }, + { + "name": "ca2a-runtime", + "version": "0.2.1" + }, + { + "name": "CacheControl", + "version": "0.14.4" + }, + { + "name": "cbor2", + "version": "6.1.4" + }, + { + "name": "cedarpy", + "version": "4.8.7" + }, + { + "name": "certifi", + "version": "2026.7.22" + }, + { + "name": "cffi", + "version": "2.1.1" + }, + { + "name": "charset-normalizer", + "version": "3.5.1" + }, + { + "name": "click", + "version": "8.5.0" + }, + { + "name": "cmcp-runtime", + "version": "0.5.0" + }, + { + "name": "coverage", + "version": "7.16.1" + }, + { + "name": "cryptography", + "version": "50.0.1" + }, + { + "name": "cyclonedx-python-lib", + "version": "11.12.0" + }, + { + "name": "defusedxml", + "version": "0.7.1" + }, + { + "name": "dnspython", + "version": "2.8.0" + }, + { + "name": "email-validator", + "version": "2.3.0" + }, + { + "name": "fastapi", + "version": "0.141.1" + }, + { + "name": "filelock", + "version": "4.0.1" + }, + { + "name": "h11", + "version": "0.16.0" + }, + { + "name": "httpcore", + "version": "1.0.9" + }, + { + "name": "httpx", + "version": "0.28.1" + }, + { + "name": "id", + "version": "1.6.1" + }, + { + "name": "idna", + "version": "3.20" + }, + { + "name": "in-toto-attestation", + "version": "0.9.3" + }, + { + "name": "iniconfig", + "version": "2.3.0" + }, + { + "name": "jsonschema", + "version": "4.26.0" + }, + { + "name": "jsonschema-specifications", + "version": "2025.9.1" + }, + { + "name": "librt", + "version": "0.15.0" + }, + { + "name": "license-expression", + "version": "30.4.4" + }, + { + "name": "markdown-it-py", + "version": "4.2.0" + }, + { + "name": "mdurl", + "version": "0.1.2" + }, + { + "name": "model-signing", + "version": "1.1.1" + }, + { + "name": "msgpack", + "version": "1.2.2" + }, + { + "name": "mypy", + "version": "2.3.1" + }, + { + "name": "mypy_extensions", + "version": "1.1.0" + }, + { + "name": "packageurl-python", + "version": "0.17.6" + }, + { + "name": "packaging", + "version": "26.3" + }, + { + "name": "pathspec", + "version": "1.1.1" + }, + { + "name": "pip", + "version": "26.2.1" + }, + { + "name": "pip-requirements-parser", + "version": "32.0.1" + }, + { + "name": "pip_api", + "version": "0.0.35" + }, + { + "name": "pip_audit", + "version": "2.10.1" + }, + { + "name": "platformdirs", + "version": "4.11.11" + }, + { + "name": "pluggy", + "version": "1.6.0" + }, + { + "name": "protobuf", + "version": "7.36.2" + }, + { + "name": "py-serializable", + "version": "2.1.0" + }, + { + "name": "pyasn1", + "version": "0.6.4" + }, + { + "name": "pycparser", + "version": "3.0" + }, + { + "name": "pydantic", + "version": "2.13.5" + }, + { + "name": "pydantic_core", + "version": "2.46.5" + }, + { + "name": "Pygments", + "version": "2.21.0" + }, + { + "name": "PyJWT", + "version": "2.14.0" + }, + { + "name": "pyOpenSSL", + "version": "26.4.0" + }, + { + "name": "pyparsing", + "version": "3.3.2" + }, + { + "name": "pytest", + "version": "9.1.1" + }, + { + "name": "pytest-cov", + "version": "7.1.0" + }, + { + "name": "PyYAML", + "version": "6.0.3" + }, + { + "name": "referencing", + "version": "0.37.0" + }, + { + "name": "requests", + "version": "2.34.2" + }, + { + "name": "rfc3161-client", + "version": "1.0.9" + }, + { + "name": "rfc3986-validator", + "version": "0.1.1" + }, + { + "name": "rfc8785", + "version": "0.1.4" + }, + { + "name": "rich", + "version": "15.0.0" + }, + { + "name": "rpds-py", + "version": "2026.6.3" + }, + { + "name": "securesystemslib", + "version": "1.5.1" + }, + { + "name": "sigstore", + "version": "4.5.0" + }, + { + "name": "sigstore-models", + "version": "0.0.6" + }, + { + "name": "sigstore-rekor-types", + "version": "0.0.18" + }, + { + "name": "sortedcontainers", + "version": "2.4.0" + }, + { + "name": "starlette", + "version": "1.6.0" + }, + { + "name": "stevedore", + "version": "5.9.1" + }, + { + "name": "tomli", + "version": "2.4.1" + }, + { + "name": "tomli_w", + "version": "1.2.0" + }, + { + "name": "tuf", + "version": "7.0.1" + }, + { + "name": "typing-inspection", + "version": "0.4.4" + }, + { + "name": "typing_extensions", + "version": "4.16.0" + }, + { + "name": "urllib3", + "version": "2.8.0" + }, + { + "name": "uvicorn", + "version": "0.53.0" + }, + { + "name": "weight-custody-manifest", + "version": "0.28.2" + } + ], + "policy": { + "model": "affine y=3x+2, input 7", + "tool": "confidential ceiling", + "peer_scope": [ + "read" + ], + "release_recipient": "recipient", + "release_purpose": "review", + "policy_version": "composition-v1" + }, + "trust": "test-generated SNP signing chain, owner signing key and local delegation root", + "principals": [ + "synthetic owner", + "broker", + "workload", + "tool", + "delegated peer", + "recipient" + ], + "limits": [ + "no hardware or protected-key custody", + "same host/operator; observers share test ownership", + "in-memory adapters and loopback HTTP", + "affine diagnostic model, not LLM execution", + "mutation coverage is scoped to named scenarios; not complete issue 199 acceptance", + "unreleased source pins; not a published integration", + "no secure erasure or trusted clock", + "selected agent egress probes only; host, broker, tool and peer remain trusted" + ], + "confinement_image": "sha256:2978d60955a1e2c55ea01eb2c03e664b8baa33251f9dee4be2bc7d26f1232b39", + "command": [ + "python", + "-m", + "pytest", + "-q", + "-o", + "addopts=", + "composed/test_composed.py", + "composed/test_confined.py" + ], + "mutation_targets": [ + "configuration", + "downgrade", + "forbidden-tool", + "key", + "missing-approval", + "missing-evidence", + "model", + "none", + "output", + "response", + "revoked", + "scope", + "timeout", + "timeout-before", + "transaction", + "workload" + ], + "untargeted_cases": [], + "exit_code": 0 +} \ No newline at end of file diff --git a/papers/confidential-handoffs/experiments/reference-2026-09-19/observations.jsonl b/papers/confidential-handoffs/experiments/reference-2026-09-19/observations.jsonl new file mode 100644 index 0000000..d70bf77 --- /dev/null +++ b/papers/confidential-handoffs/experiments/reference-2026-09-19/observations.jsonl @@ -0,0 +1,36 @@ +{"transaction": "608767311b7742ac82bf8b43535e55d0", "fault": "none", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "oracle": "passed", "expected": "one authorized delivery, replay refused", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "b71d3a01c30a4d4e8891da3d2ab93f13", "fault": "workload", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "01c62d27dc2d4c2296b3784cd66a9bea", "fault": "key", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "217375fa42ee48f59e60e611c94475fa", "fault": "configuration", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "9d5097a4b9b446ab98a717ae657fd70b", "fault": "missing-evidence", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "c019f7d5912440fca54b22da634473c9", "fault": "model", "states": {"provisioning": "established", "key_release": "established", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "InvalidTag", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "5952d7f7601f4ebca1e4d93a439b2aa6", "fault": "forbidden-tool", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "contradicted", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "299b40bcdce5462f9a712e62dc3b1f86", "fault": "transaction", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "contradicted", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "b71a77d9757a48a7aea572f06c3c75f8", "fault": "response", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "ResponseAuthenticationFailed", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "29c0372adce74767b9a2d228e5e8ccfd", "fault": "scope", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "AuthenticatedPeerError", "observations": {"tool_receipts": 1, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "4d61ea4401a34da4ae7a48d1555f7113", "fault": "downgrade", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "AttestationFailed", "observations": {"tool_receipts": 1, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "ccff278225eb45759c81dabf57accbdf", "fault": "missing-approval", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "contradicted", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "1318057eee184e4c97cfb308ed81d0c5", "fault": "revoked", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "contradicted", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "bcaec7a90f7a4b02b3ac686050974993", "fault": "output", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "contradicted", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "a715a2a6dd24411fa5b8b714736fb0c7", "fault": "timeout", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "unknown"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "oracle": "passed", "expected": "unknown delivery, no retry", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "df07993f374349b4aa0826e03fa8c59e", "fault": "timeout-before", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "unknown"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "unknown delivery, no retry", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "31d4ac0967b14a05a7d5f7e9fee7307c", "fault": "mutation-workload", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "workload", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "e19328448a8b43789f67781f7e7fd067", "fault": "mutation-sink", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "forbidden-tool", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "ab8675c2059740f48c6516bb804e0ecd", "fault": "mutation-mac", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "response", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "04803b59c8444f11bcd32fd84c2d4d8f", "fault": "mutation-output", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "output", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "17e83d6e3e2a47d9a47f23d896c24eec", "fault": "mutation-transaction", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "transaction", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "cabfe5a0867c4c3fbcf7c98155b30d5f", "fault": "mutation-replay", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 2}, "mutation_target": "none", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "a1c54603686a4be7990b45b37fa7795a", "fault": "mutation-key", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "key", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "ba5503c4dbfc4d268e2b33c71d1ec1d6", "fault": "mutation-configuration", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "configuration", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "37e04073e481487e9eda1146b03b468b", "fault": "mutation-missing-evidence", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "missing-evidence", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "5e368ca5aa9b4cd2a7eaf9a030b89752", "fault": "mutation-model", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "model", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "e7fa09654b6544f0b98786633c278522", "fault": "mutation-scope", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "scope", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "ef40ead0ad1c49f685289607eaa1a14f", "fault": "mutation-downgrade", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "downgrade", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "60ae81ac3f2b4dff9bea6a7e93d22d08", "fault": "mutation-missing-approval", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "missing-approval", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "f9f0597a05a943e2b3ba90067b28f902", "fault": "mutation-revoked", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "revoked", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "5d6ae556e329496db23736685ed61a56", "fault": "mutation-timeout", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "timeout", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "941d5523d6e94006be86bd6e7b07b5aa", "fault": "mutation-timeout-before", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "mutation_target": "timeout-before", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "229a3572343445e39f2ba540ebf8df95", "fault": "confined-positive", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "confinement": {"network": {"tcp": 0, "tcp6": 0, "udp": 0}, "file": false, "logs": false, "running": false, "public_calls": 0, "stats": {"allowed": 1, "denied": 1, "stderr_bytes": 65}, "image": "sha256:2978d60955a1e2c55ea01eb2c03e664b8baa33251f9dee4be2bc7d26f1232b39"}, "oracle": "passed", "expected": "authorized final delivery and observed isolation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "6d04f0dbcbe04bda9392cc2e07e17bf5", "fault": "confined-network", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "confinement": {"network": {"tcp": 1, "tcp6": 1, "udp": 1}, "file": false, "logs": false, "running": false, "public_calls": 0, "stats": {"allowed": 1, "denied": 1, "stderr_bytes": 65}, "image": "sha256:2978d60955a1e2c55ea01eb2c03e664b8baa33251f9dee4be2bc7d26f1232b39"}, "oracle": "passed", "expected": "weakened isolation is detected by an external sink", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "e7e81504512d442d8bded533ebeeb1d3", "fault": "confined-filesystem", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "confinement": {"network": {"tcp": 0, "tcp6": 0, "udp": 0}, "file": true, "logs": false, "running": false, "public_calls": 0, "stats": {"allowed": 1, "denied": 1, "stderr_bytes": 65}, "image": "sha256:2978d60955a1e2c55ea01eb2c03e664b8baa33251f9dee4be2bc7d26f1232b39"}, "oracle": "passed", "expected": "weakened isolation is detected by an external sink", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "e860143e99ff4922b47e4c9a133fd5fd", "fault": "confined-logging", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "confinement": {"network": {"tcp": 0, "tcp6": 0, "udp": 0}, "file": false, "logs": true, "running": false, "public_calls": 0, "stats": {"allowed": 1, "denied": 1, "stderr_bytes": 65}, "image": "sha256:2978d60955a1e2c55ea01eb2c03e664b8baa33251f9dee4be2bc7d26f1232b39"}, "oracle": "passed", "expected": "weakened isolation is detected by an external sink", "evidence_class": "synthetic-attestation/local-software"} diff --git a/papers/confidential-handoffs/experiments/reference-2026-09-19/pytest.log b/papers/confidential-handoffs/experiments/reference-2026-09-19/pytest.log new file mode 100644 index 0000000..ad5cd23 --- /dev/null +++ b/papers/confidential-handoffs/experiments/reference-2026-09-19/pytest.log @@ -0,0 +1,2 @@ +.................................... [100%] +36 passed in 19.24s diff --git a/papers/confidential-handoffs/experiments/rerun-2026-09-27.txt b/papers/confidential-handoffs/experiments/rerun-2026-09-27.txt new file mode 100644 index 0000000..e07d4da --- /dev/null +++ b/papers/confidential-handoffs/experiments/rerun-2026-09-27.txt @@ -0,0 +1,124 @@ +Composed software harness rerun, September 27, 2026 +=================================================== + +Purpose: rerun the composed software experiment of section 8 for the version 1 +technical report, from the harness as merged on WCM main, and reproduce it from +a fresh environment using the harness README (python/composed/README.md and +BUNDLE.md). + +Sources +------- +WCM (agentrust-io/weight-custody-manifest) main 94d5519fd604f7be5a784539a37c1fc021e3d607 + (chore(release): 0.28.5 (#166); python/composed merged via PR 148 on + 2026-09-20, reproduction bundle via PR 151 on 2026-09-22) +cMCP (agentrust-io/cmcp) 2cdb168ce52020406ff0ef6cbc34447f0ea55aee +cA2A (agentrust-io/ca2a) fc22c644846111c7f375446399e9097a73f93214 +cMCP confinement adapter bad751becca0ec5c42d70062e5c9fe5ee1380e85 +These are the same cMCP, cA2A and confinement revisions as the recorded run. + +Recorded run (reference, cited in the September 19 draft) +--------------------------------------------------------- +WCM tested merge 15e67c2fb75b92b4e7d0f182e398915bfd9bc6a2 (PR head f168aa8), +GitHub Actions run 35469947313: 36 passed in 19.24s. +Files: reference-2026-09-19/ + +Runs for this edition +--------------------- +A. Hosted, WCM main + GitHub Actions run 36272971255 (workflow composed-software, push to main, + 2026-09-26T21:27Z to 21:29Z, conclusion success). + Ubuntu runner, kernel 6.17.0-1022-azure, Docker Engine 28.0.4, Python 3.12. + run.py with --confinement-source: 36 passed in 18.76s. + Files: rerun-2026-09-27/hosted-main/ + The run was triggered by the merge push, not by this edition; its logs and + artifacts were downloaded and checked on 2026-09-27. + +B. Hosted, fresh environment from the bundle + Same workflow run. package.py built composed-reproduction.zip + (sha256 a209faca6139cd3c02b554edae0d9702af4f1932ae19fca939ce4e4229829ef4), + then reproduce.py --confined fetched all four sources by revision into a new + workspace and a new virtual environment: 36 passed in 18.60s. + Files: rerun-2026-09-27/hosted-fresh-bundle/ + +C. Local, fresh environment from the bundle (executed 2026-09-27) + The same ZIP (digest above, bundle.json file digests verified by the + launcher) was extracted and run as + python reproduce.py --workspace + on Windows 11 (10.0.26200), Python 3.12.10. The launcher created the + workspace, fetched WCM, cMCP and cA2A at the revisions above, installed the + pinned runtime versions (cbor2 6.1.4 imported without error here), and ran + the portable profile: 32 passed in 18.56s, exit 0. + The native confined profile needs native Linux with Docker; it was not + available on this host, so the 4 native cases rest on runs A and B. + Files: rerun-2026-09-27/windows-fresh-bundle/ + Note: this checkout used Git's CRLF conversion, so the harness file hashes + in its manifest.json differ from runs A and B. After converting CRLF to LF, + all 12 harness files hash identically to run A. Windows added one extra + installed package, colorama 0.4.6 (a pytest dependency on Windows). + +Case counts +----------- + recorded A B C +Portable valid path 1 1 1 1 +Portable refusal paths 13 13 13 13 +Ambiguous callback outcome 2 2 2 2 +Portable gate mutations 16 16 16 16 +Native valid path 1 1 1 - +Native isolation mutations 3 3 3 - +Total passed 36 36 36 32 +Oracle failures 0 0 0 0 + +Comparison with the recorded run +-------------------------------- + python compare_observations.py reference-2026-09-19/observations.jsonl \ + rerun-2026-09-27/hosted-main/observations.jsonl \ + rerun-2026-09-27/hosted-fresh-bundle/observations.jsonl \ + rerun-2026-09-27/windows-fresh-bundle/observations.jsonl + +After removing per-run transaction identifiers and the locally built +container image digest, every record in A and B equals a record of the +recorded run (36 of 36), and every record in C equals one of the 32 portable +recorded records. Stage states, failure classes, receipt counts, network, +filesystem and log observations, allowed/denied gateway counts and stderr +byte counts are unchanged. No numbers differ. + +Container image digests: recorded sha256:2978d609...; A and B +sha256:d46bb547.... The image is rebuilt per run from the pinned Dockerfile. + +Harness changes between the recorded revision and main +------------------------------------------------------- +Of the nine harness files hashed by the recorded run, eight are byte-identical +on main. test_composed.py differs in one place: the missing-evidence mutation +wrapper for KeyBrokerService._check_cpu_quote takes an added `manifest` +argument, following the key-broker interface change in WCM PR 162 +(feat(kbs): let the manifest require evidence verification). The mutation +still removes the same check. package.py, reproduce.py and test_package.py +are new with PR 151. Runtime dependency changes from the recorded run: +agent-manifest 0.12.0 to 0.13.0, weight-custody-manifest 0.28.2 to 0.28.5, +PyJWT 2.14.0 to 2.15.0, starlette 1.6.0 to 1.7.0, uvicorn 0.53.0 to 0.54.0, +filelock 4.0.1 to 4.0.3, platformdirs 4.11.11 to 4.11.14, pyparsing 3.3.2 to +3.3.3. + +Released packages (wheel inspection, 2026-09-27, not executed with the harness) +------------------------------------------------------------------------------- +cmcp-runtime 0.6.0 (PyPI upload 2026-09-25) + cmcp_runtime-0.6.0-py3-none-any.whl + sha256 51a2cad197f2fa4ef484f0c9936df7d58e6f1a4cd9c8877ea6fbd8a3f2016f5b + contains cmcp_runtime/disclosure.py, byte-identical to src/cmcp_runtime/ + disclosure.py at cMCP 2cdb168. Does not contain the examples/confinement + adapter the native profile imports. +ca2a-runtime 0.3.1 (PyPI upload 2026-09-26) + ca2a_runtime-0.3.1-py3-none-any.whl + sha256 d2ec958118733a02514b7f4e85ddf3f0a981efb22b75bed8cf30de22aaaf0b07 + contains ca2a_runtime/response.py, byte-identical to src/ca2a_runtime/ + response.py at cA2A fc22c64; transport/client.py exposes + require_authenticated_response. +The harness still imports WCM test fixtures and the cMCP confinement example +from source. integrations#199 remains open. + +Scope +----- +Synthetic attestation signed by test-owned roots. One operator runs every +party. The diagnostic affine model runs on the trusted host. No hardware, +protected key custody, GPU path or independently operated peer is involved. +No hardware evidence is included in these files. diff --git a/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/bundle.json b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/bundle.json new file mode 100644 index 0000000..d0c8b46 --- /dev/null +++ b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/bundle.json @@ -0,0 +1,26 @@ +{ + "format": "wcm-composed-source-bundle-v1", + "sources": { + "wcm": "94d5519fd604f7be5a784539a37c1fc021e3d607", + "cmcp": "2cdb168ce52020406ff0ef6cbc34447f0ea55aee", + "ca2a": "fc22c644846111c7f375446399e9097a73f93214", + "confinement": "bad751becca0ec5c42d70062e5c9fe5ee1380e85" + }, + "evidence_class": "synthetic-attestation/local-software", + "evaluated_profile": "wcm-composed-software-v1", + "reference_manifest_sha256": "1bcb035e47d60abcc8b5b94e9885aec475999eb238b91986bbd2aa0aec94795f", + "files": { + "reproduce.py": "e91fc3687421deb4ee68acd18e46bb11375ec9e44558b5383cdfc800cb77d74d", + "requirements.txt": "48cefc7af21dc71cb7694665f31493381f1b02eb377b08db315eb6ca3bbfb996", + "README.md": "e82e344b2dd9f8a088d3f32b8f8230f980766092d90ff8d722bd6aebab6ebb6d", + "LICENSE": "c95bae1d1ce0235ecccd3560b772ec1efb97f348a79f0fbe0a634f0c2ccefe2c", + "NOTICE": "bd432d8da5c70cec594ffd907a6d61e933815ae1c113d65475afbc2070c3b64f" + }, + "limits": [ + "source-based research package; not a released integration", + "network required for GitHub, PyPI and optional Docker base image", + "runtime versions pinned; dependencies and build tools not hash locked", + "bundle hashes detect modification; they are not independent provenance", + "synthetic attestation, same operator, host-side diagnostic model" + ] +} diff --git a/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/manifest.json b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/manifest.json new file mode 100644 index 0000000..7f81d95 --- /dev/null +++ b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/manifest.json @@ -0,0 +1,444 @@ +{ + "profile": "wcm-composed-software-v1", + "claim": "selected software admission gates prevent downstream delivery in the tested path", + "evidence_class": "synthetic-attestation/local-software", + "sources": { + "wcm": "94d5519fd604f7be5a784539a37c1fc021e3d607", + "cmcp": "2cdb168ce52020406ff0ef6cbc34447f0ea55aee", + "ca2a": "fc22c644846111c7f375446399e9097a73f93214", + "confinement": "bad751becca0ec5c42d70062e5c9fe5ee1380e85" + }, + "wcm_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "harness_files": { + "agent.py": "c3d86cb0ad1116213ebb734f827897436268b6543c3481f876d6da362dc476df", + "confined.py": "02b0e9491ccabf6d8868a40c3e8c4904773e5d449e0d08c568e8d0afac5f7c26", + "gateway.py": "f2a4630e38c5db808276bbb2ab747253a829a27b9d0b7e9220a4f555b3d0ba9e", + "harness.py": "38f2f6bf28cd5b9ea2460f4e34fa400997b1285291e5d84dbdb4d4986c881c74", + "package.py": "a15c4eab5903537ae57166c7f0df580b5fb67cbce1cfe1f352102d110802330a", + "reproduce.py": "e91fc3687421deb4ee68acd18e46bb11375ec9e44558b5383cdfc800cb77d74d", + "run.py": "768eba2d49e207a1985e89c82dfe279102aec567cad274dd8cdcb62e5fd55347", + "test_composed.py": "72e005d3fa891a807e7732e37a8dd74d612174d5d3c06d90bb8a32ad8804f5f5", + "test_confined.py": "b47659cb9cabb4b3ed8541a359514fab85ba5a4afb1139faa99f695ee2c5b405", + "test_package.py": "8746a966607008a24d4203b357b9813cbd5f9b25571b5ae948e2e539a7753506", + "upstream.py": "78d0dbe530849fdf323c1896471508f343a3730ac4678394eef50742e89adb74", + "Dockerfile": "83006eec5b6b02327b6be3426054cb6f3de0081e661d9523b85be1aa6e24bb6e" + }, + "dependencies": [ + { + "name": "agent-manifest", + "version": "0.13.0" + }, + { + "name": "agentrust-trace", + "version": "0.10.0" + }, + { + "name": "annotated-doc", + "version": "0.0.5" + }, + { + "name": "annotated-types", + "version": "0.8.0" + }, + { + "name": "anyio", + "version": "4.15.1" + }, + { + "name": "asn1crypto", + "version": "1.5.1" + }, + { + "name": "ast_serialize", + "version": "0.11.2" + }, + { + "name": "attrs", + "version": "26.1.0" + }, + { + "name": "bandit", + "version": "1.9.4" + }, + { + "name": "blake3", + "version": "1.0.9" + }, + { + "name": "boolean.py", + "version": "5.0" + }, + { + "name": "ca2a-runtime", + "version": "0.2.1" + }, + { + "name": "CacheControl", + "version": "0.14.4" + }, + { + "name": "cbor2", + "version": "6.1.4" + }, + { + "name": "cedarpy", + "version": "4.8.7" + }, + { + "name": "certifi", + "version": "2026.7.22" + }, + { + "name": "cffi", + "version": "2.1.1" + }, + { + "name": "charset-normalizer", + "version": "3.5.1" + }, + { + "name": "click", + "version": "8.5.0" + }, + { + "name": "cmcp-runtime", + "version": "0.5.0" + }, + { + "name": "coverage", + "version": "7.16.1" + }, + { + "name": "cryptography", + "version": "50.0.1" + }, + { + "name": "cyclonedx-python-lib", + "version": "11.12.0" + }, + { + "name": "defusedxml", + "version": "0.7.1" + }, + { + "name": "dnspython", + "version": "2.8.0" + }, + { + "name": "email-validator", + "version": "2.3.0" + }, + { + "name": "fastapi", + "version": "0.141.1" + }, + { + "name": "filelock", + "version": "4.0.3" + }, + { + "name": "h11", + "version": "0.16.0" + }, + { + "name": "httpcore", + "version": "1.0.9" + }, + { + "name": "httpx", + "version": "0.28.1" + }, + { + "name": "id", + "version": "1.6.1" + }, + { + "name": "idna", + "version": "3.20" + }, + { + "name": "in-toto-attestation", + "version": "0.9.3" + }, + { + "name": "iniconfig", + "version": "2.3.0" + }, + { + "name": "jsonschema", + "version": "4.26.0" + }, + { + "name": "jsonschema-specifications", + "version": "2025.9.1" + }, + { + "name": "librt", + "version": "0.15.0" + }, + { + "name": "license-expression", + "version": "30.4.4" + }, + { + "name": "markdown-it-py", + "version": "4.2.0" + }, + { + "name": "mdurl", + "version": "0.1.2" + }, + { + "name": "model-signing", + "version": "1.1.1" + }, + { + "name": "msgpack", + "version": "1.2.2" + }, + { + "name": "mypy", + "version": "2.3.1" + }, + { + "name": "mypy_extensions", + "version": "1.1.0" + }, + { + "name": "packageurl-python", + "version": "0.17.6" + }, + { + "name": "packaging", + "version": "26.3" + }, + { + "name": "pathspec", + "version": "1.1.1" + }, + { + "name": "pip", + "version": "26.2.1" + }, + { + "name": "pip-requirements-parser", + "version": "32.0.1" + }, + { + "name": "pip_api", + "version": "0.0.35" + }, + { + "name": "pip_audit", + "version": "2.10.1" + }, + { + "name": "platformdirs", + "version": "4.11.14" + }, + { + "name": "pluggy", + "version": "1.6.0" + }, + { + "name": "protobuf", + "version": "7.36.2" + }, + { + "name": "py-serializable", + "version": "2.1.0" + }, + { + "name": "pyasn1", + "version": "0.6.4" + }, + { + "name": "pycparser", + "version": "3.0" + }, + { + "name": "pydantic", + "version": "2.13.5" + }, + { + "name": "pydantic_core", + "version": "2.46.5" + }, + { + "name": "Pygments", + "version": "2.21.0" + }, + { + "name": "PyJWT", + "version": "2.15.0" + }, + { + "name": "pyOpenSSL", + "version": "26.4.0" + }, + { + "name": "pyparsing", + "version": "3.3.3" + }, + { + "name": "pytest", + "version": "9.1.1" + }, + { + "name": "pytest-cov", + "version": "7.1.0" + }, + { + "name": "PyYAML", + "version": "6.0.3" + }, + { + "name": "referencing", + "version": "0.37.0" + }, + { + "name": "requests", + "version": "2.34.2" + }, + { + "name": "rfc3161-client", + "version": "1.0.9" + }, + { + "name": "rfc3986-validator", + "version": "0.1.1" + }, + { + "name": "rfc8785", + "version": "0.1.4" + }, + { + "name": "rich", + "version": "15.0.0" + }, + { + "name": "rpds-py", + "version": "2026.6.3" + }, + { + "name": "securesystemslib", + "version": "1.5.1" + }, + { + "name": "sigstore", + "version": "4.5.0" + }, + { + "name": "sigstore-models", + "version": "0.0.6" + }, + { + "name": "sigstore-rekor-types", + "version": "0.0.18" + }, + { + "name": "sortedcontainers", + "version": "2.4.0" + }, + { + "name": "starlette", + "version": "1.7.0" + }, + { + "name": "stevedore", + "version": "5.9.1" + }, + { + "name": "tomli", + "version": "2.4.1" + }, + { + "name": "tomli_w", + "version": "1.2.0" + }, + { + "name": "tuf", + "version": "7.0.1" + }, + { + "name": "typing-inspection", + "version": "0.4.4" + }, + { + "name": "typing_extensions", + "version": "4.16.0" + }, + { + "name": "urllib3", + "version": "2.8.0" + }, + { + "name": "uvicorn", + "version": "0.54.0" + }, + { + "name": "weight-custody-manifest", + "version": "0.28.5" + } + ], + "policy": { + "model": "affine y=3x+2, input 7", + "tool": "confidential ceiling", + "peer_scope": [ + "read" + ], + "release_recipient": "recipient", + "release_purpose": "review", + "policy_version": "composition-v1" + }, + "trust": "test-generated SNP signing chain, owner signing key and local delegation root", + "principals": [ + "synthetic owner", + "broker", + "workload", + "tool", + "delegated peer", + "recipient" + ], + "limits": [ + "no hardware or protected-key custody", + "same host/operator; observers share test ownership", + "in-memory adapters and loopback HTTP", + "affine diagnostic model, not LLM execution", + "mutation coverage is scoped to named scenarios; not complete issue 199 acceptance", + "unreleased source pins; not a published integration", + "no secure erasure or trusted clock", + "selected agent egress probes only; host, broker, tool and peer remain trusted" + ], + "confinement_image": "sha256:d46bb54723bc45e6ea2782854d06fa322b4273a1a4394f9d6ecd0caae4a76c5d", + "command": [ + "python", + "-m", + "pytest", + "-q", + "-o", + "addopts=", + "composed/test_composed.py", + "composed/test_confined.py" + ], + "mutation_targets": [ + "configuration", + "downgrade", + "forbidden-tool", + "key", + "missing-approval", + "missing-evidence", + "model", + "none", + "output", + "response", + "revoked", + "scope", + "timeout", + "timeout-before", + "transaction", + "workload" + ], + "untargeted_cases": [], + "exit_code": 0 +} \ No newline at end of file diff --git a/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/observations.jsonl b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/observations.jsonl new file mode 100644 index 0000000..0126b80 --- /dev/null +++ b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/observations.jsonl @@ -0,0 +1,36 @@ +{"transaction": "c6f52306b523402d95fd2d92a60c9097", "fault": "none", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "oracle": "passed", "expected": "one authorized delivery, replay refused", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "09f87aa02c8943db8d9af2563e750d15", "fault": "workload", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "966222d02d094916b27d390fc0af85bc", "fault": "key", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "13ea982404184c60a04bde1d27d247e8", "fault": "configuration", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "e37a3726fe2d4710b609b7f9e88512b9", "fault": "missing-evidence", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "9aa0ea4f0580407d9096ce0097e1a5e4", "fault": "model", "states": {"provisioning": "established", "key_release": "established", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "InvalidTag", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "47da9831c9174a90b720eb06962db8d5", "fault": "forbidden-tool", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "contradicted", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "82ecad3667ac462190fe7dcf79f5b147", "fault": "transaction", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "contradicted", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "b6ec2078e6c74b79ba69d54b5cb9cfe0", "fault": "response", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "ResponseAuthenticationFailed", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "923a5fc0575945efa698ba6c0bea4e4d", "fault": "scope", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "AuthenticatedPeerError", "observations": {"tool_receipts": 1, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "3761c6b244184bdb92e09f2afb59912f", "fault": "downgrade", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "AttestationFailed", "observations": {"tool_receipts": 1, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "7cd4c05d0fdf47579116689e63986e14", "fault": "missing-approval", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "contradicted", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "0191749fe2934eccafe20a9b68ce5256", "fault": "revoked", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "contradicted", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "74d810c7b4b0471b9bbdb42577858d8d", "fault": "output", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "contradicted", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "d4beff82510a40a9ac87f951f8f0d6fa", "fault": "timeout", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "unknown"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "oracle": "passed", "expected": "unknown delivery, no retry", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "803a5b2829eb4bfea12a1cd03a783b79", "fault": "timeout-before", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "unknown"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "unknown delivery, no retry", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "c09ef12cae8341198031c735a41f9b3d", "fault": "mutation-workload", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "workload", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "a4674037a74745d294d1c05751849164", "fault": "mutation-sink", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "forbidden-tool", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "23eb9ef72a5b470c80e9e2761062fa1a", "fault": "mutation-mac", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "response", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "a8fa1787909c4b64947aeb97cdcccfc4", "fault": "mutation-output", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "output", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "b9c0f9f6f52d4dad9c9564ebcde791a4", "fault": "mutation-transaction", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "transaction", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "c1d168bda3c34d06bf3fb0662d211f66", "fault": "mutation-replay", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 2}, "mutation_target": "none", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "b2ff7b8b2e93427a939afb9d4224fc8a", "fault": "mutation-key", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "key", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "6bc50e53605847a8bd5aa227b6701b2d", "fault": "mutation-configuration", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "configuration", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "a68c10ad4fd7417b879b61c27b7b3f8a", "fault": "mutation-missing-evidence", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "missing-evidence", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "96d782bf73554740a929ff8b2db945d2", "fault": "mutation-model", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "model", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "d3928eb70c3340f4945ae218b17d0ccd", "fault": "mutation-scope", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "scope", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "90941cc6ded9499f8eb6da45d170046b", "fault": "mutation-downgrade", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "downgrade", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "c20df8139b174215851b2cbe7d4a1ddf", "fault": "mutation-missing-approval", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "missing-approval", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "7650dfc440cd4d73a2e25bc9949ccaa0", "fault": "mutation-revoked", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "revoked", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "39a4ab009ed14ac5b9321584296a4d79", "fault": "mutation-timeout", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "timeout", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "b4279aeb7e424c8f80627152a05b93a2", "fault": "mutation-timeout-before", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "mutation_target": "timeout-before", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "035808fcf775428e949f4b3b65192bad", "fault": "confined-positive", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "confinement": {"network": {"tcp": 0, "tcp6": 0, "udp": 0}, "file": false, "logs": false, "running": false, "public_calls": 0, "stats": {"allowed": 1, "denied": 1, "stderr_bytes": 65}, "image": "sha256:d46bb54723bc45e6ea2782854d06fa322b4273a1a4394f9d6ecd0caae4a76c5d"}, "oracle": "passed", "expected": "authorized final delivery and observed isolation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "48a45614a3d64bf288c0d72ff403690f", "fault": "confined-network", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "confinement": {"network": {"tcp": 1, "tcp6": 1, "udp": 1}, "file": false, "logs": false, "running": false, "public_calls": 0, "stats": {"allowed": 1, "denied": 1, "stderr_bytes": 65}, "image": "sha256:d46bb54723bc45e6ea2782854d06fa322b4273a1a4394f9d6ecd0caae4a76c5d"}, "oracle": "passed", "expected": "weakened isolation is detected by an external sink", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "9d5620e199fe45dea658b6b4e06e2e81", "fault": "confined-filesystem", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "confinement": {"network": {"tcp": 0, "tcp6": 0, "udp": 0}, "file": true, "logs": false, "running": false, "public_calls": 0, "stats": {"allowed": 1, "denied": 1, "stderr_bytes": 65}, "image": "sha256:d46bb54723bc45e6ea2782854d06fa322b4273a1a4394f9d6ecd0caae4a76c5d"}, "oracle": "passed", "expected": "weakened isolation is detected by an external sink", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "7db1b1471b1d436bb6ac416e1c62c376", "fault": "confined-logging", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "confinement": {"network": {"tcp": 0, "tcp6": 0, "udp": 0}, "file": false, "logs": true, "running": false, "public_calls": 0, "stats": {"allowed": 1, "denied": 1, "stderr_bytes": 65}, "image": "sha256:d46bb54723bc45e6ea2782854d06fa322b4273a1a4394f9d6ecd0caae4a76c5d"}, "oracle": "passed", "expected": "weakened isolation is detected by an external sink", "evidence_class": "synthetic-attestation/local-software"} diff --git a/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/pytest.log b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/pytest.log new file mode 100644 index 0000000..f8b42a1 --- /dev/null +++ b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-fresh-bundle/pytest.log @@ -0,0 +1,2 @@ +.................................... [100%] +36 passed in 18.60s diff --git a/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/docker-version.txt b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/docker-version.txt new file mode 100644 index 0000000..504a7a4 --- /dev/null +++ b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/docker-version.txt @@ -0,0 +1,27 @@ +Client: Docker Engine - Community + Version: 28.0.4 + API version: 1.48 + Go version: go1.23.7 + Git commit: b8034c0 + Built: Tue Mar 25 15:07:16 2025 + OS/Arch: linux/amd64 + Context: default + +Server: Docker Engine - Community + Engine: + Version: 28.0.4 + API version: 1.48 (minimum version 1.24) + Go version: go1.23.7 + Git commit: 6430e49 + Built: Tue Mar 25 15:07:16 2025 + OS/Arch: linux/amd64 + Experimental: false + containerd: + Version: v2.3.5 + GitCommit: 1294c24a7da8e5a793ed378161673abe94118892 + runc: + Version: 1.5.1 + GitCommit: v1.5.1-0-g8f2685a4 + docker-init: + Version: 0.19.0 + GitCommit: de40ad0 diff --git a/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/kernel.txt b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/kernel.txt new file mode 100644 index 0000000..dcdb3b7 --- /dev/null +++ b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/kernel.txt @@ -0,0 +1 @@ +Linux runnervmtr4k5 6.17.0-1022-azure #22-Ubuntu SMP Mon Jul 27 17:24:03 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux diff --git a/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/manifest.json b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/manifest.json new file mode 100644 index 0000000..7f81d95 --- /dev/null +++ b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/manifest.json @@ -0,0 +1,444 @@ +{ + "profile": "wcm-composed-software-v1", + "claim": "selected software admission gates prevent downstream delivery in the tested path", + "evidence_class": "synthetic-attestation/local-software", + "sources": { + "wcm": "94d5519fd604f7be5a784539a37c1fc021e3d607", + "cmcp": "2cdb168ce52020406ff0ef6cbc34447f0ea55aee", + "ca2a": "fc22c644846111c7f375446399e9097a73f93214", + "confinement": "bad751becca0ec5c42d70062e5c9fe5ee1380e85" + }, + "wcm_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "harness_files": { + "agent.py": "c3d86cb0ad1116213ebb734f827897436268b6543c3481f876d6da362dc476df", + "confined.py": "02b0e9491ccabf6d8868a40c3e8c4904773e5d449e0d08c568e8d0afac5f7c26", + "gateway.py": "f2a4630e38c5db808276bbb2ab747253a829a27b9d0b7e9220a4f555b3d0ba9e", + "harness.py": "38f2f6bf28cd5b9ea2460f4e34fa400997b1285291e5d84dbdb4d4986c881c74", + "package.py": "a15c4eab5903537ae57166c7f0df580b5fb67cbce1cfe1f352102d110802330a", + "reproduce.py": "e91fc3687421deb4ee68acd18e46bb11375ec9e44558b5383cdfc800cb77d74d", + "run.py": "768eba2d49e207a1985e89c82dfe279102aec567cad274dd8cdcb62e5fd55347", + "test_composed.py": "72e005d3fa891a807e7732e37a8dd74d612174d5d3c06d90bb8a32ad8804f5f5", + "test_confined.py": "b47659cb9cabb4b3ed8541a359514fab85ba5a4afb1139faa99f695ee2c5b405", + "test_package.py": "8746a966607008a24d4203b357b9813cbd5f9b25571b5ae948e2e539a7753506", + "upstream.py": "78d0dbe530849fdf323c1896471508f343a3730ac4678394eef50742e89adb74", + "Dockerfile": "83006eec5b6b02327b6be3426054cb6f3de0081e661d9523b85be1aa6e24bb6e" + }, + "dependencies": [ + { + "name": "agent-manifest", + "version": "0.13.0" + }, + { + "name": "agentrust-trace", + "version": "0.10.0" + }, + { + "name": "annotated-doc", + "version": "0.0.5" + }, + { + "name": "annotated-types", + "version": "0.8.0" + }, + { + "name": "anyio", + "version": "4.15.1" + }, + { + "name": "asn1crypto", + "version": "1.5.1" + }, + { + "name": "ast_serialize", + "version": "0.11.2" + }, + { + "name": "attrs", + "version": "26.1.0" + }, + { + "name": "bandit", + "version": "1.9.4" + }, + { + "name": "blake3", + "version": "1.0.9" + }, + { + "name": "boolean.py", + "version": "5.0" + }, + { + "name": "ca2a-runtime", + "version": "0.2.1" + }, + { + "name": "CacheControl", + "version": "0.14.4" + }, + { + "name": "cbor2", + "version": "6.1.4" + }, + { + "name": "cedarpy", + "version": "4.8.7" + }, + { + "name": "certifi", + "version": "2026.7.22" + }, + { + "name": "cffi", + "version": "2.1.1" + }, + { + "name": "charset-normalizer", + "version": "3.5.1" + }, + { + "name": "click", + "version": "8.5.0" + }, + { + "name": "cmcp-runtime", + "version": "0.5.0" + }, + { + "name": "coverage", + "version": "7.16.1" + }, + { + "name": "cryptography", + "version": "50.0.1" + }, + { + "name": "cyclonedx-python-lib", + "version": "11.12.0" + }, + { + "name": "defusedxml", + "version": "0.7.1" + }, + { + "name": "dnspython", + "version": "2.8.0" + }, + { + "name": "email-validator", + "version": "2.3.0" + }, + { + "name": "fastapi", + "version": "0.141.1" + }, + { + "name": "filelock", + "version": "4.0.3" + }, + { + "name": "h11", + "version": "0.16.0" + }, + { + "name": "httpcore", + "version": "1.0.9" + }, + { + "name": "httpx", + "version": "0.28.1" + }, + { + "name": "id", + "version": "1.6.1" + }, + { + "name": "idna", + "version": "3.20" + }, + { + "name": "in-toto-attestation", + "version": "0.9.3" + }, + { + "name": "iniconfig", + "version": "2.3.0" + }, + { + "name": "jsonschema", + "version": "4.26.0" + }, + { + "name": "jsonschema-specifications", + "version": "2025.9.1" + }, + { + "name": "librt", + "version": "0.15.0" + }, + { + "name": "license-expression", + "version": "30.4.4" + }, + { + "name": "markdown-it-py", + "version": "4.2.0" + }, + { + "name": "mdurl", + "version": "0.1.2" + }, + { + "name": "model-signing", + "version": "1.1.1" + }, + { + "name": "msgpack", + "version": "1.2.2" + }, + { + "name": "mypy", + "version": "2.3.1" + }, + { + "name": "mypy_extensions", + "version": "1.1.0" + }, + { + "name": "packageurl-python", + "version": "0.17.6" + }, + { + "name": "packaging", + "version": "26.3" + }, + { + "name": "pathspec", + "version": "1.1.1" + }, + { + "name": "pip", + "version": "26.2.1" + }, + { + "name": "pip-requirements-parser", + "version": "32.0.1" + }, + { + "name": "pip_api", + "version": "0.0.35" + }, + { + "name": "pip_audit", + "version": "2.10.1" + }, + { + "name": "platformdirs", + "version": "4.11.14" + }, + { + "name": "pluggy", + "version": "1.6.0" + }, + { + "name": "protobuf", + "version": "7.36.2" + }, + { + "name": "py-serializable", + "version": "2.1.0" + }, + { + "name": "pyasn1", + "version": "0.6.4" + }, + { + "name": "pycparser", + "version": "3.0" + }, + { + "name": "pydantic", + "version": "2.13.5" + }, + { + "name": "pydantic_core", + "version": "2.46.5" + }, + { + "name": "Pygments", + "version": "2.21.0" + }, + { + "name": "PyJWT", + "version": "2.15.0" + }, + { + "name": "pyOpenSSL", + "version": "26.4.0" + }, + { + "name": "pyparsing", + "version": "3.3.3" + }, + { + "name": "pytest", + "version": "9.1.1" + }, + { + "name": "pytest-cov", + "version": "7.1.0" + }, + { + "name": "PyYAML", + "version": "6.0.3" + }, + { + "name": "referencing", + "version": "0.37.0" + }, + { + "name": "requests", + "version": "2.34.2" + }, + { + "name": "rfc3161-client", + "version": "1.0.9" + }, + { + "name": "rfc3986-validator", + "version": "0.1.1" + }, + { + "name": "rfc8785", + "version": "0.1.4" + }, + { + "name": "rich", + "version": "15.0.0" + }, + { + "name": "rpds-py", + "version": "2026.6.3" + }, + { + "name": "securesystemslib", + "version": "1.5.1" + }, + { + "name": "sigstore", + "version": "4.5.0" + }, + { + "name": "sigstore-models", + "version": "0.0.6" + }, + { + "name": "sigstore-rekor-types", + "version": "0.0.18" + }, + { + "name": "sortedcontainers", + "version": "2.4.0" + }, + { + "name": "starlette", + "version": "1.7.0" + }, + { + "name": "stevedore", + "version": "5.9.1" + }, + { + "name": "tomli", + "version": "2.4.1" + }, + { + "name": "tomli_w", + "version": "1.2.0" + }, + { + "name": "tuf", + "version": "7.0.1" + }, + { + "name": "typing-inspection", + "version": "0.4.4" + }, + { + "name": "typing_extensions", + "version": "4.16.0" + }, + { + "name": "urllib3", + "version": "2.8.0" + }, + { + "name": "uvicorn", + "version": "0.54.0" + }, + { + "name": "weight-custody-manifest", + "version": "0.28.5" + } + ], + "policy": { + "model": "affine y=3x+2, input 7", + "tool": "confidential ceiling", + "peer_scope": [ + "read" + ], + "release_recipient": "recipient", + "release_purpose": "review", + "policy_version": "composition-v1" + }, + "trust": "test-generated SNP signing chain, owner signing key and local delegation root", + "principals": [ + "synthetic owner", + "broker", + "workload", + "tool", + "delegated peer", + "recipient" + ], + "limits": [ + "no hardware or protected-key custody", + "same host/operator; observers share test ownership", + "in-memory adapters and loopback HTTP", + "affine diagnostic model, not LLM execution", + "mutation coverage is scoped to named scenarios; not complete issue 199 acceptance", + "unreleased source pins; not a published integration", + "no secure erasure or trusted clock", + "selected agent egress probes only; host, broker, tool and peer remain trusted" + ], + "confinement_image": "sha256:d46bb54723bc45e6ea2782854d06fa322b4273a1a4394f9d6ecd0caae4a76c5d", + "command": [ + "python", + "-m", + "pytest", + "-q", + "-o", + "addopts=", + "composed/test_composed.py", + "composed/test_confined.py" + ], + "mutation_targets": [ + "configuration", + "downgrade", + "forbidden-tool", + "key", + "missing-approval", + "missing-evidence", + "model", + "none", + "output", + "response", + "revoked", + "scope", + "timeout", + "timeout-before", + "transaction", + "workload" + ], + "untargeted_cases": [], + "exit_code": 0 +} \ No newline at end of file diff --git a/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/observations.jsonl b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/observations.jsonl new file mode 100644 index 0000000..1627e4c --- /dev/null +++ b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/observations.jsonl @@ -0,0 +1,36 @@ +{"transaction": "4e80dd06563349efbff8e9add7f05a70", "fault": "none", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "oracle": "passed", "expected": "one authorized delivery, replay refused", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "e86b27f33ddc46a8ac809e9256b5e35f", "fault": "workload", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "192f927b392147188348cdc492dd3068", "fault": "key", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "7add5db7eed44648819128d28c3b7306", "fault": "configuration", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "01431002b5d14a03af18d75700ed7ed8", "fault": "missing-evidence", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "725f2f119bcc4039a8f6e13830473c70", "fault": "model", "states": {"provisioning": "established", "key_release": "established", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "InvalidTag", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "c4d5e4c31e2b49e7955b5f057a419863", "fault": "forbidden-tool", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "contradicted", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "221ea8f2b9ed47f3b26cbb7ed74d63d0", "fault": "transaction", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "contradicted", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "a84fe2af5bf1456a988d66f7bc9192ee", "fault": "response", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "ResponseAuthenticationFailed", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "b0513f3f02cd4995b808fe07d35e4821", "fault": "scope", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "AuthenticatedPeerError", "observations": {"tool_receipts": 1, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "b59511f8224a4f71824772c16965c967", "fault": "downgrade", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "AttestationFailed", "observations": {"tool_receipts": 1, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "bd6bffe43ba348489d6509e94b1fc408", "fault": "missing-approval", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "contradicted", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "16d812e407764cbe94ef48907ceffc35", "fault": "revoked", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "contradicted", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "b486c41812b24f4e81b40d668adc5303", "fault": "output", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "contradicted", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "44165247aefc4238a9e9069191d9078a", "fault": "timeout", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "unknown"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "oracle": "passed", "expected": "unknown delivery, no retry", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "3851bd712e934658ac973e13d357933b", "fault": "timeout-before", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "unknown"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "unknown delivery, no retry", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "acad6fd647fb46459c9c2d6d268c9e28", "fault": "mutation-workload", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "workload", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "a83affc6e3e84eacaae2866d7a8ad235", "fault": "mutation-sink", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "forbidden-tool", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "90e4ed49cda3484c8bbee7393eaa8e57", "fault": "mutation-mac", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "response", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "edc07d1a5ec84482a5ded055538496b8", "fault": "mutation-output", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "output", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "8b12b420547b4e90b2eb2a63e6de9ca8", "fault": "mutation-transaction", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "transaction", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "24bf90d071d4481eb11353f704b5af9d", "fault": "mutation-replay", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 2}, "mutation_target": "none", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "67ac7d40bf7b49449787820dad92e266", "fault": "mutation-key", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "key", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "55d5f5ca79f34af393242e75f8677f74", "fault": "mutation-configuration", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "configuration", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "84c8ecee99f449c2b57e6e4d8c9fda01", "fault": "mutation-missing-evidence", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "missing-evidence", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "bd2e63cbcc824c66b983d6e85da596fb", "fault": "mutation-model", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "model", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "ab6e91e3f815460f9394cf60c66ba034", "fault": "mutation-scope", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "scope", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "5036293a1ce14a378f34c9ab7e8201b6", "fault": "mutation-downgrade", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "downgrade", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "67552f3caa0b41c6be05236416a84313", "fault": "mutation-missing-approval", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "missing-approval", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "ceb886ede40540aea8a02b58e00cdc8a", "fault": "mutation-revoked", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "revoked", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "cd60c59dc49445208d5b0c441524645e", "fault": "mutation-timeout", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "timeout", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "a8c3cd93d8904cf5a52cae44007f61f2", "fault": "mutation-timeout-before", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "mutation_target": "timeout-before", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "17e2ac9bf99a47acba9dd7f51c852d5d", "fault": "confined-positive", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "confinement": {"network": {"tcp": 0, "tcp6": 0, "udp": 0}, "file": false, "logs": false, "running": false, "public_calls": 0, "stats": {"allowed": 1, "denied": 1, "stderr_bytes": 65}, "image": "sha256:d46bb54723bc45e6ea2782854d06fa322b4273a1a4394f9d6ecd0caae4a76c5d"}, "oracle": "passed", "expected": "authorized final delivery and observed isolation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "772a8ae3b52a4741bf415864cf151e28", "fault": "confined-network", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "confinement": {"network": {"tcp": 1, "tcp6": 1, "udp": 1}, "file": false, "logs": false, "running": false, "public_calls": 0, "stats": {"allowed": 1, "denied": 1, "stderr_bytes": 65}, "image": "sha256:d46bb54723bc45e6ea2782854d06fa322b4273a1a4394f9d6ecd0caae4a76c5d"}, "oracle": "passed", "expected": "weakened isolation is detected by an external sink", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "9c0e39d59b4f40f4b6933ee94a6bb485", "fault": "confined-filesystem", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "confinement": {"network": {"tcp": 0, "tcp6": 0, "udp": 0}, "file": true, "logs": false, "running": false, "public_calls": 0, "stats": {"allowed": 1, "denied": 1, "stderr_bytes": 65}, "image": "sha256:d46bb54723bc45e6ea2782854d06fa322b4273a1a4394f9d6ecd0caae4a76c5d"}, "oracle": "passed", "expected": "weakened isolation is detected by an external sink", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "fd882cc289a243f3ac534449671163d0", "fault": "confined-logging", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "confinement": {"network": {"tcp": 0, "tcp6": 0, "udp": 0}, "file": false, "logs": true, "running": false, "public_calls": 0, "stats": {"allowed": 1, "denied": 1, "stderr_bytes": 65}, "image": "sha256:d46bb54723bc45e6ea2782854d06fa322b4273a1a4394f9d6ecd0caae4a76c5d"}, "oracle": "passed", "expected": "weakened isolation is detected by an external sink", "evidence_class": "synthetic-attestation/local-software"} diff --git a/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/pytest.log b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/pytest.log new file mode 100644 index 0000000..e87d33c --- /dev/null +++ b/papers/confidential-handoffs/experiments/rerun-2026-09-27/hosted-main/pytest.log @@ -0,0 +1,2 @@ +.................................... [100%] +36 passed in 18.76s diff --git a/papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/bundle.json b/papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/bundle.json new file mode 100644 index 0000000..d0c8b46 --- /dev/null +++ b/papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/bundle.json @@ -0,0 +1,26 @@ +{ + "format": "wcm-composed-source-bundle-v1", + "sources": { + "wcm": "94d5519fd604f7be5a784539a37c1fc021e3d607", + "cmcp": "2cdb168ce52020406ff0ef6cbc34447f0ea55aee", + "ca2a": "fc22c644846111c7f375446399e9097a73f93214", + "confinement": "bad751becca0ec5c42d70062e5c9fe5ee1380e85" + }, + "evidence_class": "synthetic-attestation/local-software", + "evaluated_profile": "wcm-composed-software-v1", + "reference_manifest_sha256": "1bcb035e47d60abcc8b5b94e9885aec475999eb238b91986bbd2aa0aec94795f", + "files": { + "reproduce.py": "e91fc3687421deb4ee68acd18e46bb11375ec9e44558b5383cdfc800cb77d74d", + "requirements.txt": "48cefc7af21dc71cb7694665f31493381f1b02eb377b08db315eb6ca3bbfb996", + "README.md": "e82e344b2dd9f8a088d3f32b8f8230f980766092d90ff8d722bd6aebab6ebb6d", + "LICENSE": "c95bae1d1ce0235ecccd3560b772ec1efb97f348a79f0fbe0a634f0c2ccefe2c", + "NOTICE": "bd432d8da5c70cec594ffd907a6d61e933815ae1c113d65475afbc2070c3b64f" + }, + "limits": [ + "source-based research package; not a released integration", + "network required for GitHub, PyPI and optional Docker base image", + "runtime versions pinned; dependencies and build tools not hash locked", + "bundle hashes detect modification; they are not independent provenance", + "synthetic attestation, same operator, host-side diagnostic model" + ] +} diff --git a/papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/manifest.json b/papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/manifest.json new file mode 100644 index 0000000..aca9d35 --- /dev/null +++ b/papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/manifest.json @@ -0,0 +1,445 @@ +{ + "profile": "wcm-composed-software-v1", + "claim": "selected software admission gates prevent downstream delivery in the tested path", + "evidence_class": "synthetic-attestation/local-software", + "sources": { + "wcm": "94d5519fd604f7be5a784539a37c1fc021e3d607", + "cmcp": "2cdb168ce52020406ff0ef6cbc34447f0ea55aee", + "ca2a": "fc22c644846111c7f375446399e9097a73f93214" + }, + "wcm_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "harness_files": { + "agent.py": "5921bd7b724a3cd07a96c7784f2d5b1b99da2e09927ba2338d9cab9a3a6257d7", + "confined.py": "db52f10b0f65b352fb0d6b1e68e94e426d77f740dd3ee853a258c4fefc9e1bf2", + "gateway.py": "c0c0efa7e14ccbc1908a93ac989dca84730bb8bb1961b7fa170ccb9207ba51cb", + "harness.py": "8230defa4dde03beb5fee8441833ecb979a27a84e80dd038c18d6af136ed4ac0", + "package.py": "e1e0e71aa21a8285d5d6914e818217f0d771946b85487af84631eaad6089c23d", + "reproduce.py": "d1ee7cf0555be4582bd07f4561cd0932d79bfefa3038540656631b5b0d8dcbba", + "run.py": "0796401ed2563a0aab2cfda83293fef1c94aa87206ad0c10a8095b4521c3a6c5", + "test_composed.py": "44659bd2240642a0f80a4be5046057b2394d2fecc77c563285e775b52bc35909", + "test_confined.py": "7e87e24cdb7bf52dda729503b2c373286840bbe3538936595c3c23f50758ae4a", + "test_package.py": "888f9467170ba168993eded8c8c9fd63d267b552605f042ae0a0aba539a9d7e3", + "upstream.py": "9d7f42079f7ed380ccfb6506e15a23347baa01746e527619bd10f23608397ea5", + "Dockerfile": "2d215237f9f73e2968122c3d3d3125670f472888850ea3642652ad7f831d4dab" + }, + "dependencies": [ + { + "name": "agent-manifest", + "version": "0.13.0" + }, + { + "name": "agentrust-trace", + "version": "0.10.0" + }, + { + "name": "annotated-doc", + "version": "0.0.5" + }, + { + "name": "annotated-types", + "version": "0.8.0" + }, + { + "name": "anyio", + "version": "4.15.1" + }, + { + "name": "asn1crypto", + "version": "1.5.1" + }, + { + "name": "ast_serialize", + "version": "0.11.2" + }, + { + "name": "attrs", + "version": "26.1.0" + }, + { + "name": "bandit", + "version": "1.9.4" + }, + { + "name": "blake3", + "version": "1.0.9" + }, + { + "name": "boolean.py", + "version": "5.0" + }, + { + "name": "ca2a-runtime", + "version": "0.2.1" + }, + { + "name": "CacheControl", + "version": "0.14.4" + }, + { + "name": "cbor2", + "version": "6.1.4" + }, + { + "name": "cedarpy", + "version": "4.8.7" + }, + { + "name": "certifi", + "version": "2026.7.22" + }, + { + "name": "cffi", + "version": "2.1.1" + }, + { + "name": "charset-normalizer", + "version": "3.5.1" + }, + { + "name": "click", + "version": "8.5.0" + }, + { + "name": "cmcp-runtime", + "version": "0.5.0" + }, + { + "name": "colorama", + "version": "0.4.6" + }, + { + "name": "coverage", + "version": "7.16.1" + }, + { + "name": "cryptography", + "version": "50.0.1" + }, + { + "name": "cyclonedx-python-lib", + "version": "11.12.0" + }, + { + "name": "defusedxml", + "version": "0.7.1" + }, + { + "name": "dnspython", + "version": "2.8.0" + }, + { + "name": "email-validator", + "version": "2.3.0" + }, + { + "name": "fastapi", + "version": "0.141.1" + }, + { + "name": "filelock", + "version": "4.0.3" + }, + { + "name": "h11", + "version": "0.16.0" + }, + { + "name": "httpcore", + "version": "1.0.9" + }, + { + "name": "httpx", + "version": "0.28.1" + }, + { + "name": "id", + "version": "1.6.1" + }, + { + "name": "idna", + "version": "3.20" + }, + { + "name": "in-toto-attestation", + "version": "0.9.3" + }, + { + "name": "iniconfig", + "version": "2.3.0" + }, + { + "name": "jsonschema", + "version": "4.26.0" + }, + { + "name": "jsonschema-specifications", + "version": "2025.9.1" + }, + { + "name": "librt", + "version": "0.15.0" + }, + { + "name": "license-expression", + "version": "30.4.4" + }, + { + "name": "markdown-it-py", + "version": "4.2.0" + }, + { + "name": "mdurl", + "version": "0.1.2" + }, + { + "name": "model-signing", + "version": "1.1.1" + }, + { + "name": "msgpack", + "version": "1.2.2" + }, + { + "name": "mypy", + "version": "2.3.1" + }, + { + "name": "mypy_extensions", + "version": "1.1.0" + }, + { + "name": "packageurl-python", + "version": "0.17.6" + }, + { + "name": "packaging", + "version": "26.3" + }, + { + "name": "pathspec", + "version": "1.1.1" + }, + { + "name": "pip", + "version": "26.2.1" + }, + { + "name": "pip-requirements-parser", + "version": "32.0.1" + }, + { + "name": "pip_api", + "version": "0.0.35" + }, + { + "name": "pip_audit", + "version": "2.10.1" + }, + { + "name": "platformdirs", + "version": "4.11.14" + }, + { + "name": "pluggy", + "version": "1.6.0" + }, + { + "name": "protobuf", + "version": "7.36.2" + }, + { + "name": "py-serializable", + "version": "2.1.0" + }, + { + "name": "pyasn1", + "version": "0.6.4" + }, + { + "name": "pycparser", + "version": "3.0" + }, + { + "name": "pydantic", + "version": "2.13.5" + }, + { + "name": "pydantic_core", + "version": "2.46.5" + }, + { + "name": "Pygments", + "version": "2.21.0" + }, + { + "name": "PyJWT", + "version": "2.15.0" + }, + { + "name": "pyOpenSSL", + "version": "26.4.0" + }, + { + "name": "pyparsing", + "version": "3.3.3" + }, + { + "name": "pytest", + "version": "9.1.1" + }, + { + "name": "pytest-cov", + "version": "7.1.0" + }, + { + "name": "PyYAML", + "version": "6.0.3" + }, + { + "name": "referencing", + "version": "0.37.0" + }, + { + "name": "requests", + "version": "2.34.2" + }, + { + "name": "rfc3161-client", + "version": "1.0.9" + }, + { + "name": "rfc3986-validator", + "version": "0.1.1" + }, + { + "name": "rfc8785", + "version": "0.1.4" + }, + { + "name": "rich", + "version": "15.0.0" + }, + { + "name": "rpds-py", + "version": "2026.6.3" + }, + { + "name": "securesystemslib", + "version": "1.5.1" + }, + { + "name": "sigstore", + "version": "4.5.0" + }, + { + "name": "sigstore-models", + "version": "0.0.6" + }, + { + "name": "sigstore-rekor-types", + "version": "0.0.18" + }, + { + "name": "sortedcontainers", + "version": "2.4.0" + }, + { + "name": "starlette", + "version": "1.7.0" + }, + { + "name": "stevedore", + "version": "5.9.1" + }, + { + "name": "tomli", + "version": "2.4.1" + }, + { + "name": "tomli_w", + "version": "1.2.0" + }, + { + "name": "tuf", + "version": "7.0.1" + }, + { + "name": "typing-inspection", + "version": "0.4.4" + }, + { + "name": "typing_extensions", + "version": "4.16.0" + }, + { + "name": "urllib3", + "version": "2.8.0" + }, + { + "name": "uvicorn", + "version": "0.54.0" + }, + { + "name": "weight-custody-manifest", + "version": "0.28.5" + } + ], + "policy": { + "model": "affine y=3x+2, input 7", + "tool": "confidential ceiling", + "peer_scope": [ + "read" + ], + "release_recipient": "recipient", + "release_purpose": "review", + "policy_version": "composition-v1" + }, + "trust": "test-generated SNP signing chain, owner signing key and local delegation root", + "principals": [ + "synthetic owner", + "broker", + "workload", + "tool", + "delegated peer", + "recipient" + ], + "limits": [ + "no hardware or protected-key custody", + "no OS egress confinement in this composed run", + "same host/operator; observers share test ownership", + "in-memory adapters and loopback HTTP", + "affine diagnostic model, not LLM execution", + "mutation coverage is scoped to named scenarios; not complete issue 199 acceptance", + "unreleased source pins; not a published integration", + "no secure erasure or trusted clock" + ], + "command": [ + "python", + "-m", + "pytest", + "-q", + "-o", + "addopts=", + "composed/test_composed.py" + ], + "mutation_targets": [ + "configuration", + "downgrade", + "forbidden-tool", + "key", + "missing-approval", + "missing-evidence", + "model", + "none", + "output", + "response", + "revoked", + "scope", + "timeout", + "timeout-before", + "transaction", + "workload" + ], + "untargeted_cases": [], + "exit_code": 0 +} \ No newline at end of file diff --git a/papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/observations.jsonl b/papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/observations.jsonl new file mode 100644 index 0000000..e4a7a83 --- /dev/null +++ b/papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/observations.jsonl @@ -0,0 +1,32 @@ +{"transaction": "b7b140db5e38473d9756d7b8fba56022", "fault": "none", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "oracle": "passed", "expected": "one authorized delivery, replay refused", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "c361c3295ae8407b8d4264fa4eee8561", "fault": "workload", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "3baceb753d61446eb8e72219346169a3", "fault": "key", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "a94e506b1d774c639a7a8291745d1431", "fault": "configuration", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "758a74c59da94756871b94ececb3ef30", "fault": "missing-evidence", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "701a461ad7214b6bad1dbb418e2923bb", "fault": "model", "states": {"provisioning": "established", "key_release": "established", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "InvalidTag", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "c7f6d367981041f2b26f03b6ea400dab", "fault": "forbidden-tool", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "contradicted", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "8271146517cf451180c1843f43384f43", "fault": "transaction", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "contradicted", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "03b2ad5c6b1041ef9d14abc829556e25", "fault": "response", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "ResponseAuthenticationFailed", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "22051a7fede14002951884f1f2896de5", "fault": "scope", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "AuthenticatedPeerError", "observations": {"tool_receipts": 1, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "9d307bbf86464652a2f72a54cf0008ae", "fault": "downgrade", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "AttestationFailed", "observations": {"tool_receipts": 1, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "612d2e4c690d468ba6089e93fa5c59dc", "fault": "missing-approval", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "contradicted", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "d2cefe4cd0ab41edb2ec678d218c4452", "fault": "revoked", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "contradicted", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "0a22a590b65846208b78953b4ec86d8e", "fault": "output", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "contradicted", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "602389dce7f64e64b568cd331f7b44ce", "fault": "timeout", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "unknown"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "oracle": "passed", "expected": "unknown delivery, no retry", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "89f8603a19434200988679e3d1c24b26", "fault": "timeout-before", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "unknown"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "unknown delivery, no retry", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "0275bf1028844b4aad876389109b46ca", "fault": "mutation-workload", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "workload", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "3a26322f44c54114a8746a9ee12cb45a", "fault": "mutation-sink", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "forbidden-tool", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "1f00e0452a5d44a4aef963f23a617ff3", "fault": "mutation-mac", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "response", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "5685c0ea603d4f4c890f7467390153c9", "fault": "mutation-output", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "output", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "815777dac8a34052b2e64e431d633ed9", "fault": "mutation-transaction", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "transaction", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "58527e1f4413444cb35b3f76a7e75240", "fault": "mutation-replay", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 2}, "mutation_target": "none", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "27fe4bf0065e4b6cb97f254cf212a7fc", "fault": "mutation-key", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "key", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "47eacfe5ec7d43d986afaae972199b6a", "fault": "mutation-configuration", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "configuration", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "621cd8f5733c4ed891a6ce897e010b38", "fault": "mutation-missing-evidence", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "missing-evidence", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "964b9dbfd3e641e19df1dffe27e271f5", "fault": "mutation-model", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "model", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "cd001d8d172e418b938657c555c71f0e", "fault": "mutation-scope", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "scope", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "2bbfa93254fa4d27aa7a6dacc19fe84d", "fault": "mutation-downgrade", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "downgrade", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "3bd95f54bbee4c6c81602f95e601a18d", "fault": "mutation-missing-approval", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "missing-approval", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "98e6716aa4204369900a5ab9d712c78e", "fault": "mutation-revoked", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "revoked", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "67e4e1a7d0da4d609f8312dcddcf31f1", "fault": "mutation-timeout", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "timeout", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "7d62ae6dbd6a492d9ac095854fe545eb", "fault": "mutation-timeout-before", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "mutation_target": "timeout-before", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} diff --git a/papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/pytest.log b/papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/pytest.log new file mode 100644 index 0000000..db9db58 --- /dev/null +++ b/papers/confidential-handoffs/experiments/rerun-2026-09-27/windows-fresh-bundle/pytest.log @@ -0,0 +1,2 @@ +................................ [100%] +32 passed in 18.56s diff --git a/papers/confidential-handoffs/experiments/results/rerun-2026-10-01/README.txt b/papers/confidential-handoffs/experiments/results/rerun-2026-10-01/README.txt new file mode 100644 index 0000000..9543c24 --- /dev/null +++ b/papers/confidential-handoffs/experiments/results/rerun-2026-10-01/README.txt @@ -0,0 +1,47 @@ +Composed software harness and component reruns, 2026-10-01 +========================================================== + +Host: Windows 11 (10.0.26200), Python 3.12.10. Native Linux with Docker is not +available here, so the 4 native confinement cases were not run. + +A. Composed harness, fresh environment from the hosted bundle + Bundle: composed-reproduction.zip downloaded from GitHub Actions run + 36272971255 (WCM main 94d5519, artifact composed-reproduction-bundle), + sha256 a209faca6139cd3c02b554edae0d9702af4f1932ae19fca939ce4e4229829ef4, + identical to the digest recorded on 2026-09-27. + Command: python reproduce.py --workspace + Sources fetched by the launcher: WCM 94d5519fd604, cMCP 2cdb168ce520, + cA2A fc22c6448461 (the paper's revisions). + Result: 32 passed in 43.19s, exit 0. windows-fresh-bundle/ + Installed dependency list, harness file hashes, policy, trust, principals, + limits and mutation targets are identical to the 2026-09-27 Windows run. + + python ../../compare_observations.py \ + ../../reference-2026-09-19/observations.jsonl \ + windows-fresh-bundle/observations.jsonl + -> 32 records, 0 oracle failures, 0 not in reference, + 4 reference records absent (the 4 native cases) + + Paper section 8 numbers: 32 portable cases (1 valid, 13 refusals, + 2 unknown-outcome, 16 mutations) reproduce. Only wall time differs + (18.56s on 2026-09-27, 43.19s here). The 36-case hosted results (runs + 35469947313 and 36272971255) were not re-executed; both runs are + recorded as success at the stated head revisions. + +B. Component experiments (section 7), rerun at the pinned revisions + cA2A fc22c6448461 tests/unit/test_response_authentication.py 57 passed (paper: 57) + cMCP 2cdb168ce520 tests/unit/test_disclosure.py + + tests/unit/test_sink_policy.py 77 passed (paper: 77) + (needs pytest-asyncio, which the bundle venv omits; + without it 22 sink tests fail on the missing plugin) + WCM db8f10638bb9 python/tests/test_provisioning_lifecycle.py 18 passed (paper: 18 new cases) + full suite 995 passed, 32 skipped (paper: same) + + Full-suite counts for cA2A (691 passed, 2 skipped) and cMCP (2,046 passed, + 20 skipped) were not rerun. The 118 "existing focused controls" in 7.3 are + not named by file in the paper, so the 136 total was not reconstructed. + +Not repeated: the cMCP confinement suite (61 native Linux tests, Docker), +firmware TCG cases (edk2/QEMU), the Azure and GCP SEV-SNP diagnostics and the +September 17 live cA2A handoff. These need hardware or native Linux and the +raw captures are private. diff --git a/papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/bundle.json b/papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/bundle.json new file mode 100644 index 0000000..d0c8b46 --- /dev/null +++ b/papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/bundle.json @@ -0,0 +1,26 @@ +{ + "format": "wcm-composed-source-bundle-v1", + "sources": { + "wcm": "94d5519fd604f7be5a784539a37c1fc021e3d607", + "cmcp": "2cdb168ce52020406ff0ef6cbc34447f0ea55aee", + "ca2a": "fc22c644846111c7f375446399e9097a73f93214", + "confinement": "bad751becca0ec5c42d70062e5c9fe5ee1380e85" + }, + "evidence_class": "synthetic-attestation/local-software", + "evaluated_profile": "wcm-composed-software-v1", + "reference_manifest_sha256": "1bcb035e47d60abcc8b5b94e9885aec475999eb238b91986bbd2aa0aec94795f", + "files": { + "reproduce.py": "e91fc3687421deb4ee68acd18e46bb11375ec9e44558b5383cdfc800cb77d74d", + "requirements.txt": "48cefc7af21dc71cb7694665f31493381f1b02eb377b08db315eb6ca3bbfb996", + "README.md": "e82e344b2dd9f8a088d3f32b8f8230f980766092d90ff8d722bd6aebab6ebb6d", + "LICENSE": "c95bae1d1ce0235ecccd3560b772ec1efb97f348a79f0fbe0a634f0c2ccefe2c", + "NOTICE": "bd432d8da5c70cec594ffd907a6d61e933815ae1c113d65475afbc2070c3b64f" + }, + "limits": [ + "source-based research package; not a released integration", + "network required for GitHub, PyPI and optional Docker base image", + "runtime versions pinned; dependencies and build tools not hash locked", + "bundle hashes detect modification; they are not independent provenance", + "synthetic attestation, same operator, host-side diagnostic model" + ] +} diff --git a/papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/manifest.json b/papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/manifest.json new file mode 100644 index 0000000..aca9d35 --- /dev/null +++ b/papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/manifest.json @@ -0,0 +1,445 @@ +{ + "profile": "wcm-composed-software-v1", + "claim": "selected software admission gates prevent downstream delivery in the tested path", + "evidence_class": "synthetic-attestation/local-software", + "sources": { + "wcm": "94d5519fd604f7be5a784539a37c1fc021e3d607", + "cmcp": "2cdb168ce52020406ff0ef6cbc34447f0ea55aee", + "ca2a": "fc22c644846111c7f375446399e9097a73f93214" + }, + "wcm_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "harness_files": { + "agent.py": "5921bd7b724a3cd07a96c7784f2d5b1b99da2e09927ba2338d9cab9a3a6257d7", + "confined.py": "db52f10b0f65b352fb0d6b1e68e94e426d77f740dd3ee853a258c4fefc9e1bf2", + "gateway.py": "c0c0efa7e14ccbc1908a93ac989dca84730bb8bb1961b7fa170ccb9207ba51cb", + "harness.py": "8230defa4dde03beb5fee8441833ecb979a27a84e80dd038c18d6af136ed4ac0", + "package.py": "e1e0e71aa21a8285d5d6914e818217f0d771946b85487af84631eaad6089c23d", + "reproduce.py": "d1ee7cf0555be4582bd07f4561cd0932d79bfefa3038540656631b5b0d8dcbba", + "run.py": "0796401ed2563a0aab2cfda83293fef1c94aa87206ad0c10a8095b4521c3a6c5", + "test_composed.py": "44659bd2240642a0f80a4be5046057b2394d2fecc77c563285e775b52bc35909", + "test_confined.py": "7e87e24cdb7bf52dda729503b2c373286840bbe3538936595c3c23f50758ae4a", + "test_package.py": "888f9467170ba168993eded8c8c9fd63d267b552605f042ae0a0aba539a9d7e3", + "upstream.py": "9d7f42079f7ed380ccfb6506e15a23347baa01746e527619bd10f23608397ea5", + "Dockerfile": "2d215237f9f73e2968122c3d3d3125670f472888850ea3642652ad7f831d4dab" + }, + "dependencies": [ + { + "name": "agent-manifest", + "version": "0.13.0" + }, + { + "name": "agentrust-trace", + "version": "0.10.0" + }, + { + "name": "annotated-doc", + "version": "0.0.5" + }, + { + "name": "annotated-types", + "version": "0.8.0" + }, + { + "name": "anyio", + "version": "4.15.1" + }, + { + "name": "asn1crypto", + "version": "1.5.1" + }, + { + "name": "ast_serialize", + "version": "0.11.2" + }, + { + "name": "attrs", + "version": "26.1.0" + }, + { + "name": "bandit", + "version": "1.9.4" + }, + { + "name": "blake3", + "version": "1.0.9" + }, + { + "name": "boolean.py", + "version": "5.0" + }, + { + "name": "ca2a-runtime", + "version": "0.2.1" + }, + { + "name": "CacheControl", + "version": "0.14.4" + }, + { + "name": "cbor2", + "version": "6.1.4" + }, + { + "name": "cedarpy", + "version": "4.8.7" + }, + { + "name": "certifi", + "version": "2026.7.22" + }, + { + "name": "cffi", + "version": "2.1.1" + }, + { + "name": "charset-normalizer", + "version": "3.5.1" + }, + { + "name": "click", + "version": "8.5.0" + }, + { + "name": "cmcp-runtime", + "version": "0.5.0" + }, + { + "name": "colorama", + "version": "0.4.6" + }, + { + "name": "coverage", + "version": "7.16.1" + }, + { + "name": "cryptography", + "version": "50.0.1" + }, + { + "name": "cyclonedx-python-lib", + "version": "11.12.0" + }, + { + "name": "defusedxml", + "version": "0.7.1" + }, + { + "name": "dnspython", + "version": "2.8.0" + }, + { + "name": "email-validator", + "version": "2.3.0" + }, + { + "name": "fastapi", + "version": "0.141.1" + }, + { + "name": "filelock", + "version": "4.0.3" + }, + { + "name": "h11", + "version": "0.16.0" + }, + { + "name": "httpcore", + "version": "1.0.9" + }, + { + "name": "httpx", + "version": "0.28.1" + }, + { + "name": "id", + "version": "1.6.1" + }, + { + "name": "idna", + "version": "3.20" + }, + { + "name": "in-toto-attestation", + "version": "0.9.3" + }, + { + "name": "iniconfig", + "version": "2.3.0" + }, + { + "name": "jsonschema", + "version": "4.26.0" + }, + { + "name": "jsonschema-specifications", + "version": "2025.9.1" + }, + { + "name": "librt", + "version": "0.15.0" + }, + { + "name": "license-expression", + "version": "30.4.4" + }, + { + "name": "markdown-it-py", + "version": "4.2.0" + }, + { + "name": "mdurl", + "version": "0.1.2" + }, + { + "name": "model-signing", + "version": "1.1.1" + }, + { + "name": "msgpack", + "version": "1.2.2" + }, + { + "name": "mypy", + "version": "2.3.1" + }, + { + "name": "mypy_extensions", + "version": "1.1.0" + }, + { + "name": "packageurl-python", + "version": "0.17.6" + }, + { + "name": "packaging", + "version": "26.3" + }, + { + "name": "pathspec", + "version": "1.1.1" + }, + { + "name": "pip", + "version": "26.2.1" + }, + { + "name": "pip-requirements-parser", + "version": "32.0.1" + }, + { + "name": "pip_api", + "version": "0.0.35" + }, + { + "name": "pip_audit", + "version": "2.10.1" + }, + { + "name": "platformdirs", + "version": "4.11.14" + }, + { + "name": "pluggy", + "version": "1.6.0" + }, + { + "name": "protobuf", + "version": "7.36.2" + }, + { + "name": "py-serializable", + "version": "2.1.0" + }, + { + "name": "pyasn1", + "version": "0.6.4" + }, + { + "name": "pycparser", + "version": "3.0" + }, + { + "name": "pydantic", + "version": "2.13.5" + }, + { + "name": "pydantic_core", + "version": "2.46.5" + }, + { + "name": "Pygments", + "version": "2.21.0" + }, + { + "name": "PyJWT", + "version": "2.15.0" + }, + { + "name": "pyOpenSSL", + "version": "26.4.0" + }, + { + "name": "pyparsing", + "version": "3.3.3" + }, + { + "name": "pytest", + "version": "9.1.1" + }, + { + "name": "pytest-cov", + "version": "7.1.0" + }, + { + "name": "PyYAML", + "version": "6.0.3" + }, + { + "name": "referencing", + "version": "0.37.0" + }, + { + "name": "requests", + "version": "2.34.2" + }, + { + "name": "rfc3161-client", + "version": "1.0.9" + }, + { + "name": "rfc3986-validator", + "version": "0.1.1" + }, + { + "name": "rfc8785", + "version": "0.1.4" + }, + { + "name": "rich", + "version": "15.0.0" + }, + { + "name": "rpds-py", + "version": "2026.6.3" + }, + { + "name": "securesystemslib", + "version": "1.5.1" + }, + { + "name": "sigstore", + "version": "4.5.0" + }, + { + "name": "sigstore-models", + "version": "0.0.6" + }, + { + "name": "sigstore-rekor-types", + "version": "0.0.18" + }, + { + "name": "sortedcontainers", + "version": "2.4.0" + }, + { + "name": "starlette", + "version": "1.7.0" + }, + { + "name": "stevedore", + "version": "5.9.1" + }, + { + "name": "tomli", + "version": "2.4.1" + }, + { + "name": "tomli_w", + "version": "1.2.0" + }, + { + "name": "tuf", + "version": "7.0.1" + }, + { + "name": "typing-inspection", + "version": "0.4.4" + }, + { + "name": "typing_extensions", + "version": "4.16.0" + }, + { + "name": "urllib3", + "version": "2.8.0" + }, + { + "name": "uvicorn", + "version": "0.54.0" + }, + { + "name": "weight-custody-manifest", + "version": "0.28.5" + } + ], + "policy": { + "model": "affine y=3x+2, input 7", + "tool": "confidential ceiling", + "peer_scope": [ + "read" + ], + "release_recipient": "recipient", + "release_purpose": "review", + "policy_version": "composition-v1" + }, + "trust": "test-generated SNP signing chain, owner signing key and local delegation root", + "principals": [ + "synthetic owner", + "broker", + "workload", + "tool", + "delegated peer", + "recipient" + ], + "limits": [ + "no hardware or protected-key custody", + "no OS egress confinement in this composed run", + "same host/operator; observers share test ownership", + "in-memory adapters and loopback HTTP", + "affine diagnostic model, not LLM execution", + "mutation coverage is scoped to named scenarios; not complete issue 199 acceptance", + "unreleased source pins; not a published integration", + "no secure erasure or trusted clock" + ], + "command": [ + "python", + "-m", + "pytest", + "-q", + "-o", + "addopts=", + "composed/test_composed.py" + ], + "mutation_targets": [ + "configuration", + "downgrade", + "forbidden-tool", + "key", + "missing-approval", + "missing-evidence", + "model", + "none", + "output", + "response", + "revoked", + "scope", + "timeout", + "timeout-before", + "transaction", + "workload" + ], + "untargeted_cases": [], + "exit_code": 0 +} \ No newline at end of file diff --git a/papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/observations.jsonl b/papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/observations.jsonl new file mode 100644 index 0000000..fbd0c89 --- /dev/null +++ b/papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/observations.jsonl @@ -0,0 +1,32 @@ +{"transaction": "09183f5b567643f4b6845f017e1ad1e9", "fault": "none", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "oracle": "passed", "expected": "one authorized delivery, replay refused", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "3b09025f7fed428e85c5fd1359a6e244", "fault": "workload", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "cd52c6118ac54454ba55689c8c141a33", "fault": "key", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "296f5d304e4b4c49a82b9a317d8f5cb5", "fault": "configuration", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "f5fc1ba6bfa646229abbab29f2bef831", "fault": "missing-evidence", "states": {"provisioning": "established", "key_release": "contradicted", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "cbd05b170bc4411fa27eb994676cc2e9", "fault": "model", "states": {"provisioning": "established", "key_release": "established", "model": "unavailable", "tool": "unavailable", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "InvalidTag", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "6d51fe4a097b4a1aab46bc1f821b3e8d", "fault": "forbidden-tool", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "contradicted", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 0, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "57baa7e6d21f44f3a8e35d50fdbd6fc9", "fault": "transaction", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "contradicted", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "5a5613de895049a9ae7ae9984122b80c", "fault": "response", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "ResponseAuthenticationFailed", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "3f6afb9c6647405ca553d739848d5058", "fault": "scope", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "AuthenticatedPeerError", "observations": {"tool_receipts": 1, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "39ac994dba0f48acb671fb3a0fe99e60", "fault": "downgrade", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "unavailable", "response": "unavailable", "authorization": "unavailable", "delivery": "unavailable"}, "failure": "AttestationFailed", "observations": {"tool_receipts": 1, "peer_receipts": 0, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "18cc6c570b75497ba1b8a4b953152b94", "fault": "missing-approval", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "contradicted", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "f0a7e12e61ac42ae9f0e274ffcf844df", "fault": "revoked", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "contradicted", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "c8dbb0f292d64b40a2f00947eabac05f", "fault": "output", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "contradicted", "delivery": "unavailable"}, "failure": "Refused", "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "no final disclosure", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "6df629f0d2bf41ea8c6dbde58625106c", "fault": "timeout", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "unknown"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "oracle": "passed", "expected": "unknown delivery, no retry", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "c2a0191228c84e6c8ea92bf8be344cbe", "fault": "timeout-before", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "unknown"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "oracle": "passed", "expected": "unknown delivery, no retry", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "950d7332c9a048b999605dffda00b5e7", "fault": "mutation-workload", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "workload", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "f67a6d32b68d4222be81a675abe342e0", "fault": "mutation-sink", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "forbidden-tool", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "aae8a39ce6c8464d9d49a5737e7dec56", "fault": "mutation-mac", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "response", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "d1c85c93a4574093857c10a935f58a64", "fault": "mutation-output", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "output", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "6841c3a34183472e9c982861ff9bcdef", "fault": "mutation-transaction", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "transaction", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "a4a17687b6854877954ca76d08944f56", "fault": "mutation-replay", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 2}, "mutation_target": "none", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "01cca40c52234da58b42ec1768d0f188", "fault": "mutation-key", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "key", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "1f53b92a712d4e20b1a4ccd876e5a141", "fault": "mutation-configuration", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "configuration", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "5e6b87ed366b4cb7938fd9034a013468", "fault": "mutation-missing-evidence", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "missing-evidence", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "f29dcaeeed1542039a540d2a40797cc8", "fault": "mutation-model", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "model", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "7fa070c478744ea681d48e73eb8eec9d", "fault": "mutation-scope", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "scope", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "58c59a7855da4ba08af1c2a02b39947e", "fault": "mutation-downgrade", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "downgrade", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "d7223203aece42ef80fe7a98627bcd67", "fault": "mutation-missing-approval", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "missing-approval", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "d097b14357af4b60aecb2db221b3969f", "fault": "mutation-revoked", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "revoked", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "d486021798b140188ae4a8bf13fe0944", "fault": "mutation-timeout", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 1}, "mutation_target": "timeout", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} +{"transaction": "a62e4f214a804a24a54c503fc03f5be4", "fault": "mutation-timeout-before", "states": {"provisioning": "established", "key_release": "established", "model": "established", "tool": "established", "peer": "established", "response": "established", "authorization": "established", "delivery": "acknowledged"}, "failure": null, "observations": {"tool_receipts": 1, "peer_receipts": 1, "deliveries": 0}, "mutation_target": "timeout-before", "oracle": "passed", "expected": "weakening changes the required boundary observation", "evidence_class": "synthetic-attestation/local-software"} diff --git a/papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/pytest.log b/papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/pytest.log new file mode 100644 index 0000000..9ca084a --- /dev/null +++ b/papers/confidential-handoffs/experiments/results/rerun-2026-10-01/windows-fresh-bundle/pytest.log @@ -0,0 +1,2 @@ +................................ [100%] +32 passed in 43.19s diff --git a/papers/confidential-handoffs/launcher/LICENSE b/papers/confidential-handoffs/launcher/LICENSE new file mode 100644 index 0000000..57bc88a --- /dev/null +++ b/papers/confidential-handoffs/launcher/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/papers/confidential-handoffs/launcher/NOTICE b/papers/confidential-handoffs/launcher/NOTICE new file mode 100644 index 0000000..31b6110 --- /dev/null +++ b/papers/confidential-handoffs/launcher/NOTICE @@ -0,0 +1,35 @@ +Weight Custody Manifest +Copyright 2026 AgenTrust Authors + +This product includes software developed by the AgenTrust project +(https://github.com/agentrust-io/weight-custody-manifest). + +Portions of the Python SDK's canonicalization and signing primitives were +adapted from the agentrust-io/agent-manifest SDK (Apache-2.0) to keep the +family byte-compatible. + +--- + +Third-party components used by the Python SDK: + +pydantic + License: MIT + https://github.com/pydantic/pydantic + +cryptography + License: Apache-2.0 / BSD + https://github.com/pyca/cryptography + +fastapi (optional - reference KBS server, [server] extra) + License: MIT + https://github.com/fastapi/fastapi + +uvicorn (optional - reference KBS server, [server] extra) + License: BSD + https://github.com/encode/uvicorn + +The post-quantum profile (ML-DSA-65) uses cryptography's native FIPS 204 +support; it requires no external liboqs/pyoqs. The CLI uses only the Python +standard library (argparse), and canonicalization (RFC 8785) is implemented +in-tree - so the base package's only runtime dependencies are pydantic and +cryptography. diff --git a/papers/confidential-handoffs/launcher/composed-reproduction.zip b/papers/confidential-handoffs/launcher/composed-reproduction.zip new file mode 100644 index 0000000000000000000000000000000000000000..544ba070815d6e9317e82924795ad7e66dbe4739 GIT binary patch literal 9835 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 fP4XY|pS4Lr8sa}@^qePmR8 literal 0 HcmV?d00001 diff --git a/papers/confidential-handoffs/manifest.json b/papers/confidential-handoffs/manifest.json new file mode 100644 index 0000000..fc987bc --- /dev/null +++ b/papers/confidential-handoffs/manifest.json @@ -0,0 +1,32 @@ +{ + "LICENSE.md": "912bbaf2d6446033faa56fe90331f11f908cc1ad667e701b16a9e60793788343", + "README.md": "4b96a5bd86330fed69f929c7ee069f0f0657d0725fadedd6a08221a45f815747", + "build_paper.py": "ada411c2c6e03349fa024e64d3b695aed46e40e9db86fb31fdce597aeba2f1eb", + "experiments/compare_observations.py": "dfd2d9563253d793baacf92cac89b495571a17e8ae5bb0eaefb81fad1c6aba85", + "experiments/reference-2026-09-19/manifest.json": "a113d9b4b7ad06fb7fa65276ddac41c862e344ae65aabf33708d95022aa552f9", + "experiments/reference-2026-09-19/observations.jsonl": "53ea6977ea3a7407f060a3dfba1616bfbcaacc5e8919e21ef13f41bf043e802d", + "experiments/reference-2026-09-19/pytest.log": "73d1ab205e01a57562c1439b4d8a6abf76749479f4c2a7eb9c7cd6d88193d3ba", + "experiments/rerun-2026-09-27.txt": "9ca8b7edb5320bd1c4040eecf510fe6b7da9e68c8f4e7034f2d311042206f76d", + "experiments/rerun-2026-09-27/hosted-fresh-bundle/bundle.json": "91517d10fa543dac4a249c88e47fbe83a9c762102bfd5254629db5596f3a6eae", + "experiments/rerun-2026-09-27/hosted-fresh-bundle/manifest.json": "1bcb035e47d60abcc8b5b94e9885aec475999eb238b91986bbd2aa0aec94795f", + "experiments/rerun-2026-09-27/hosted-fresh-bundle/observations.jsonl": "6404c6ca7f9ae56190b063670cb09ba78637618d836b7c36df3f358dd081ee57", + "experiments/rerun-2026-09-27/hosted-fresh-bundle/pytest.log": "57a2f758c94e6a871b44827033e7ddfcef824007cd476d3cbe69659023ebca72", + "experiments/rerun-2026-09-27/hosted-main/docker-version.txt": "27d90540f3fbb3876e7cc4c4e29d7c11e36a603712a60dcd29901de86040d9fc", + "experiments/rerun-2026-09-27/hosted-main/kernel.txt": "1150233bb271b8f280e64ea31fc11d74ba60a774582af967e567daceead45054", + "experiments/rerun-2026-09-27/hosted-main/manifest.json": "1bcb035e47d60abcc8b5b94e9885aec475999eb238b91986bbd2aa0aec94795f", + "experiments/rerun-2026-09-27/hosted-main/observations.jsonl": "b05fafc3adadd521bf7568d4f5c467351e1f07d00db81ad9717540adf9aadca8", + "experiments/rerun-2026-09-27/hosted-main/pytest.log": "82231b4b09f713e24b44fbb569391c3239056f5f9d771454cda13f0e00641ea2", + "experiments/rerun-2026-09-27/windows-fresh-bundle/bundle.json": "91517d10fa543dac4a249c88e47fbe83a9c762102bfd5254629db5596f3a6eae", + "experiments/rerun-2026-09-27/windows-fresh-bundle/manifest.json": "1e4b255c22892b755dc7be5fdbe72dfd0ba671ac6fd2bc6ab5b53b377f71410c", + "experiments/rerun-2026-09-27/windows-fresh-bundle/observations.jsonl": "ef03261896b39b7e9ddb09a6b2161af2c786235119b4f8341f5fe1c3916a01b8", + "experiments/rerun-2026-09-27/windows-fresh-bundle/pytest.log": "a5a6730ff7318cb3d9b0874db3baa40098132aceb7bf6117938fd9a4f0ff33f3", + "experiments/results/rerun-2026-10-01/README.txt": "2994969d48ecd8e3ed9b3529387314f6c0812c690c7d191bd40a56a553ccb581", + "experiments/results/rerun-2026-10-01/windows-fresh-bundle/bundle.json": "91517d10fa543dac4a249c88e47fbe83a9c762102bfd5254629db5596f3a6eae", + "experiments/results/rerun-2026-10-01/windows-fresh-bundle/manifest.json": "1e4b255c22892b755dc7be5fdbe72dfd0ba671ac6fd2bc6ab5b53b377f71410c", + "experiments/results/rerun-2026-10-01/windows-fresh-bundle/observations.jsonl": "f6ed1995058db5fa606cfb0b2a34059cb104ea20c6d7e1d47a575d7534feae76", + "experiments/results/rerun-2026-10-01/windows-fresh-bundle/pytest.log": "b9ed4af95f2c73bdca9beeeb46e8cc6ae78247cd3bd233eb871bd18c7a6979ad", + "launcher/LICENSE": "c95bae1d1ce0235ecccd3560b772ec1efb97f348a79f0fbe0a634f0c2ccefe2c", + "launcher/NOTICE": "bd432d8da5c70cec594ffd907a6d61e933815ae1c113d65475afbc2070c3b64f", + "launcher/composed-reproduction.zip": "a209faca6139cd3c02b554edae0d9702af4f1932ae19fca939ce4e4229829ef4", + "paper.md": "0c4cc151990b655b21fcbe21a926dd55e2a43ad77083e0b3de9d9d36b5ea2dfc" +} diff --git a/papers/confidential-handoffs/paper.md b/papers/confidential-handoffs/paper.md new file mode 100644 index 0000000..80b2fc7 --- /dev/null +++ b/papers/confidential-handoffs/paper.md @@ -0,0 +1,266 @@ +# Confidentiality Across Agent Handoffs + +## Conditions for preserving an inference guarantee through tools and delegation + +Imran Siddique · OPAQUE Systems · October 1, 2026 + +Technical report, version 3 · Component and composed software experiments + +### Abstract + +This paper states conditions under which tool calls and agent handoffs preserve an authorized plaintext-holder boundary. This requires a protected channel bound to an appraised workload, restricted authority, enforceable downstream information-flow rules, and control over every other plaintext sink. A signature on an execution record or a valid hardware quote alone cannot establish these conditions. If a recipient cannot satisfy them, the sender must withhold the data or obtain authorization for a precisely described disclosure. This paper develops a conditional composition argument, a proposed handoff contract, and component experiments that expose failures of appraisal, supervision, and outcome inference. A composed software harness joins provisioning, diagnostic model computation, a confined agent, a mediated tool, delegated peer authentication, and exact-output disclosure. Its paired mutations expose earlier leaks despite successful final delivery; a rerun on September 27, 2026 reproduced all 36 recorded observations. All results are software results with synthetic attestation and a single operator. AgenTrust supplies relevant identity, key-release, gateway, delegation, and evidence primitives, but its present components do not demonstrate the complete property. The distinction matters most at remote tools, CPU-GPU transfers, operator-controlled key brokers, runtime changes, and audit systems. + +**Scope of this edition.** This is an author-prepared technical report, not peer reviewed and not a normative specification. Version 2 adds one related-work citation that version 1 (September 27, 2026) omitted; results are unchanged. Version 3 (October 1, 2026) qualifies the mutual-appraisal result of section 7.6 after advisory GHSA-xxj2-p57x-x954, notes in section 8.3 that ca2a-runtime 0.3.1 is affected by it, and updates two redirected links; the portable composed cases and four component counts were rerun on October 1 with the same results. This is a bounded software edition. Attestation in the composed experiment is synthetic, signed by test-owned roots. One operator runs every party, including the tool, the delegated peer and the observers. No model key or model computation is protected by hardware, and no GPU path is exercised. Hardware acceptance on an owner-controlled launch host and composition with independently operated peers are future work; section 8.5 records the status of each acceptance item as of September 27, 2026. Two cloud hardware diagnostics are summarized in section 7 (GCP on September 17 and 18, Azure on September 18). Their raw captures contain device-identifying data and are not published with this report. Component results preserve the counts recorded at their pinned revisions. The composed software experiment was rerun for this edition; the record is in the accompanying experiments/rerun-2026-09-27.txt. + +**Patent Pending.** + +### 1. The guarantee that must survive + +A June 2025 design report by Pattern Labs (now Irregular), written with a frontier-model developer, distinguishes confidentiality of inference inputs and outputs from confidentiality of model weights. Its architecture considers attested execution, model provisioning, and the confidential boundary, including accelerator paths. That analysis motivates examining the whole data path. It does not establish that arbitrary external tools inherit an inference service's protections. [Confidential Inference Systems: Design principles and security risks, June 2025](https://www.irregular.com/research/confidential-inference-systems) + +Forough, Kogias and Haddadi's survey of confidential computing for agentic AI lists compound attestation for multi-hop agent chains among its open challenges. This paper takes up one part of that problem: whether a guarantee established at one hop survives the next tool call or delegation. [When Agents Handle Secrets, arXiv:2605.03213](https://arxiv.org/abs/2605.03213) + +Consider an agent analyzing a private contract. It sends a paragraph to a translation tool, stores an embedding, then asks another agent to identify renewal obligations. The initial inference service may protect memory from its infrastructure operator. Each subsequent operation introduces another possible plaintext holder: a tool process, embedding provider, database, remote model, tracing service, or privileged administrator. + +The relevant question is therefore asset-specific: **who may learn which information, through which execution paths, under which assumptions?** “Confidential” without an asset, adversary, recipient set, and leakage boundary is insufficient. + +We distinguish three properties: + +| Property | Protected asset | Example failure | +|---|---|---| +| Operator confidentiality | Inputs, outputs, and intermediate state | A tool operator reads request logs. | +| Authorized information flow | Information derived from protected inputs | An approved agent sends a secret to an unauthorized recipient. | +| Model custody | Model weights and related secrets | An operator-controlled broker releases the decryption key to a substituted workload. | + +The first protects against infrastructure access. The second constrains software that legitimately sees plaintext. The third can remain intact even when a tool leaks user data. A deployment must report these properties separately. + +### 2. Threat model and trust assumptions + +The proposed design considers a malicious network, an infrastructure administrator outside the accepted hardware boundary, a substituted peer or tool, replayed evidence, stale policy, and malicious instructions in model inputs or tool results. Application behavior is untrusted until constrained by an approved implementation and its enforcement boundary. Model-generated assertions of permission have no authority. + +The trusted computing base includes hardware and firmware accepted by deployment policy; evidence verifiers and trust roots; the measured workload and its dynamic-loading controls; channel and key handling; policy evaluation; release authorities; and all components able to read protected plaintext. A VM-level boundary includes its guest operating system and privileged guest software. Shared isolation between a tool and its policy engine enlarges that trusted base. + +Cryptographic primitives are assumed sound. Accepted hardware must meet the stated adversary model. Physical extraction, speculative or ciphertext side channels, firmware compromise, and traffic analysis require explicit platform-specific treatment. A deployment cannot convert an excluded adversary into a covered adversary by adding an attestation check. + +Availability and semantic correctness of model answers are outside the confidentiality claim. Complete elimination of timing, message-length, access-pattern, and termination leakage is also outside the proposed baseline. The permitted leakage must be documented. Deployments requiring stronger secrecy need separate analysis of padding, scheduling, storage access, and covert channels. + +### 3. A conditional composition argument + +Represent a workflow as a graph whose vertices are execution or storage domains and whose edges carry data. For each asset x, define an authorized recipient set R(x), an allowed-purpose policy, a retention rule, a declassification authority, and an explicit leakage allowance. Derived data inherits the relevant restrictions unless an authorized transformation changes them. + +Let B(x) contain every approved domain permitted to hold plaintext derived from x. An individual vertex belongs in B(x) only when its execution protections and application controls satisfy the policy for x. A vendor certificate or endpoint name does not establish membership by itself. + +For each transfer from domain A to domain B, require: + +1. **Recipient appraisal.** The sender accepts B's evidence under locally trusted reference values, platform policy, freshness requirements, and revocation rules. The evidence binds the destination channel key to the accepted execution domain. A signed report containing an arbitrary public key is insufficient unless the accepted workload creates and retains the corresponding private key under protection. +2. **Authority.** The caller has authority for the specific asset, operation, recipient, purpose, and interval. Further delegation cannot increase that authority. Recipient identity and evidence identity must refer to the same party in the same transaction. +3. **Protected transfer.** Encryption terminates inside B's accepted domain. Relays, TLS terminators, error handlers, and queues outside it receive no protected plaintext. Request and response channels both satisfy this rule. +4. **Complete mediation.** Every plaintext exit from B is controlled: network traffic, tool arguments, files, model-provider requests, debugging, logs, crash dumps, checkpoints, and returned results. Internal transformations cannot silently strip restrictions. +5. **Continuity.** The workload and policy used for the operation remain those accepted during appraisal. Changes invalidate the authorization or trigger a new appraisal. Key lifetimes and session rules bound the interval of trust. + +**Conditional claim.** Given an initially protected asset, these conditions preserve its authorized plaintext-holder boundary across any finite sequence of mediated transfers, excluding the declared leakage and authorized declassifications. + +The argument is inductive. Initially, all plaintext holders are approved. At each transfer, mediation either blocks release or admits one appraised and authorized recipient over a protected channel. The receiver's controls constrain subsequent copies and derivatives. The approved-holder property therefore remains true after that step. Composition fails as soon as an unmediated sink, unauthorized recipient, false appraisal, or unenforced application policy is possible. + +This is an architectural argument under strong premises, not a formal proof of noninterference or a proof about an implementation. Its invariant concerns authorized plaintext holders, including holders of derivatives; it does not establish that an authorized reader uses information only for an intended purpose. Complete mediation, sound propagation of restrictions, private-key confinement, and correct policy enforcement require independent evidence. Cycles and retries preserve the argument only if every repeated transfer is mediated; cached authorization is subject to the continuity rule. No collection of passing quote-signature tests establishes these premises. + +### 4. The proposed handoff contract + +Each handoff should bind the information below into one authenticated transaction. This is a proposed integration contract, not a claim that a current AgenTrust schema implements these fields together. + +| Contract element | Required meaning | +|---|---| +| Transaction context | Protocol version, unique request identifier, challenge, expiry, sender and recipient identities, and session transcript binding. | +| Workload identity | Approved manifest and policy digests, accepted execution evidence, destination key, and verifier decision. | +| Asset restrictions | Opaque asset identifier, confidentiality classification, permitted operation and audience, retention limit, and downstream delegation constraints. | +| Authority | Issuer-approved permission, parent delegation reference, attenuation constraints, and any applicable revocation state. | +| Platform requirements | Named policy version, accepted hardware class and security state, minimum security versions, debug restrictions, and required mitigations. | +| Output restrictions | Permitted return endpoint, inherited classification, approved declassification transform if any, and audit-disclosure policy. | + +The sender validates recipient evidence before sealing sensitive data. The recipient validates caller authority before opening or processing it. Where both sides release secrets or accept privileged actions, each appraises the other's execution environment. Mutual appraisal does not require claiming that both sides learn the outcome simultaneously; protocol sequencing and failure behavior must be specified. + +Bind operation, audience, request identity, policy version, and channel context into authenticated data or a signed transcript. Otherwise, ciphertext and valid authorization may be transplanted into a different operation. A fresh challenge does not make an application request single-use: that requires request state or an idempotency policy covering retries, concurrent copies, and restarts. + +Attestation follows the separation in the IETF RATS architecture: evidence is appraised against policy, and the relying party uses the result to decide access. An appraisal should carry its scope and expiry; a Boolean “verified” discards security-relevant distinctions. [RFC 9334](https://www.rfc-editor.org/rfc/rfc9334.html) + +Public-key sealing can use established constructions such as HPKE, with an appropriate authenticated protocol around them. Encryption alone supplies neither delegation authority nor proof of correct application behavior. This is a design recommendation, not a claim that cA2A's existing sealing format is HPKE-compatible. [RFC 9180](https://www.rfc-editor.org/rfc/rfc9180.html) + +### 5. Derivatives, declassification, and ordinary tools + +A summary, embedding, classification, or generated tool argument may reveal its source. Changing representation does not establish a safe release. The conservative rule is to propagate the restrictions of all inputs that could influence an output. More precise release requires a justified transformation and the authority to approve it. + +This follows established information-flow work: owners can impose dissemination rules, and declassification requires the relevant authority. The contribution proposed here is applying that discipline to appraised agent endpoints and explicit handoff evidence, rather than introducing a new information-flow theory. [Myers and Liskov, 1997](https://www.cs.cornell.edu/andru/papers/iflow-sosp97/paper.html) + +Suppose the contract-analysis agent needs a public exchange rate. It can query with a currency pair and date if policy authorizes those values to leave. It must not include the contract paragraph merely because the tool call is permitted. A purpose string such as “financial analysis” does not constrain what the remote service does with plaintext. + +There are three defensible outcomes at an ordinary external API: + +- Send no protected information, after an approved transformation or selection establishes the permitted disclosure. +- Obtain authorization for a specified disclosure to that service, recording that the original operator-confidentiality boundary ends there for the released information. +- Refuse the operation. + +A disclosure record should identify the authorized recipient, data category, transformation, approving authority, and resulting confidentiality scope. It must not claim that the recipient inherited a TEE guarantee. Once a recipient has learned a secret, revocation cannot make it forget. Revocation can stop future access; retention enforcement and key destruction can constrain future recovery only within their actual trust assumptions. + +Prompt injection is relevant because it can cause an otherwise approved workload to propose an unauthorized disclosure. A trusted policy engine must evaluate actual destinations and payload restrictions independently of model instructions. Attestation establishes identity and reported state; it does not establish that a language model understands confidentiality correctly. + +### 6. Release authorities, accelerators, and evidence + +**The release authority is part of the boundary.** If an adversary can read model keys or replace the broker's accepted roots and policy, workload appraisal is bypassable. A protected broker needs owner-verifiable identity, protected provisioning, an authorized update path, and rollback resistance. Administrative separation within an organization does not constrain that organization if it can override both administrators. AWS Nitro Enclaves provides a concrete example: KMS key policies can condition operations on enclave measurements, and attested requests receive results encrypted to the public key in the attestation document. The deployment's policy administration remains a separate trust decision. [Nitro Enclaves with AWS KMS](https://docs.aws.amazon.com/enclaves/latest/user/kms.html), [Cryptographic attestation support in AWS KMS](https://docs.aws.amazon.com/kms/latest/developerguide/cryptographic-attestation.html) + +**CPU and GPU evidence must describe the actual path.** Two valid reports do not establish that the measured CPU workload used that GPU, that plaintext crossed a protected link, or that a fallback device was excluded. A common nonce can establish challenge freshness without proving co-location or a protected interconnect. A useful validation binds the transaction, device assignment, accepted modes, protected transport, and operation together. Multi-GPU systems must include their interconnects. NVIDIA documents distinct GPU and switch attestation requirements; device appraisal is one input to this broader validation. [NVIDIA Attestation SDK](https://docs.nvidia.com/attestation/attestation-client-tools-sdk/latest/gpu_and_switch_attestation.html) + +**Evidence is another output channel.** Tool arguments, response bodies, identity chains, and even unkeyed hashes of low-entropy secrets can disclose information. Prefer minimized records with opaque references, policy and decision identifiers, and protected evidence storage. Where equality checking is necessary, consider keyed commitments with explicit key ownership and rotation. Publishing a commitment may still reveal linkage or timing. A transparency log establishes publication properties within its design; it does not prove that omitted events never happened. + +**Time and revocation are bounded assumptions.** Short leases constrain future key release but cannot erase keys already copied by an adversary. Wipe-on-expiry depends on trusted execution, memory handling, and a clock the adversary cannot stall. Verifiers should distinguish historical signature validity from present authorization and should record when revocation services could not answer. + +### 7. Component experiments and their limits + +The evidence below comes from separately pinned software, hosted Linux and cloud experiments conducted September 17 to 19, 2026. They are not one composed run. Test counts identify the scope of a particular execution; they do not measure confidentiality or add together into an assurance score. Synthetic attestation, emulated firmware, fresh hardware capture and owner-side replay remain separate evidence classes. + +#### 7.1 Response authentication + +The opt-in cA2A profile `ca2a-response-mac-v1` binds a response to the complete request occurrence and appraised responder key using X25519, HKDF and HMAC. A strict caller refuses missing, changed or unsupported proofs instead of accepting a legacy reply. The response commitment includes the request digest, status, kind and body. Per-occurrence state consumes verification once; expiry or a restarted verifier does not authorize replay. A submission timeout preserves an unknown execution outcome and does not cause automatic resubmission. + +The implementation passed 57 focused software tests and 691 full-suite tests, with two full-suite skips, at the recorded revision. Its fixture cross-checks key derivation and authentication independently of the runtime helpers. This authenticates provenance or denial to the live caller. It supplies neither confidential output transport nor a portable signed execution receipt, and software-only peer evidence remains software assurance. The caller can compute the same MAC, so a third party cannot treat it as proof of who authored a response. [Response profile and implementation](https://github.com/agentrust-io/ca2a/pull/187) + +#### 7.2 Confinement and a supervision counterexample + +The native-Linux Docker reference places an adversarial agent behind a trusted host bridge and cMCP gateway. Its selected boundary includes a local Docker daemon, disabled container networking, restricted filesystem and process configuration, named tool adapters, and operator-owned sensitivity ceilings. Live policy restrictions serialize cutover with actual dispatch. Synthetic payloads probe network, tool, log, audit and telemetry boundaries. These controls address an agent escaping its configured release path; they do not hide plaintext from the host administrator or an approved tool. + +Review exposed a supervision defect: a successful write and drain into a watchdog pipe does not prove the watcher is responsive. A suspended watcher could leave the container alive while the bridge continued to write. The repair requires an acknowledgment within one second, bounding write, drain and acknowledgment together. If the watcher is paused, the surviving bridge stops the container. The watcher separately stops it after loss of bridge heartbeats, subject to a responsive host and daemon. + +The hosted Linux run passed 61 tests. The paused-watcher control terminated the container; a deliberately weakened write-only implementation left it running, causing the intended stop assertion to fail. Separate bridge kill/pause controls exercised the independent watcher. This causal result supports the tested supervision mechanism, not host-wide recovery, simultaneous loss of bridge and watcher, secure memory erasure or absence of every covert channel. [Confinement implementation](https://github.com/agentrust-io/cmcp/pull/663), [Linux run](https://github.com/agentrust-io/cmcp/actions/runs/35445862949) + +#### 7.3 Provisioning outcomes and continuity + +WCM's owner and receiver bind provisioning to a fresh receiver key, appraised workload, effective configuration, model-key label and policy epoch. A receiver installs one authenticated envelope and refuses release before provisioning or after retirement. Owner epoch storage constrains older policy only while its database and namespace remain authoritative. Storage deletion, substitution, snapshot rollback and separate clones can restore acceptance of an older policy. Tests make this trust assumption observable rather than describing SQLite persistence as rollback resistance. + +The lifecycle experiment added 18 software cases to 118 existing focused controls: 136 passed; the full local run passed 995 tests with 32 skips. Real loopback HTTP exchanges used synthetic signed SNP reports. Lost acknowledgments were induced before and after installation; a false acknowledgment showed that an HTTP receipt does not establish installation. Four deliberate weakenings made their tests fail: nonce consumption, install expiry, installation-proof disclosure and teardown after clock failure. + +A worker control opened a real software release envelope and attached a fake loaded worker through the existing serving-stop adapter. It exercised the declared callbacks and simulated clocks. It did not execute a model in protected hardware or demonstrate real suspend/resume, trusted time, accelerator cleanup or erasure. Retirement and epoch advancement cannot recall an already delivered key. [Lifecycle contract and tests](https://github.com/agentrust-io/weight-custody-manifest/pull/148) + +#### 7.4 Measured-image candidates and cloud counterexamples + +A restricted OVMF candidate and boot loader constrain selected launch inputs. Eleven complete-image TCG cases included a valid boot, malformed or missing launch tables, substituted kernel/initrd/command line/header, a named denied image and a weakened hash-comparison control. The test variants inject synthetic launch state and include diagnostic instrumentation. Passing these controls does not demonstrate that the exact production image enforced the same property on SNP hardware. Actual built-artifact prediction and seven digest-sensitivity controls are also software results. + +A separate Azure SNP/vTPM experiment on September 18 authenticated fresh evidence; seven controls passed on both guest and owner. After a nonsecret probe file changed, the provider still quoted the old caller-supplied digest and the verifier accepted that digest. This counterexample demonstrates that authenticated caller-selected data does not establish measurement or enforcement of the current application. It is not a bypass of the restricted firmware, which was not loaded. + +On GCP, owner-side verification accepted two fresh native SNP reports and rejected five altered-evidence or trust-input cases. A third native provisioning report carried the requested binding. Changing a nonsecret application probe between reports left the launch measurement unchanged. The native collector therefore worked, but neither the unchanged measurement nor the caller binding established the identity or custody of the subsequently installed broker code. Managed guests lacked the launch-host access needed to run the custom firmware. Both clouds' temporary resources were deleted. + +The missing experiment is a launch under owner-approved firmware, kernel, application and configuration, with a predicted measurement chosen before collecting a fresh native report. The controlled-host prerequisite remains open. No stronger application-identity claim is inferred from authentic cloud reports. [Firmware and hardware diagnostic work](https://github.com/agentrust-io/weight-custody-manifest/pull/148) + +#### 7.5 Exact-output disclosure + +The cMCP disclosure adapter requires independently scoped owner approval for an external or less restricted release. An Ed25519 approval binds exact output bytes through their digest, inherited labels, source scope, workload, recipient, purpose, policy version, nonce and validity. Changing the bytes or those bindings requires a new approval. Model-generated labels cannot replace accumulated restrictions. The adapter is an opt-in library path, separate from gateway sink ceilings; it is not a gateway bypass parameter or a semantic classifier. + +A trusted recipient registry selects delivery callbacks. A durable transaction consumes the request identifier before callback execution. An exception or lost acknowledgment leaves delivery unknown and the identifier consumed; a normal return is an acknowledgment, not proof of processing or future confidentiality. Classification, source completeness, transport identity, clocks and replay storage remain trusted. Deleting or rolling back the store defeats its replay guarantee. Approval digests stay private because low-entropy outputs can be guessed. + +The focused run passed 77 tests, the full run 2,046 with 20 skips. Six deliberate gate weakenings each caused an assertion failure. These results support the sampled exact-output checks. They do not establish integration with confinement or automatically justify release of summaries and embeddings. [Disclosure contract and implementation](https://github.com/agentrust-io/cmcp/pull/672) + +#### 7.6 Historical live handoff diagnostic + +On September 17, two temporary GCP AMD Milan SNP guests completed paced cA2A handoffs in both directions: one positive and ten expected rejections per direction, 22 cases total. Receiver-side logs observed hardware caller appraisal and expected nonsecret payload digests. Both guests belonged to one operator and one project. Measurement pins came from authenticated reports, not an independently predicted application image. The reported platform state lacked ciphertext hiding and enabled SMT; the diagnostic explicitly accepted that narrower profile. Stronger policy settings rejected those hosts. + +The executed snapshot used unauthenticated HTTP responses and unsigned receipts. The later response-MAC software tests do not upgrade this historical run. An earlier burst timed out in each direction; the cause remains unisolated. The paced run supports live callee appraisal and selected sealed-delivery rejection paths. Its caller-side appraisal is weaker than the logs suggest: advisory GHSA-xxj2-p57x-x954 (high severity, published October 1, 2026) showed that in ca2a-runtime 0.2.0 to 0.3.1 the callee never required the caller to prove possession of the attested channel key, so a delegate could relay another node's hardware offer and be recorded as hardware-attested. That is the failure condition 1 of section 3 names. Version 0.4.0 separates caller and callee offers and adds a possession proof. The run does not establish independent-operator composition, application confinement, protected model execution or a stronger hardware posture. Both guests and disks were deleted. [Diagnostic and response follow-up](https://github.com/agentrust-io/ca2a/pull/187) + +### 8. Composed software experiment and remaining acceptance + +The pinned development harness executes provisioning, diagnostic model computation, a mediated tool, delegated peer, response verification and exact-output disclosure in one transaction. A native Linux profile inserts a confined adversarial agent at the tool boundary. The recorded hosted run passed 36 tests in 19.24 seconds. This is a synthetic-attestation, same-operator software result. The harness merged into WCM main with pull request 148 on September 20, 2026, and its standalone reproduction bundle with pull request 151 on September 22. [Implementation](https://github.com/agentrust-io/weight-custody-manifest/pull/148), [reproduction bundle](https://github.com/agentrust-io/weight-custody-manifest/pull/151), [recorded hosted run](https://github.com/agentrust-io/weight-custody-manifest/actions/runs/35469947313) + +For this edition the harness was run again at WCM main `94d5519`, with the same cMCP, cA2A and confinement revisions. The hosted workflow on that commit passed 36 tests in 18.76 seconds, then rebuilt the bundle, fetched every source afresh into a new workspace and passed 36 tests in 18.60 seconds. A fresh-environment bundle run on Windows, where the native profile is unavailable, passed the 32 portable tests in 18.56 seconds. Ignoring transaction identifiers and the rebuilt container image digest, every observation record matched the recorded run. Between the two WCM revisions, one mutation helper changed its signature to follow an unrelated key-broker interface change. [Rerun on WCM main](https://github.com/agentrust-io/weight-custody-manifest/actions/runs/36272971255) + +#### 8.1 Executed path and observations + +Test-owned roots sign synthetic SNP evidence. WCM owner provisioning, receiver installation, workload appraisal and sealed key release precede AES-GCM decryption of a nonsecret affine model. A fresh transaction identifier appears inside the encrypted model and its authenticated associated data. The plaintext model digest enters the owner-pinned manifest and provisioning policy. The host computes 3 * 7 + 2 and carries the transaction and value 23 forward. + +A cMCP/Cedar gateway dispatches a JSON-RPC subprocess tool. The actual tool return becomes the sealed cA2A payload to a loopback HTTP peer, whose local policy restricts delegation scope. The caller checks the response MAC and transaction record identifier before a scoped owner approval authorizes exact bytes for the final registered recipient. Controller checks bind these stages; they are trusted application code, not a new interoperable transcript schema. The model manifest is owner-pinned; this fixture does not test a model publisher's signature. + +The native profile gives the already computed bytes to an adversarial Docker agent. It attempts IPv4 TCP, IPv6 TCP, UDP, a filesystem export and stderr disclosure, then requests one permitted and one forbidden tool. The bridge routes the permitted call through the same gateway and stops/removes the agent before the tool result proceeds to the peer and disclosure gate. Tool-side subprocess records, peer-side decrypted bytes and final callback bytes must each equal the expected transaction and value. These are distinct observation points under one test operator, with shared fixtures and controller assumptions. + +#### 8.2 Paired controls and causal results + +| Test group | Cases | Required observation | +|---|---|---| +| Portable valid path | 1 | One authorized delivery; reopening replay storage still refuses reuse. | +| Portable refusal paths | 13 | Selected key, evidence, model, tool, delegation, response or disclosure gate prevents final disclosure. | +| Ambiguous callback outcome | 2 | Failure before or after actual receipt remains unknown; request stays consumed without retry. | +| Portable gate mutations | 16 | Removing the targeted rule changes the observation and makes its intended oracle fail. | +| Native valid path | 1 | Authorized delivery succeeds; selected network, filesystem and persistent-log probes observe no leak. | +| Native isolation mutations | 3 | Weakened networking, filesystem or logging produces the corresponding observed leak. | + +The portable refusal set covers substituted workload, channel key, manifest configuration, encrypted model and transaction; missing evidence or approval; forbidden tool, excess delegation scope and software downgrade; changed response MAC, revoked disclosure authority and changed approved bytes. Sixteen mutations cover these thirteen refusals, replay and both unknown-outcome cases. The runner requires equality between the named scenario set and mutation-target set. This establishes causal coverage of that enumerated set, not every protocol rule, input shape, concurrency schedule or downgrade mechanism. + +Each mutation must first produce its predicted unsafe delivery, replay or falsely acknowledged outcome; the test then requires the corresponding acceptance assertion to fail. For example, the channel-key mutation permits an attacker-owned key to open the release when quote binding is removed. The model mutation removes AES-GCM tag finalization while retaining real decryption, making changed-tag plaintext usable. These controls test consequential behavior rather than merely changing a return label. + +In the native valid case, all three network observers recorded zero matching receipts; filesystem and persistent-log probes found no payload; the forbidden public tool was not invoked; and the agent was stopped. One allowed and one denied gateway operation were observed. Each native mutation was first rejected by normal container-profile inspection. The test then bypassed that inspection to establish causality: host networking yielded one matching receipt at each network observer, a writable filesystem exposed the export, and persistent logging exposed stderr. + +All three weakened native cases still completed authorized final delivery. A successful final receipt therefore cannot establish that earlier plaintext exits were controlled. The isolation assertions detect those earlier leaks separately. This result is specific to the probes and configuration tested; it does not establish absence of other exits or covert channels. + +#### 8.3 What this run does not establish + +The host, kernel, Docker daemon, bridge, broker, model computation, tool and peer remain trusted ordinary processes. The model executes on the host before the agent receives its result. No hardware protects the model key or computation from that operator. Synthetic SNP signatures exercise verification paths against test-owned roots; they do not attest a live workload. The native suite does not repeat every supervisor failure from the separate component experiment. Teardown proves observed process termination, not secure erasure. + +Provisioning and key-release stage labels describe local API observations. They do not establish remote installation under lost acknowledgment; the separate lifecycle experiment addresses that ambiguity. The harness does not provide independent operators, a trusted clock, rollback-proof replay storage, production LLM execution, a protected accelerator path or semantic justification for declassification. No claim about the complete confidentiality boundary follows from the test count. + +On September 19, downloaded PyPI wheels `cmcp-runtime==0.5.0` and `ca2a-runtime==0.2.0` lacked the disclosure and response modules. The later releases `cmcp-runtime==0.6.0` (September 25) and `ca2a-runtime==0.3.1` (September 26) ship `disclosure.py` and `response.py` byte-identical to the pinned source revisions. This is a wheel inspection; the harness has not been run against the released packages. ca2a-runtime 0.3.1 is within the range of advisory GHSA-xxj2-p57x-x954 (section 7.6), so a package-only run should use 0.4.0 or later. The composed harness sends no caller attestation offer and does not exercise that path. The confinement adapter remains in cMCP's example sources and is not in the wheel, and the harness still imports WCM test fixtures. A released-package integration therefore still needs those dependencies packaged or replaced, then a package-only run of the acceptance controls. [Integration acceptance issue](https://github.com/agentrust-io/integrations/issues/199), [contribution rules](https://github.com/agentrust-io/integrations/blob/main/CONTRIBUTING.md) + +#### 8.4 Remaining hardware execution contract + +Status on September 27, 2026: not started. It waits on a controlled SNP launch host (WCM issue 149) or partner-operated execution; identity, lifecycle and protected execution acceptance (WCM issues 144, 145 and 146) remain open. [Controlled-host prerequisite](https://github.com/agentrust-io/weight-custody-manifest/issues/149), [acceptance backlog](https://github.com/agentrust-io/.github/issues/41) + +Begin with an owner-approved, nonsecret deterministic CPU model and one input. This is a diagnostic fixture for the custody path; it is not evidence about production model accuracy or GPU protection. Pin the artifact, runtime, effective policy, launch inputs and channel keys before admission. Use independently supplied trust roots and reference values. A production confidentiality claim additionally requires protected broker key generation, model decryption and loading inside the accepted measured boundary. + +Record distinct observations for authorization, envelope dispatch, receipt, installation, decryption, load, inference, response authentication, disclosure and teardown. Each observation identifies its producer, authenticated transaction binding, revision and evidence class. Report each outcome as established, contradicted or unavailable. Missing instrumentation is unavailable. A timeout after dispatch leaves the remote outcome unknown; it does not imply refusal or justify another execution. + +Observe ciphertext/key release from the owner side and load/inference from the accepted runtime, with an independent receiver or verifier at each external boundary. A trusted runtime receipt can establish only what its measured implementation actually observes. It cannot independently prove erasure, absence of hidden copies or semantic correctness. Public artifacts use opaque references; raw reports, private payloads and guessable confidential digests remain in controlled storage. + +Enumerate plaintext in guest buffers, temporary files, caches, model staging, swap, crash services, logs and outputs. The CPU-only run must declare no accelerator path. A later GPU experiment must bind device identity and accepted mode to the actual protected CPU/device channel and executing model, reject unrelated valid device evidence, and account for multi-device links. A GPU verifier result alone does not satisfy that test. + +#### 8.5 Remaining deployment acceptance matrix + +The matrix below specifies the broader deployment target. Section 8.2 covers selected software cases within it; the remaining platform, concurrency, runtime-change and lifecycle cases need deployment evidence. Every row needs a successful valid control, observation at the operation boundary, and a deliberate weakening that makes the intended rejection assertion fail. If an earlier parser or schema blocks a case, that case does not demonstrate the later gate. Mutations and acceptance oracles must be recorded separately. + +| Change under test | Boundary observation required | Status, September 27, 2026 | +|---|---|---| +| Substitute workload or effective configuration | Owner withholds the key before provisioning to the substituted image. | Software control and mutation passed (synthetic attestation). Controlled hardware: not started. | +| Substitute recipient or channel key | No protected bytes reach the substituted endpoint. | Software control and mutation passed. Controlled hardware: not started. | +| Downgrade software or platform mode | Admission refuses before key release or payload sealing. | Software downgrade passed. Platform mode: GCP diagnostic hosts were rejected under stronger policy; no controlled-host test. | +| Substitute model or runtime after appraisal | Decryption/load refuses the substituted artifact before execution. | Changed encrypted model refused in software. Runtime substitution after appraisal: not tested. | +| Replay across concurrency or restart | Authoritative state rejects reuse; snapshot assumptions remain explicit. | Sequential replay after reopening storage refused in software. Concurrency and rollback-proof storage: not tested. | +| Omit required evidence | No fallback promotes missing proof to established assurance. | Software control and mutation passed. Controlled hardware: not started. | +| Attempt direct egress or forbidden disclosure | Independent sink observes no protected delivery; valid authorized delivery succeeds. | Native Linux profile passed with same-operator sinks. Independently operated sinks: not started. | +| Revoke after approval and before use | Future admission stops under the stated cutover rule; in-flight outcomes remain separate. | Revoked disclosure authority refused in software. Deployment cutover: not tested. | +| Lose acknowledgment after dispatch | Sender reports unknown; no blind retry; independent receiver distinguishes the two actual outcomes. | Software callback and loopback HTTP cases passed. Independent receiver: not started. | +| Fail clock, supervisor or teardown | Declared admission/stop behavior is observed; inability to establish cleanup remains visible. | Supervisor pause and simulated clock failure passed in software. Trusted clock and hardware teardown: not started. | + +#### 8.6 Reproducibility and closure + +A run manifest must contain the selected claim, asset and principal roles, permitted recipients, trusted components, policy/trust-input references, transaction-binding format, exact source and package digests, commands and observation producers. Each result retains its own evidence class: synthetic, emulated, replayed hardware or fresh hardware. Fresh hardware in one component never promotes another component's synthetic result. Sanitization must preserve verifiability or state what the public reader cannot reproduce. + +The development harness retains source revisions, harness hashes, installed dependency versions, commands, policy and trust descriptions, and 36 minimized observation records. Its environment is recorded per run, not completely hash-locked. The published software milestone additionally requires installable fixture and confinement-adapter support and repetition of the acceptance controls without source-tree imports; the disclosure and response APIs are now released. The live milestone additionally needs approved measured application identity, validated hardware lifecycle and independently operated peers. WCM source is publicly accessible as of September 20, 2026. Portions of the hardware evidence remain access-controlled; the source-based software bundle does not reproduce those private observations. The parent research claim remains open until one selected deployment satisfies the composed criteria. The live milestone requires hardware access; the published software milestone requires packaging of the remaining development-only dependencies. + +### 9. Findings and open questions + +The experiments identify three specific ways an apparently successful check can overstate a confidentiality boundary. A fresh authenticated quote can carry a stale caller-chosen application digest. A successful heartbeat write can coexist with an unresponsive supervisor. A successful or missing HTTP acknowledgment can fail to establish key installation. Exact-output approval and request-bound response authentication address additional software boundaries, but neither proves the complete workflow. + +The conditional composition argument still depends on measured application identity, key confinement, complete mediation and continuity at every plaintext holder. Those premises have not been established together. The composed software result establishes selected admission and egress controls within a trusted-host workflow. The next empirical target is moving key handling and model execution into an owner-appraised controlled launch, followed by independent-peer reproduction. The semantic research question remains how to justify narrower releases of model-derived information without treating the model's own judgment as authorization. + +### Appendix A. Revision and reproduction ledger + +The following are evidence snapshots, not interchangeable descriptions of a latest branch. Review and merge state is maintained in the project tracker rather than used as an assurance level. WCM repository links are publicly accessible as of September 20, 2026; hardware evidence access remains separate. + +| Result | Source snapshot | Reproduction entry point | +|---|---|---| +| Response authentication | cA2A `fc22c6448461` | `tests/unit/test_response_authentication.py`; response-MAC fixture and profile. | +| Supervision repair | cMCP `bad751becca0` | Confinement workflow run `35445862949`; 61 native Linux tests. | +| Provisioning lifecycle | WCM `db8f10638bb9` | `python/tests/test_provisioning_lifecycle.py`; full suite 995 passed, 32 skipped. | +| Firmware prediction controls | WCM `050002114123` | `python/boot/prediction_controls.py`; retained built-image and VM artifacts. | +| Azure hardware diagnostic | WCM `1f5092256f7f` | September 18 capture and owner replay; diagnostic recorded separately in `af5468e70964`. | +| GCP native diagnostic | WCM `af5468e70964` | September 18 native reports and owner replay; receipt update `dc5dd52`. | +| Exact-output disclosure | cMCP `2cdb168ce520` | `tests/unit/test_disclosure.py`; disclosure contract and mutation records. | +| Composed software and confined agent | WCM PR head `f168aa861c0e`; tested merge `15e67c2fb75b` | Workflow run `35469947313`; `python/composed/run.py`; 36 tests. | +| Composed rerun for this edition | WCM main `94d5519fd604` | Workflow run `36272971255`; `python/composed/reproduce.py`; 36 tests hosted, 32 portable on Windows. | + +The composed run used cMCP `2cdb168ce520`, cA2A `fc22c6448461` and confinement adapter `bad751becca0`. Its manifest records full revisions and the built container image identity. The WCM test-merge revision `15e67c2fb75b` differs from the reviewed PR head because GitHub executed its generated merge checkout. The September 27 rerun used the same cMCP, cA2A and confinement revisions. + +The canonical implementation record retains full revisions, commands, outputs, private-evidence locations and cleanup receipts. The paper reports selected bounded results. It does not redistribute device-identifying hardware bundles, private keys or protected data. A reviewer without the private evidence can assess the public software tests and the stated limits, but cannot independently reproduce every reported hardware observation from this PDF alone. diff --git a/papers/handoff-response-authentication/README.md b/papers/handoff-response-authentication/README.md new file mode 100644 index 0000000..799c607 --- /dev/null +++ b/papers/handoff-response-authentication/README.md @@ -0,0 +1,28 @@ +# Authenticating the Answer in an Agent Handoff + +Technical report, version 1, October 1, 2026. Imran Siddique, OPAQUE Systems. Not peer reviewed. + +Build: `python build_paper.py --source . --output paper.pdf` (ReportLab 5.0.1 and PyMuPDF +1.28.2). It reads Times New Roman, Arial and Courier New TrueType files from `--fonts` +(default `C:/Windows/Fonts`); fonts are not bundled. `manifest.json` lists the SHA-256 of +every other file in this package. + +Paper text is licensed CC BY 4.0: https://creativecommons.org/licenses/by/4.0/ +Project code retains its existing project license; see https://github.com/agentrust-io/ca2a. + +The report evaluates cA2A fc22c644846111c7f375446399e9097a73f93214 (head of PR 187; its squash +merge 762f329 on main has the identical tree). Reproduce with Python 3.12 from this directory: + + git clone https://github.com/agentrust-io/ca2a.git subjects/ca2a + git -C subjects/ca2a fetch origin fc22c644846111c7f375446399e9097a73f93214 + git -C subjects/ca2a checkout --detach fc22c644846111c7f375446399e9097a73f93214 + python -m venv .venv + .venv/Scripts/python -m pip install --require-hashes -r subjects/ca2a/requirements/dev.txt + .venv/Scripts/python -B experiments/run.py --source subjects/ca2a --output fresh-results + +If the PR head commit is unavailable, pass `--commit 762f329aeae589c28219b2cdc2d4a5d6e41407be`. +`experiments/results/rerun-2026-10-01/` holds the October 1 rerun and +`experiments/results/main-19407fb-2026-10-01/` the same run against cA2A main. + +Not run: peer-key rotation on attested hardware and Internet transport. +Current implementation guidance: https://ca2a.agentrust-io.com/ diff --git a/papers/handoff-response-authentication/build_paper.py b/papers/handoff-response-authentication/build_paper.py new file mode 100644 index 0000000..ae000ff --- /dev/null +++ b/papers/handoff-response-authentication/build_paper.py @@ -0,0 +1,170 @@ +"""Build paper.pdf from paper.md with ReportLab and embedded TrueType fonts. + +Usage: python build_paper.py [--source DIR] [--output paper.pdf] [--fonts DIR] + +Requires reportlab and pymupdf. Fonts come from --fonts (default C:/Windows/Fonts): +times.ttf, timesbd.ttf, timesi.ttf, arial.ttf and cour.ttf. Adapted from the +confidential-handoffs builder, adding a subtitle, code blocks, inline code and +tables with any column count. Render and inspect every page after a rebuild. +""" +import argparse +from functools import partial +from html import escape +from pathlib import Path +import re + +from reportlab.lib import colors +from reportlab.lib.enums import TA_LEFT +from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet +from reportlab.lib.units import inch +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfbase.ttfonts import TTFont +from reportlab.pdfgen.canvas import Canvas +from reportlab.platypus import Paragraph, Preformatted, SimpleDocTemplate, Spacer, Table, TableStyle + +FOOTER = "OPAQUE Systems | Technical report, version 1 | Software experiments; no hardware claims" +p = argparse.ArgumentParser() +p.add_argument("--source", type=Path, default=Path(__file__).resolve().parent) +p.add_argument("--output", type=Path, default=None) +p.add_argument("--fonts", type=Path, default=Path("C:/Windows/Fonts")) +a = p.parse_args() +ROOT = a.source +OUT = a.output or ROOT / "paper.pdf" +for name, filename in [("PaperSerif", "times.ttf"), ("PaperBold", "timesbd.ttf"), ("PaperItalic", "timesi.ttf"), + ("PaperSans", "arial.ttf"), ("PaperMono", "cour.ttf")]: + pdfmetrics.registerFont(TTFont(name, str(a.fonts / filename))) +pdfmetrics.registerFontFamily("PaperSerif", normal="PaperSerif", bold="PaperBold", italic="PaperItalic", + boldItalic="PaperBold") +styles = getSampleStyleSheet() +styles.add(ParagraphStyle(name="PaperTitle", fontName="PaperBold", fontSize=23, leading=27, spaceAfter=6, + textColor=colors.HexColor("#15334b"))) +styles.add(ParagraphStyle(name="PaperSubtitle", fontName="PaperItalic", fontSize=13.5, leading=17, spaceAfter=12, + textColor=colors.HexColor("#2d4a61"))) +styles.add(ParagraphStyle(name="PaperHeading", fontName="PaperBold", fontSize=13, leading=17, spaceBefore=13, + spaceAfter=6, keepWithNext=True)) +styles.add(ParagraphStyle(name="PaperSubheading", fontName="PaperBold", fontSize=11.2, leading=15, spaceBefore=9, + spaceAfter=4, keepWithNext=True)) +styles.add(ParagraphStyle(name="PaperBody", fontName="PaperSerif", fontSize=10.6, leading=14.3, spaceAfter=6, + alignment=TA_LEFT, splitLongWords=True)) +styles.add(ParagraphStyle(name="PaperRef", parent=styles["PaperBody"], fontSize=9.6, leading=12.6, leftIndent=18, + firstLineIndent=-18)) +styles.add(ParagraphStyle(name="PaperCell", fontName="PaperSerif", fontSize=8.8, leading=11, spaceAfter=1, + splitLongWords=True)) +styles.add(ParagraphStyle(name="PaperCode", fontName="PaperMono", fontSize=8.2, leading=10.4, leftIndent=8, + spaceBefore=2, spaceAfter=8, textColor=colors.HexColor("#1d2d3a"))) + + +def markup(text): + out, last = [], 0 + for m in re.finditer(r"\[([^\]]+)\]\((https?://[^)]+)\)", text): + out.append(_plain(text[last:m.start()])) + out.append(f'{_plain(m.group(1))}') + last = m.end() + out.append(_plain(text[last:])) + return "".join(out) + + +def _plain(text): + t = escape(text, quote=False) + t = re.sub(r"https://[^\s<]+[^\s<.,)]", lambda m: f'{m[0]}', t) + t = re.sub(r"\*\*([^*]+)\*\*", r"\1", t) + t = re.sub(r"(?\1", t) + t = re.sub(r"`([^`]+)`", r'\1', t) + return t + + +def footer(canvas, doc): + canvas.saveState() + canvas.setStrokeColor(colors.HexColor("#ccd5dc")) + canvas.line(0.8 * inch, 0.57 * inch, 7.7 * inch, 0.57 * inch) + canvas.setFont("PaperSans", 8) + canvas.setFillColor(colors.HexColor("#52616e")) + canvas.drawString(0.8 * inch, 0.4 * inch, FOOTER) + canvas.drawRightString(7.7 * inch, 0.4 * inch, str(doc.page)) + canvas.restoreState() + + +def table(lines): + rows = [] + for line in lines: + cells = [v.strip() for v in line.strip().strip("|").split("|")] + if not all(re.fullmatch(r"[-: ]+", c) for c in cells): + rows.append([Paragraph(markup(c), styles["PaperCell"]) for c in cells]) + n = len(rows[0]) + weights = [max(len(r[i].text) for r in rows) ** 0.5 + 4 for i in range(n)] + widths = [6.9 * inch * w / sum(weights) for w in weights] + t = Table(rows, colWidths=widths, repeatRows=1) + t.setStyle(TableStyle([ + ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#e5edf3")), + ("FONTNAME", (0, 0), (-1, 0), "PaperBold"), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("LEFTPADDING", (0, 0), (-1, -1), 5), ("RIGHTPADDING", (0, 0), (-1, -1), 5), + ("TOPPADDING", (0, 0), (-1, -1), 4), ("BOTTOMPADDING", (0, 0), (-1, -1), 4), + ("LINEBELOW", (0, 0), (-1, 0), 0.7, colors.HexColor("#9aaebc")), + ("LINEBELOW", (0, 1), (-1, -1), 0.25, colors.HexColor("#d9e0e5")), + ])) + return t + + +def build(): + lines = (ROOT / "paper.md").read_text(encoding="utf-8-sig").splitlines() + story, title, i, in_refs = [], "", 0, False + while i < len(lines): + line = lines[i].strip() + if not line: + i += 1 + continue + if line.startswith("```"): + block = [] + i += 1 + while i < len(lines) and not lines[i].strip().startswith("```"): + block.append(lines[i]) + i += 1 + story.append(Preformatted("\n".join(block), styles["PaperCode"])) + i += 1 + continue + if line.startswith("|"): + block = [] + while i < len(lines) and lines[i].strip().startswith("|"): + block.append(lines[i]) + i += 1 + story.extend([table(block), Spacer(1, 8)]) + continue + if line.startswith("# "): + title = line[2:] + story.append(Paragraph(markup(title), styles["PaperTitle"])) + elif line.startswith("## "): + story.append(Paragraph(markup(line[3:]), styles["PaperSubtitle"])) + elif line.startswith("### "): + in_refs = line[4:].strip() == "References" + story.append(Paragraph(markup(line[4:]), styles["PaperHeading"])) + elif line.startswith("#### "): + story.append(Paragraph(markup(line[5:]), styles["PaperSubheading"])) + elif line.startswith("- "): + story.append(Paragraph("• " + markup(line[2:]), styles["PaperBody"])) + else: + paragraph = [line] + while (i + 1 < len(lines) and lines[i + 1].strip() + and not lines[i + 1].lstrip().startswith(("#", "|", "- ", "```")) + and not (in_refs and lines[i + 1].startswith("["))): + i += 1 + paragraph.append(lines[i].strip()) + story.append(Paragraph(markup(" ".join(paragraph)), styles["PaperRef" if in_refs else "PaperBody"])) + i += 1 + doc = SimpleDocTemplate(str(OUT), pagesize=(8.5 * inch, 11 * inch), rightMargin=0.8 * inch, + leftMargin=0.8 * inch, topMargin=0.65 * inch, bottomMargin=0.8 * inch, title=title, + author="Imran Siddique", pageCompression=1, invariant=1) + doc.build(story, onFirstPage=footer, onLaterPages=footer, + canvasmaker=partial(Canvas, initialFontName="PaperSans")) + + +if __name__ == "__main__": + build() + # Remove ReportLab's unused base-font resource; keep the embedded font streams. + import pymupdf + with pymupdf.open(OUT) as pdf: + for page in pdf: + page.clean_contents() + cleaned = pdf.tobytes(garbage=4, deflate=True) + OUT.write_bytes(cleaned) + print(OUT) diff --git a/papers/handoff-response-authentication/experiments/race_and_caller_mac.py b/papers/handoff-response-authentication/experiments/race_and_caller_mac.py new file mode 100644 index 0000000..5f5c7bb --- /dev/null +++ b/papers/handoff-response-authentication/experiments/race_and_caller_mac.py @@ -0,0 +1,53 @@ +"""Concurrent verification and the caller-constructed MAC. + +Twenty rounds of 16 concurrent verifications of one valid response on eight worker threads +must each expose exactly one accepted response. Then the caller builds a response statement +with its own derived key and its verifier accepts it: the expected counterexample to reading +the MAC as transferable proof of peer authorship. The second part reads a private attribute +(_key) on purpose; that access is a test seam, not an attacker capability. + +Usage: race_and_caller_mac.py SCRATCH (run with the pinned cA2A source on PYTHONPATH) +""" +from concurrent.futures import ThreadPoolExecutor +import hmac +import json +from pathlib import Path +import sys + + +def response() -> dict: + from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey + from ca2a_runtime.attestation import VerifiedPeer + from ca2a_runtime.response import (PendingResponse, ResponseAuthenticationFailed, ResponseProducer, + decode, encode) + private = X25519PrivateKey.generate() + peer = VerifiedPeer(private.public_key().public_bytes_raw().hex(), "none", "synthetic") + counts = [] + for _ in range(20): + pending = PendingResponse(peer, {"task": "synthetic"}) + raw = encode(ResponseProducer(private, decode(pending.request_bytes)).finish(200, {"accepted": True})) + + def consume(_): + try: + pending.verify(200, raw) + return True + except ResponseAuthenticationFailed: + return False + with ThreadPoolExecutor(max_workers=8) as pool: + count = sum(pool.map(consume, range(16))) + assert count == 1 + counts.append(count) + pending = PendingResponse(peer, {"task": "synthetic forgery demonstration"}) + statement = {"profile": decode(pending.request_bytes)["profile"], + "request_sha256": pending.request_sha256, "status": 200, "kind": "provenance", + "body": {"accepted": True, "note": "constructed locally by caller"}} + mac = hmac.new(pending._key, b"ca2a/response-mac/v1/statement\0" + encode(statement), "sha256").hexdigest() + assert pending.verify(200, encode({**statement, "mac": mac})).body["accepted"] + return {"rounds": 20, "attempts": 320, "successes_per_round": counts, + "caller_constructed_mac_accepted": True, + "limit": "Local caller authentication is not transferable evidence of peer authorship."} + + +if __name__ == "__main__": + Path(sys.argv[1]).mkdir(parents=True) + print(json.dumps(response(), indent=2)) diff --git a/papers/handoff-response-authentication/experiments/reference-2026-09.json b/papers/handoff-response-authentication/experiments/reference-2026-09.json new file mode 100644 index 0000000..61471b1 --- /dev/null +++ b/papers/handoff-response-authentication/experiments/reference-2026-09.json @@ -0,0 +1,207 @@ +{ + "note": "Recorded results this rerun is compared against: the selected tests and control removals of September 23, 2026, and the experiments of September 27, 2026, at the same source pin. Extracted from the original run records; timings are informational. Experiment labels were renamed to match this directory; the comparison ignores them.", + "pytest": { + "baseline": { + "exit_code": 0, + "tests": 57, + "failures": 0, + "errors": 0, + "skipped": 0, + "failed_cases": [] + }, + "response-mac": { + "exit_code": 1, + "tests": 57, + "failures": 4, + "errors": 0, + "skipped": 0, + "failed_cases": [ + "test_changed_response_is_not_exposed[body-value0]", + "test_changed_response_is_not_exposed[mac-0000000000000000000000000000000000000000000000000000000000000000]", + "test_real_http_substituted_response_never_reaches_caller[body]", + "test_committed_language_neutral_vector_and_negative_twins" + ] + }, + "consumption": { + "exit_code": 1, + "tests": 57, + "failures": 3, + "errors": 0, + "skipped": 0, + "failed_cases": [ + "test_valid_response_and_one_use", + "test_concurrent_consumption_has_exactly_one_success", + "test_invalid_response_consumes_occurrence" + ] + } + }, + "boundary": { + "rounds": 20, + "attempts": 320, + "successes_per_round": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "caller_constructed_mac_accepted": true, + "limit": "Local caller authentication is not transferable evidence of peer authorship." + }, + "experiment": { + "experiment": "transport-cut", + "cases": [ + { + "mode": "pass", + "caller_outcome": "accepted", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-pass": "accepted" + }, + "captured_response_replay": "refused", + "ok": true + }, + { + "mode": "cut_before_dispatch", + "caller_outcome": "unknown", + "peer_executions": 0, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-cut_before_dispatch": "unknown" + }, + "captured_response_replay": "no-capture", + "ok": true + }, + { + "mode": "cut_after_dispatch", + "caller_outcome": "unknown", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-cut_after_dispatch": "unknown" + }, + "captured_response_replay": "refused", + "ok": true + }, + { + "mode": "truncate", + "caller_outcome": "unknown", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-truncate": "unknown" + }, + "captured_response_replay": "refused", + "ok": true + }, + { + "mode": "hold", + "caller_outcome": "caller-killed", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-hold": "unknown" + }, + "captured_response_replay": "refused", + "ok": true + } + ], + "cases_passed": 5, + "cases_total": 5, + "criterion_met": true + }, + "controls": { + "resend-on-transport-failure": { + "experiment": "transport-cut", + "cases": [ + { + "mode": "pass", + "caller_outcome": "accepted", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-pass": "accepted" + }, + "captured_response_replay": "refused", + "ok": true + }, + { + "mode": "cut_before_dispatch", + "caller_outcome": "unknown", + "peer_executions": 0, + "submissions_seen_by_relay": 2, + "after_restart": { + "business-op-cut_before_dispatch": "unknown" + }, + "captured_response_replay": "no-capture", + "ok": false + }, + { + "mode": "cut_after_dispatch", + "caller_outcome": "unknown", + "peer_executions": 2, + "submissions_seen_by_relay": 2, + "after_restart": { + "business-op-cut_after_dispatch": "unknown" + }, + "captured_response_replay": "refused", + "ok": false + }, + { + "mode": "truncate", + "caller_outcome": "unknown", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-truncate": "unknown" + }, + "captured_response_replay": "refused", + "ok": true + }, + { + "mode": "hold", + "caller_outcome": "caller-killed", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-hold": "unknown" + }, + "captured_response_replay": "refused", + "ok": true + } + ], + "cases_passed": 3, + "cases_total": 5, + "criterion_met": false + } + }, + "recorded_versions_2026_09_23": { + "pytest": "9.1.1", + "pytest-asyncio": "1.4.0", + "cryptography": "50.0.1", + "pydantic": "2.13.5", + "rfc8785": "0.1.4", + "agent-manifest": "0.12.0", + "agentrust-trace": "0.5.0", + "cedarpy": "4.8.7", + "httpx": "0.28.1", + "starlette": "1.3.1" + }, + "recorded_python_2026_09_23": "3.12.14" +} \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/SHA256SUMS.json b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/SHA256SUMS.json new file mode 100644 index 0000000..42ea4d0 --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/SHA256SUMS.json @@ -0,0 +1,13 @@ +{ + "commands.txt": "09b030d1873c1632691fef78ea4550f054839a70614161ceac55203f1454360e", + "control-resend-on-transport-failure.json": "76869e36c4e2f734fadb18d51890ab319c1d8364dfc531e10731d3dd4e5fd403", + "pytest-baseline.log": "87913ff2c47b45e037f8f7ef7fac63f0a7e0ef4733718f5cac35fc6b170d4b0e", + "pytest-baseline.xml": "c319e47ee4bbd97c1a577846db314806c997f2074b12c73e5447010bd2d2c691", + "pytest-consumption.log": "9fa05122cb673c37befc8397fb7bcd6d3d7211a6e1faa86284babbea916c5c70", + "pytest-consumption.xml": "9758e1a009ed05402fe8e41807f7a6eb9307d1345f3d96611236fcf27ca2d3eb", + "pytest-response-mac.log": "ad1d4107067603868219d1bd00bb192b57d8ce3859f0aa7e98ec5893ddb81bd1", + "pytest-response-mac.xml": "7114f08c4c6e39f8e422783e4a62b66e3337ce7014f43a95ef75d2e65a5abe89", + "race-and-caller-mac.json": "f810ef7145c2371f36681737b4ae0a614fdc179d66bb532088efbe3ec87fe9fd", + "run.json": "4839df5a71fab27b768e26bb364b46360ac5b2dbb7ccbfdb41b9f5f92fc461a8", + "transport-cut.json": "b4908d253bd45fcc073306df3d612dc32450be6bb695d7c5285d63ac03a51ea1" +} \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/commands.txt b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/commands.txt new file mode 100644 index 0000000..0b2b3d2 --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/commands.txt @@ -0,0 +1,13 @@ +# Environment (Python 3.12), from a clone of agentrust-io/ca2a +git -C checkout --detach 19407fb52f4ce5fa81db322d75908c9d9f17d862 +python -m venv +/Scripts/python -m pip install --require-hashes -r /requirements/dev.txt +# Run (from this report's directory) +/Scripts/python -B experiments/run.py --source --commit 19407fb52f4ce5fa81db322d75908c9d9f17d862 --output +# Individual commands, as executed +python -B -m pytest -p no:cacheprovider -p pytest_asyncio.plugin -q --tb=short --basetemp=\baseline-tmp --junitxml=\pytest-baseline.xml tests/unit/test_response_authentication.py +python -B -m pytest -p no:cacheprovider -p pytest_asyncio.plugin -q --tb=short --basetemp=\response-mac-tmp --junitxml=\pytest-response-mac.xml tests/unit/test_response_authentication.py +python -B -m pytest -p no:cacheprovider -p pytest_asyncio.plugin -q --tb=short --basetemp=\consumption-tmp --junitxml=\pytest-consumption.xml tests/unit/test_response_authentication.py +python -B experiments/race_and_caller_mac.py +python -B experiments/transport_cut.py run +python -B experiments/transport_cut.py run diff --git a/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/control-resend-on-transport-failure.json b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/control-resend-on-transport-failure.json new file mode 100644 index 0000000..1058c9f --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/control-resend-on-transport-failure.json @@ -0,0 +1,63 @@ +{ + "experiment": "transport-cut", + "cases": [ + { + "mode": "pass", + "caller_outcome": "accepted", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-pass": "accepted" + }, + "captured_response_replay": "refused", + "ok": true + }, + { + "mode": "cut_before_dispatch", + "caller_outcome": "unknown", + "peer_executions": 0, + "submissions_seen_by_relay": 2, + "after_restart": { + "business-op-cut_before_dispatch": "unknown" + }, + "captured_response_replay": "no-capture", + "ok": false + }, + { + "mode": "cut_after_dispatch", + "caller_outcome": "unknown", + "peer_executions": 2, + "submissions_seen_by_relay": 2, + "after_restart": { + "business-op-cut_after_dispatch": "unknown" + }, + "captured_response_replay": "refused", + "ok": false + }, + { + "mode": "truncate", + "caller_outcome": "unknown", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-truncate": "unknown" + }, + "captured_response_replay": "refused", + "ok": true + }, + { + "mode": "hold", + "caller_outcome": "caller-killed", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-hold": "unknown" + }, + "captured_response_replay": "refused", + "ok": true + } + ], + "cases_passed": 3, + "cases_total": 5, + "criterion_met": false +} \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-baseline.log b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-baseline.log new file mode 100644 index 0000000..2ed8442 --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-baseline.log @@ -0,0 +1,14 @@ +============================= test session starts ============================= +platform win32 -- Python 3.12.10, pytest-9.1.1, pluggy-1.6.0 +rootdir: \ca2a +configfile: pyproject.toml +asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function +collected 57 items + +tests\unit\test_response_authentication.py ............................. [ 50%] +............................ [100%] + +- generated xml file: \pytest-baseline.xml - +============================= 57 passed in 5.45s ============================== + +STDERR diff --git a/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-baseline.xml b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-baseline.xml new file mode 100644 index 0000000..0d9f4ff --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-baseline.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-consumption.log b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-consumption.log new file mode 100644 index 0000000..8a9e863 --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-consumption.log @@ -0,0 +1,37 @@ +============================= test session starts ============================= +platform win32 -- Python 3.12.10, pytest-9.1.1, pluggy-1.6.0 +rootdir: \copy-consumption +configfile: pyproject.toml +asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function +collected 57 items + +tests\unit\test_response_authentication.py F.....................F...... [ 50%] +...F........................ [100%] + +================================== FAILURES =================================== +_______________________ test_valid_response_and_one_use _______________________ +tests\unit\test_response_authentication.py:53: in test_valid_response_and_one_use + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed +_____________ test_concurrent_consumption_has_exactly_one_success _____________ +tests\unit\test_response_authentication.py:147: in test_concurrent_consumption_has_exactly_one_success + assert sum(pool.map(consume, range(16))) == 1 +E assert 16 == 1 +E + where 16 = sum(.result_iterator at 0x0000026B383DEF20>) +E + where .result_iterator at 0x0000026B383DEF20> = map(.consume at 0x0000026B384DEAC0>, range(0, 16)) +E + where map = .map +E + and range(0, 16) = range(16) +__________________ test_invalid_response_consumes_occurrence __________________ +tests\unit\test_response_authentication.py:174: in test_invalid_response_consumes_occurrence + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed +- generated xml file: \pytest-consumption.xml - +=========================== short test summary info =========================== +FAILED tests/unit/test_response_authentication.py::test_valid_response_and_one_use +FAILED tests/unit/test_response_authentication.py::test_concurrent_consumption_has_exactly_one_success +FAILED tests/unit/test_response_authentication.py::test_invalid_response_consumes_occurrence +======================== 3 failed, 54 passed in 6.19s ========================= + +STDERR diff --git a/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-consumption.xml b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-consumption.xml new file mode 100644 index 0000000..b32deef --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-consumption.xml @@ -0,0 +1,13 @@ +tests\unit\test_response_authentication.py:53: in test_valid_response_and_one_use + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailedtests\unit\test_response_authentication.py:147: in test_concurrent_consumption_has_exactly_one_success + assert sum(pool.map(consume, range(16))) == 1 +E assert 16 == 1 +E + where 16 = sum(<generator object Executor.map.<locals>.result_iterator at 0x0000026B383DEF20>) +E + where <generator object Executor.map.<locals>.result_iterator at 0x0000026B383DEF20> = map(<function test_concurrent_consumption_has_exactly_one_success.<locals>.consume at 0x0000026B384DEAC0>, range(0, 16)) +E + where map = <concurrent.futures.thread.ThreadPoolExecutor object at 0x0000026B38460E00>.map +E + and range(0, 16) = range(16)tests\unit\test_response_authentication.py:174: in test_invalid_response_consumes_occurrence + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-response-mac.log b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-response-mac.log new file mode 100644 index 0000000..8b2d111 --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-response-mac.log @@ -0,0 +1,40 @@ +============================= test session starts ============================= +platform win32 -- Python 3.12.10, pytest-9.1.1, pluggy-1.6.0 +rootdir: \copy-response-mac +configfile: pyproject.toml +asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function +collected 57 items + +tests\unit\test_response_authentication.py .F....F...................... [ 50%] +.............F.............F [100%] + +================================== FAILURES =================================== +______________ test_changed_response_is_not_exposed[body-value0] ______________ +tests\unit\test_response_authentication.py:72: in test_changed_response_is_not_exposed + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed +_ test_changed_response_is_not_exposed[mac-0000000000000000000000000000000000000000000000000000000000000000] _ +tests\unit\test_response_authentication.py:72: in test_changed_response_is_not_exposed + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed +_______ test_real_http_substituted_response_never_reaches_caller[body] ________ +tests\unit\test_response_authentication.py:311: in test_real_http_substituted_response_never_reaches_caller + with pytest.raises(ResponseAuthenticationFailed, match="unknown"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed +__________ test_committed_language_neutral_vector_and_negative_twins __________ +tests\unit\test_response_authentication.py:451: in test_committed_language_neutral_vector_and_negative_twins + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed +- generated xml file: \pytest-response-mac.xml - +=========================== short test summary info =========================== +FAILED tests/unit/test_response_authentication.py::test_changed_response_is_not_exposed[body-value0] +FAILED tests/unit/test_response_authentication.py::test_changed_response_is_not_exposed[mac-0000000000000000000000000000000000000000000000000000000000000000] +FAILED tests/unit/test_response_authentication.py::test_real_http_substituted_response_never_reaches_caller[body] +FAILED tests/unit/test_response_authentication.py::test_committed_language_neutral_vector_and_negative_twins +======================== 4 failed, 53 passed in 6.13s ========================= + +STDERR diff --git a/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-response-mac.xml b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-response-mac.xml new file mode 100644 index 0000000..1ae9346 --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/pytest-response-mac.xml @@ -0,0 +1,13 @@ +tests\unit\test_response_authentication.py:72: in test_changed_response_is_not_exposed + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailedtests\unit\test_response_authentication.py:72: in test_changed_response_is_not_exposed + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailedtests\unit\test_response_authentication.py:311: in test_real_http_substituted_response_never_reaches_caller + with pytest.raises(ResponseAuthenticationFailed, match="unknown"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailedtests\unit\test_response_authentication.py:451: in test_committed_language_neutral_vector_and_negative_twins + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/race-and-caller-mac.json b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/race-and-caller-mac.json new file mode 100644 index 0000000..9df7d5e --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/race-and-caller-mac.json @@ -0,0 +1,28 @@ +{ + "rounds": 20, + "attempts": 320, + "successes_per_round": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "caller_constructed_mac_accepted": true, + "limit": "Local caller authentication is not transferable evidence of peer authorship." +} \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/run.json b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/run.json new file mode 100644 index 0000000..57e931a --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/run.json @@ -0,0 +1,246 @@ +{ + "started_utc": "2026-10-01T18:41:43Z", + "subject": "agentrust-io/ca2a", + "commit": "19407fb52f4ce5fa81db322d75908c9d9f17d862", + "tree": "5bf0212d466e2e7e4e0e197599fbafe6e02974bd", + "python": "3.12.10", + "platform": "Windows-11-10.0.26200-SP0", + "package_versions": { + "pytest": "9.1.1", + "pytest-asyncio": "1.4.0", + "cryptography": "50.0.1", + "pydantic": "2.13.5", + "rfc8785": "0.1.4", + "agent-manifest": "0.12.0", + "agentrust-trace": "0.10.0", + "cedarpy": "4.12.1", + "pyyaml": "6.0.3" + }, + "installed_distributions": { + "a2a-sdk": "1.1.5", + "agentrust-trace": "0.10.0", + "agentrust-trace-tests": "0.5.1", + "agent-manifest": "0.12.0", + "aiohappyeyeballs": "2.7.1", + "aiohttp": "3.14.3", + "aiologic": "0.17.1", + "aiosignal": "1.4.0", + "annotated-types": "0.8.0", + "anyio": "4.15.1", + "ast_serialize": "0.9.0", + "attrs": "26.1.0", + "bandit": "1.9.4", + "boolean.py": "5.0", + "CacheControl": "0.14.4", + "cbor2": "6.1.4", + "cedarpy": "4.12.1", + "certifi": "2026.7.22", + "cffi": "2.1.1", + "charset-normalizer": "3.5.1", + "click": "8.5.0", + "colorama": "0.4.6", + "coverage": "7.16.0", + "cryptography": "50.0.1", + "culsans": "0.11.0", + "cyclonedx-python-lib": "11.12.0", + "defusedxml": "0.7.1", + "filelock": "3.32.5", + "frozenlist": "1.8.0", + "googleapis-common-protos": "1.75.3", + "google-api-core": "2.36.0", + "google-auth": "2.57.1", + "h11": "0.16.0", + "httpcore": "1.0.9", + "httpx": "0.28.1", + "idna": "3.19", + "iniconfig": "2.3.0", + "jsonschema": "4.26.0", + "jsonschema-specifications": "2025.9.1", + "json-rpc": "1.15.0", + "librt": "0.15.0", + "license-expression": "30.4.4", + "markdown-it-py": "4.2.0", + "mdurl": "0.1.2", + "msgpack": "1.2.2", + "multidict": "6.7.1", + "mypy": "2.3.1", + "mypy_extensions": "1.1.0", + "opentelemetry-api": "1.44.0", + "packageurl-python": "0.17.6", + "packaging": "26.3", + "pathspec": "1.1.1", + "pip": "26.2.1", + "pip-api": "0.0.34", + "pip_audit": "2.10.1", + "pip-requirements-parser": "32.0.1", + "platformdirs": "4.11.7", + "pluggy": "1.6.0", + "propcache": "0.5.2", + "protobuf": "6.33.6", + "proto-plus": "1.28.4", + "pyasn1": "0.6.4", + "pyasn1_modules": "0.4.2", + "pycparser": "3.0", + "pydantic": "2.13.5", + "pydantic_core": "2.46.5", + "Pygments": "2.21.0", + "pyparsing": "3.3.2", + "pytest": "9.1.1", + "pytest-asyncio": "1.4.0", + "pytest-cov": "7.1.0", + "PyYAML": "6.0.3", + "py-serializable": "2.1.0", + "referencing": "0.37.0", + "requests": "2.34.2", + "rfc3986-validator": "0.1.1", + "rfc8785": "0.1.4", + "rich": "15.0.0", + "rpds-py": "2026.6.3", + "ruff": "0.16.9", + "setuptools": "84.0.0", + "sniffio": "1.3.1", + "sortedcontainers": "2.4.0", + "sse-starlette": "3.4.11", + "starlette": "1.6.0", + "stevedore": "5.9.1", + "tomli": "2.4.1", + "tomli_w": "1.2.0", + "types-PyYAML": "6.0.12.20260906", + "typing_extensions": "4.16.0", + "typing-inspection": "0.4.4", + "urllib3": "2.8.0", + "uvicorn": "0.53.0", + "wrapt": "2.4.0", + "yarl": "1.24.5" + }, + "software_only": true, + "pytest": { + "baseline": { + "exit_code": 0, + "tests": 57, + "failures": 0, + "errors": 0, + "skipped": 0, + "failed_cases": [], + "duration_seconds": 7.6, + "command": "python -B -m pytest -p no:cacheprovider -p pytest_asyncio.plugin -q --tb=short --basetemp=\\baseline-tmp --junitxml=\\pytest-baseline.xml tests/unit/test_response_authentication.py", + "matches_reference": true + }, + "response-mac": { + "exit_code": 1, + "tests": 57, + "failures": 4, + "errors": 0, + "skipped": 0, + "failed_cases": [ + "test_changed_response_is_not_exposed[body-value0]", + "test_changed_response_is_not_exposed[mac-0000000000000000000000000000000000000000000000000000000000000000]", + "test_committed_language_neutral_vector_and_negative_twins", + "test_real_http_substituted_response_never_reaches_caller[body]" + ], + "duration_seconds": 8.8, + "command": "python -B -m pytest -p no:cacheprovider -p pytest_asyncio.plugin -q --tb=short --basetemp=\\response-mac-tmp --junitxml=\\pytest-response-mac.xml tests/unit/test_response_authentication.py", + "matches_reference": true, + "mutation": { + "file": "src/ca2a_runtime/response.py", + "old": "if not hmac.compare_digest(mac, _mac(key, envelope)):", + "new": "if False:" + } + }, + "consumption": { + "exit_code": 1, + "tests": 57, + "failures": 3, + "errors": 0, + "skipped": 0, + "failed_cases": [ + "test_concurrent_consumption_has_exactly_one_success", + "test_invalid_response_consumes_occurrence", + "test_valid_response_and_one_use" + ], + "duration_seconds": 14.2, + "command": "python -B -m pytest -p no:cacheprovider -p pytest_asyncio.plugin -q --tb=short --basetemp=\\consumption-tmp --junitxml=\\pytest-consumption.xml tests/unit/test_response_authentication.py", + "matches_reference": true, + "mutation": { + "file": "src/ca2a_runtime/response.py", + "old": "self._used = True\n key, self._key = self._key, b\"\"", + "new": "self._used = False\n key = self._key" + } + } + }, + "boundary": { + "exit_code": 0, + "result": { + "rounds": 20, + "attempts": 320, + "successes_per_round": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "caller_constructed_mac_accepted": true, + "limit": "Local caller authentication is not transferable evidence of peer authorship." + }, + "matches_reference": true, + "command": "python -B experiments/race_and_caller_mac.py " + }, + "experiment": { + "exit_code": 0, + "cases_passed": 5, + "cases_total": 5, + "criterion_met": true, + "command": "python -B experiments/transport_cut.py run ", + "valid": true, + "matches_reference": true + }, + "controls": { + "resend-on-transport-failure": { + "exit_code": 1, + "cases_passed": 3, + "cases_total": 5, + "criterion_met": false, + "command": "python -B experiments/transport_cut.py run ", + "mutation": { + "file": "src/ca2a_runtime/transport/client.py", + "old": " status, raw = _post_authenticated(\n f\"{base_url}{AUTHENTICATED_TASK_PATH}\", pending.request_bytes\n )\n", + "new": " try:\n status, raw = _post_authenticated(\n f\"{base_url}{AUTHENTICATED_TASK_PATH}\", pending.request_bytes\n )\n except OSError:\n status, raw = _post_authenticated(\n f\"{base_url}{AUTHENTICATED_TASK_PATH}\", pending.request_bytes\n )\n" + }, + "valid": true, + "matches_reference": true + } + }, + "summary": { + "baseline_passed": true, + "baseline_tests": 57, + "mutations_detected": { + "response-mac": 4, + "consumption": 3 + }, + "experiment_cases": "5 of 5", + "controls_failing_as_required": 1, + "all_match_reference": true + }, + "finished_utc": "2026-10-01T18:43:03Z", + "harness_sha256": { + "race_and_caller_mac.py": "0fd853851d2314b590d586327d2e02ce234b3d1315bb8a60e17a4fdc756ccdb5", + "run.py": "54e4737474c3894ad60056da3fc27e30f3c760fc27c9235e31fe1810249176e8", + "transport_cut.py": "c9a7316c50cc2088b38889108c7c71542d25e518daad5250d0b35278683e34f2" + } +} \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/transport-cut.json b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/transport-cut.json new file mode 100644 index 0000000..eaaeaf3 --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/main-19407fb-2026-10-01/transport-cut.json @@ -0,0 +1,63 @@ +{ + "experiment": "transport-cut", + "cases": [ + { + "mode": "pass", + "caller_outcome": "accepted", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-pass": "accepted" + }, + "captured_response_replay": "refused", + "ok": true + }, + { + "mode": "cut_before_dispatch", + "caller_outcome": "unknown", + "peer_executions": 0, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-cut_before_dispatch": "unknown" + }, + "captured_response_replay": "no-capture", + "ok": true + }, + { + "mode": "cut_after_dispatch", + "caller_outcome": "unknown", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-cut_after_dispatch": "unknown" + }, + "captured_response_replay": "refused", + "ok": true + }, + { + "mode": "truncate", + "caller_outcome": "unknown", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-truncate": "unknown" + }, + "captured_response_replay": "refused", + "ok": true + }, + { + "mode": "hold", + "caller_outcome": "caller-killed", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-hold": "unknown" + }, + "captured_response_replay": "refused", + "ok": true + } + ], + "cases_passed": 5, + "cases_total": 5, + "criterion_met": true +} \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/SHA256SUMS.json b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/SHA256SUMS.json new file mode 100644 index 0000000..69da0d2 --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/SHA256SUMS.json @@ -0,0 +1,13 @@ +{ + "commands.txt": "483d8cfa5bc4f0fc11fdc9cc81fb50ed3a3506f046d5fbca90b9d67172dac732", + "control-resend-on-transport-failure.json": "76869e36c4e2f734fadb18d51890ab319c1d8364dfc531e10731d3dd4e5fd403", + "pytest-baseline.log": "03fd19fccd29d8109ec333ebeabbca9d1179b4634adf6732b80ae83949e5ab27", + "pytest-baseline.xml": "61fb5bc2b1fca5e2583970005be6c324eeb86ba3a503560fa4ce4c202d61a6ad", + "pytest-consumption.log": "255afc340dc31df65ad4dea2f2a2123cdbfe482aef744949fb921105c9e3d628", + "pytest-consumption.xml": "9951534aed77f68c88797a8d6272e526b1eacac79897e41e0ecc9545de448f2b", + "pytest-response-mac.log": "47dee226d1693835d88086647c77eb7736c554efd967c8a57cdbf73d0ea03ef6", + "pytest-response-mac.xml": "69f582b0fab9638122a16606a95910a9a57a13a8503bf52c6e4837f10893db10", + "race-and-caller-mac.json": "f810ef7145c2371f36681737b4ae0a614fdc179d66bb532088efbe3ec87fe9fd", + "run.json": "3b9f883d970357a9f65cd78cb073c55e6da2b5367f8b935d195c90523a8f8e75", + "transport-cut.json": "b4908d253bd45fcc073306df3d612dc32450be6bb695d7c5285d63ac03a51ea1" +} \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/commands.txt b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/commands.txt new file mode 100644 index 0000000..b7d22f1 --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/commands.txt @@ -0,0 +1,13 @@ +# Environment (Python 3.12), from a clone of agentrust-io/ca2a +git -C checkout --detach fc22c644846111c7f375446399e9097a73f93214 +python -m venv +/Scripts/python -m pip install --require-hashes -r /requirements/dev.txt +# Run (from this report's directory) +/Scripts/python -B experiments/run.py --source --commit fc22c644846111c7f375446399e9097a73f93214 --output +# Individual commands, as executed +python -B -m pytest -p no:cacheprovider -p pytest_asyncio.plugin -q --tb=short --basetemp=\baseline-tmp --junitxml=\pytest-baseline.xml tests/unit/test_response_authentication.py +python -B -m pytest -p no:cacheprovider -p pytest_asyncio.plugin -q --tb=short --basetemp=\response-mac-tmp --junitxml=\pytest-response-mac.xml tests/unit/test_response_authentication.py +python -B -m pytest -p no:cacheprovider -p pytest_asyncio.plugin -q --tb=short --basetemp=\consumption-tmp --junitxml=\pytest-consumption.xml tests/unit/test_response_authentication.py +python -B experiments/race_and_caller_mac.py +python -B experiments/transport_cut.py run +python -B experiments/transport_cut.py run diff --git a/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/control-resend-on-transport-failure.json b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/control-resend-on-transport-failure.json new file mode 100644 index 0000000..1058c9f --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/control-resend-on-transport-failure.json @@ -0,0 +1,63 @@ +{ + "experiment": "transport-cut", + "cases": [ + { + "mode": "pass", + "caller_outcome": "accepted", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-pass": "accepted" + }, + "captured_response_replay": "refused", + "ok": true + }, + { + "mode": "cut_before_dispatch", + "caller_outcome": "unknown", + "peer_executions": 0, + "submissions_seen_by_relay": 2, + "after_restart": { + "business-op-cut_before_dispatch": "unknown" + }, + "captured_response_replay": "no-capture", + "ok": false + }, + { + "mode": "cut_after_dispatch", + "caller_outcome": "unknown", + "peer_executions": 2, + "submissions_seen_by_relay": 2, + "after_restart": { + "business-op-cut_after_dispatch": "unknown" + }, + "captured_response_replay": "refused", + "ok": false + }, + { + "mode": "truncate", + "caller_outcome": "unknown", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-truncate": "unknown" + }, + "captured_response_replay": "refused", + "ok": true + }, + { + "mode": "hold", + "caller_outcome": "caller-killed", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-hold": "unknown" + }, + "captured_response_replay": "refused", + "ok": true + } + ], + "cases_passed": 3, + "cases_total": 5, + "criterion_met": false +} \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-baseline.log b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-baseline.log new file mode 100644 index 0000000..6e6fbf0 --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-baseline.log @@ -0,0 +1,14 @@ +============================= test session starts ============================= +platform win32 -- Python 3.12.10, pytest-9.1.1, pluggy-1.6.0 +rootdir: \ca2a +configfile: pyproject.toml +asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function +collected 57 items + +tests\unit\test_response_authentication.py ............................. [ 50%] +............................ [100%] + +- generated xml file: \pytest-baseline.xml - +============================= 57 passed in 5.40s ============================== + +STDERR diff --git a/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-baseline.xml b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-baseline.xml new file mode 100644 index 0000000..dd6afd2 --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-baseline.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-consumption.log b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-consumption.log new file mode 100644 index 0000000..ca0175c --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-consumption.log @@ -0,0 +1,37 @@ +============================= test session starts ============================= +platform win32 -- Python 3.12.10, pytest-9.1.1, pluggy-1.6.0 +rootdir: \copy-consumption +configfile: pyproject.toml +asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function +collected 57 items + +tests\unit\test_response_authentication.py F.....................F...... [ 50%] +...F........................ [100%] + +================================== FAILURES =================================== +_______________________ test_valid_response_and_one_use _______________________ +tests\unit\test_response_authentication.py:53: in test_valid_response_and_one_use + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed +_____________ test_concurrent_consumption_has_exactly_one_success _____________ +tests\unit\test_response_authentication.py:147: in test_concurrent_consumption_has_exactly_one_success + assert sum(pool.map(consume, range(16))) == 1 +E assert 16 == 1 +E + where 16 = sum(.result_iterator at 0x000001F258C56E30>) +E + where .result_iterator at 0x000001F258C56E30> = map(.consume at 0x000001F258D56700>, range(0, 16)) +E + where map = .map +E + and range(0, 16) = range(16) +__________________ test_invalid_response_consumes_occurrence __________________ +tests\unit\test_response_authentication.py:174: in test_invalid_response_consumes_occurrence + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed +- generated xml file: \pytest-consumption.xml - +=========================== short test summary info =========================== +FAILED tests/unit/test_response_authentication.py::test_valid_response_and_one_use +FAILED tests/unit/test_response_authentication.py::test_concurrent_consumption_has_exactly_one_success +FAILED tests/unit/test_response_authentication.py::test_invalid_response_consumes_occurrence +======================== 3 failed, 54 passed in 5.46s ========================= + +STDERR diff --git a/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-consumption.xml b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-consumption.xml new file mode 100644 index 0000000..e1a3340 --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-consumption.xml @@ -0,0 +1,13 @@ +tests\unit\test_response_authentication.py:53: in test_valid_response_and_one_use + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailedtests\unit\test_response_authentication.py:147: in test_concurrent_consumption_has_exactly_one_success + assert sum(pool.map(consume, range(16))) == 1 +E assert 16 == 1 +E + where 16 = sum(<generator object Executor.map.<locals>.result_iterator at 0x000001F258C56E30>) +E + where <generator object Executor.map.<locals>.result_iterator at 0x000001F258C56E30> = map(<function test_concurrent_consumption_has_exactly_one_success.<locals>.consume at 0x000001F258D56700>, range(0, 16)) +E + where map = <concurrent.futures.thread.ThreadPoolExecutor object at 0x000001F258CD6330>.map +E + and range(0, 16) = range(16)tests\unit\test_response_authentication.py:174: in test_invalid_response_consumes_occurrence + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-response-mac.log b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-response-mac.log new file mode 100644 index 0000000..7167bbd --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-response-mac.log @@ -0,0 +1,40 @@ +============================= test session starts ============================= +platform win32 -- Python 3.12.10, pytest-9.1.1, pluggy-1.6.0 +rootdir: \copy-response-mac +configfile: pyproject.toml +asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function +collected 57 items + +tests\unit\test_response_authentication.py .F....F...................... [ 50%] +.............F.............F [100%] + +================================== FAILURES =================================== +______________ test_changed_response_is_not_exposed[body-value0] ______________ +tests\unit\test_response_authentication.py:72: in test_changed_response_is_not_exposed + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed +_ test_changed_response_is_not_exposed[mac-0000000000000000000000000000000000000000000000000000000000000000] _ +tests\unit\test_response_authentication.py:72: in test_changed_response_is_not_exposed + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed +_______ test_real_http_substituted_response_never_reaches_caller[body] ________ +tests\unit\test_response_authentication.py:311: in test_real_http_substituted_response_never_reaches_caller + with pytest.raises(ResponseAuthenticationFailed, match="unknown"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed +__________ test_committed_language_neutral_vector_and_negative_twins __________ +tests\unit\test_response_authentication.py:451: in test_committed_language_neutral_vector_and_negative_twins + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed +- generated xml file: \pytest-response-mac.xml - +=========================== short test summary info =========================== +FAILED tests/unit/test_response_authentication.py::test_changed_response_is_not_exposed[body-value0] +FAILED tests/unit/test_response_authentication.py::test_changed_response_is_not_exposed[mac-0000000000000000000000000000000000000000000000000000000000000000] +FAILED tests/unit/test_response_authentication.py::test_real_http_substituted_response_never_reaches_caller[body] +FAILED tests/unit/test_response_authentication.py::test_committed_language_neutral_vector_and_negative_twins +======================== 4 failed, 53 passed in 5.47s ========================= + +STDERR diff --git a/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-response-mac.xml b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-response-mac.xml new file mode 100644 index 0000000..df805df --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/pytest-response-mac.xml @@ -0,0 +1,13 @@ +tests\unit\test_response_authentication.py:72: in test_changed_response_is_not_exposed + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailedtests\unit\test_response_authentication.py:72: in test_changed_response_is_not_exposed + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailedtests\unit\test_response_authentication.py:311: in test_real_http_substituted_response_never_reaches_caller + with pytest.raises(ResponseAuthenticationFailed, match="unknown"): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailedtests\unit\test_response_authentication.py:451: in test_committed_language_neutral_vector_and_negative_twins + with pytest.raises(ResponseAuthenticationFailed): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +E Failed: DID NOT RAISE ResponseAuthenticationFailed \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/race-and-caller-mac.json b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/race-and-caller-mac.json new file mode 100644 index 0000000..9df7d5e --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/race-and-caller-mac.json @@ -0,0 +1,28 @@ +{ + "rounds": 20, + "attempts": 320, + "successes_per_round": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "caller_constructed_mac_accepted": true, + "limit": "Local caller authentication is not transferable evidence of peer authorship." +} \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/run.json b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/run.json new file mode 100644 index 0000000..dc5b84d --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/run.json @@ -0,0 +1,246 @@ +{ + "started_utc": "2026-10-01T18:40:47Z", + "subject": "agentrust-io/ca2a", + "commit": "fc22c644846111c7f375446399e9097a73f93214", + "tree": "9e06071c40840b8fd824ae8215813cab4f64c40b", + "python": "3.12.10", + "platform": "Windows-11-10.0.26200-SP0", + "package_versions": { + "pytest": "9.1.1", + "pytest-asyncio": "1.4.0", + "cryptography": "50.0.1", + "pydantic": "2.13.5", + "rfc8785": "0.1.4", + "agent-manifest": "0.12.0", + "agentrust-trace": "0.10.0", + "cedarpy": "4.8.7", + "pyyaml": "6.0.3" + }, + "installed_distributions": { + "a2a-sdk": "1.1.2", + "agentrust-trace": "0.10.0", + "agentrust-trace-tests": "0.5.1", + "agent-manifest": "0.12.0", + "aiohappyeyeballs": "2.7.1", + "aiohttp": "3.14.3", + "aiologic": "0.17.1", + "aiosignal": "1.4.0", + "annotated-types": "0.8.0", + "anyio": "4.15.1", + "ast_serialize": "0.9.0", + "attrs": "26.1.0", + "bandit": "1.9.4", + "boolean.py": "5.0", + "CacheControl": "0.14.4", + "cbor2": "6.1.4", + "cedarpy": "4.8.7", + "certifi": "2026.7.22", + "cffi": "2.1.1", + "charset-normalizer": "3.5.1", + "click": "8.5.0", + "colorama": "0.4.6", + "coverage": "7.16.0", + "cryptography": "50.0.1", + "culsans": "0.11.0", + "cyclonedx-python-lib": "11.12.0", + "defusedxml": "0.7.1", + "filelock": "3.32.5", + "frozenlist": "1.8.0", + "googleapis-common-protos": "1.75.3", + "google-api-core": "2.36.0", + "google-auth": "2.57.1", + "h11": "0.16.0", + "httpcore": "1.0.9", + "httpx": "0.28.1", + "idna": "3.19", + "iniconfig": "2.3.0", + "jsonschema": "4.26.0", + "jsonschema-specifications": "2025.9.1", + "json-rpc": "1.15.0", + "librt": "0.15.0", + "license-expression": "30.4.4", + "markdown-it-py": "4.2.0", + "mdurl": "0.1.2", + "msgpack": "1.2.2", + "multidict": "6.7.1", + "mypy": "2.3.1", + "mypy_extensions": "1.1.0", + "opentelemetry-api": "1.44.0", + "packageurl-python": "0.17.6", + "packaging": "26.3", + "pathspec": "1.1.1", + "pip": "26.2.1", + "pip-api": "0.0.34", + "pip_audit": "2.10.1", + "pip-requirements-parser": "32.0.1", + "platformdirs": "4.11.7", + "pluggy": "1.6.0", + "propcache": "0.5.2", + "protobuf": "6.33.6", + "proto-plus": "1.28.4", + "pyasn1": "0.6.4", + "pyasn1_modules": "0.4.2", + "pycparser": "3.0", + "pydantic": "2.13.5", + "pydantic_core": "2.46.5", + "Pygments": "2.21.0", + "pyparsing": "3.3.2", + "pytest": "9.1.1", + "pytest-asyncio": "1.4.0", + "pytest-cov": "7.1.0", + "PyYAML": "6.0.3", + "py-serializable": "2.1.0", + "referencing": "0.37.0", + "requests": "2.34.2", + "rfc3986-validator": "0.1.1", + "rfc8785": "0.1.4", + "rich": "15.0.0", + "rpds-py": "2026.6.3", + "ruff": "0.16.6", + "setuptools": "84.0.0", + "sniffio": "1.3.1", + "sortedcontainers": "2.4.0", + "sse-starlette": "3.4.11", + "starlette": "1.6.0", + "stevedore": "5.9.1", + "tomli": "2.4.1", + "tomli_w": "1.2.0", + "types-PyYAML": "6.0.12.20260906", + "typing_extensions": "4.16.0", + "typing-inspection": "0.4.4", + "urllib3": "2.7.0", + "uvicorn": "0.52.4", + "wrapt": "2.4.0", + "yarl": "1.24.5" + }, + "software_only": true, + "pytest": { + "baseline": { + "exit_code": 0, + "tests": 57, + "failures": 0, + "errors": 0, + "skipped": 0, + "failed_cases": [], + "duration_seconds": 7.3, + "command": "python -B -m pytest -p no:cacheprovider -p pytest_asyncio.plugin -q --tb=short --basetemp=\\baseline-tmp --junitxml=\\pytest-baseline.xml tests/unit/test_response_authentication.py", + "matches_reference": true + }, + "response-mac": { + "exit_code": 1, + "tests": 57, + "failures": 4, + "errors": 0, + "skipped": 0, + "failed_cases": [ + "test_changed_response_is_not_exposed[body-value0]", + "test_changed_response_is_not_exposed[mac-0000000000000000000000000000000000000000000000000000000000000000]", + "test_committed_language_neutral_vector_and_negative_twins", + "test_real_http_substituted_response_never_reaches_caller[body]" + ], + "duration_seconds": 7.3, + "command": "python -B -m pytest -p no:cacheprovider -p pytest_asyncio.plugin -q --tb=short --basetemp=\\response-mac-tmp --junitxml=\\pytest-response-mac.xml tests/unit/test_response_authentication.py", + "matches_reference": true, + "mutation": { + "file": "src/ca2a_runtime/response.py", + "old": "if not hmac.compare_digest(mac, _mac(key, envelope)):", + "new": "if False:" + } + }, + "consumption": { + "exit_code": 1, + "tests": 57, + "failures": 3, + "errors": 0, + "skipped": 0, + "failed_cases": [ + "test_concurrent_consumption_has_exactly_one_success", + "test_invalid_response_consumes_occurrence", + "test_valid_response_and_one_use" + ], + "duration_seconds": 7.2, + "command": "python -B -m pytest -p no:cacheprovider -p pytest_asyncio.plugin -q --tb=short --basetemp=\\consumption-tmp --junitxml=\\pytest-consumption.xml tests/unit/test_response_authentication.py", + "matches_reference": true, + "mutation": { + "file": "src/ca2a_runtime/response.py", + "old": "self._used = True\n key, self._key = self._key, b\"\"", + "new": "self._used = False\n key = self._key" + } + } + }, + "boundary": { + "exit_code": 0, + "result": { + "rounds": 20, + "attempts": 320, + "successes_per_round": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "caller_constructed_mac_accepted": true, + "limit": "Local caller authentication is not transferable evidence of peer authorship." + }, + "matches_reference": true, + "command": "python -B experiments/race_and_caller_mac.py " + }, + "experiment": { + "exit_code": 0, + "cases_passed": 5, + "cases_total": 5, + "criterion_met": true, + "command": "python -B experiments/transport_cut.py run ", + "valid": true, + "matches_reference": true + }, + "controls": { + "resend-on-transport-failure": { + "exit_code": 1, + "cases_passed": 3, + "cases_total": 5, + "criterion_met": false, + "command": "python -B experiments/transport_cut.py run ", + "mutation": { + "file": "src/ca2a_runtime/transport/client.py", + "old": " status, raw = _post_authenticated(\n f\"{base_url}{AUTHENTICATED_TASK_PATH}\", pending.request_bytes\n )\n", + "new": " try:\n status, raw = _post_authenticated(\n f\"{base_url}{AUTHENTICATED_TASK_PATH}\", pending.request_bytes\n )\n except OSError:\n status, raw = _post_authenticated(\n f\"{base_url}{AUTHENTICATED_TASK_PATH}\", pending.request_bytes\n )\n" + }, + "valid": true, + "matches_reference": true + } + }, + "summary": { + "baseline_passed": true, + "baseline_tests": 57, + "mutations_detected": { + "response-mac": 4, + "consumption": 3 + }, + "experiment_cases": "5 of 5", + "controls_failing_as_required": 1, + "all_match_reference": true + }, + "finished_utc": "2026-10-01T18:41:42Z", + "harness_sha256": { + "race_and_caller_mac.py": "0fd853851d2314b590d586327d2e02ce234b3d1315bb8a60e17a4fdc756ccdb5", + "run.py": "54e4737474c3894ad60056da3fc27e30f3c760fc27c9235e31fe1810249176e8", + "transport_cut.py": "c9a7316c50cc2088b38889108c7c71542d25e518daad5250d0b35278683e34f2" + } +} \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/transport-cut.json b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/transport-cut.json new file mode 100644 index 0000000..eaaeaf3 --- /dev/null +++ b/papers/handoff-response-authentication/experiments/results/rerun-2026-10-01/transport-cut.json @@ -0,0 +1,63 @@ +{ + "experiment": "transport-cut", + "cases": [ + { + "mode": "pass", + "caller_outcome": "accepted", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-pass": "accepted" + }, + "captured_response_replay": "refused", + "ok": true + }, + { + "mode": "cut_before_dispatch", + "caller_outcome": "unknown", + "peer_executions": 0, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-cut_before_dispatch": "unknown" + }, + "captured_response_replay": "no-capture", + "ok": true + }, + { + "mode": "cut_after_dispatch", + "caller_outcome": "unknown", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-cut_after_dispatch": "unknown" + }, + "captured_response_replay": "refused", + "ok": true + }, + { + "mode": "truncate", + "caller_outcome": "unknown", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-truncate": "unknown" + }, + "captured_response_replay": "refused", + "ok": true + }, + { + "mode": "hold", + "caller_outcome": "caller-killed", + "peer_executions": 1, + "submissions_seen_by_relay": 1, + "after_restart": { + "business-op-hold": "unknown" + }, + "captured_response_replay": "refused", + "ok": true + } + ], + "cases_passed": 5, + "cases_total": 5, + "criterion_met": true +} \ No newline at end of file diff --git a/papers/handoff-response-authentication/experiments/run.py b/papers/handoff-response-authentication/experiments/run.py new file mode 100644 index 0000000..c09ec5f --- /dev/null +++ b/papers/handoff-response-authentication/experiments/run.py @@ -0,0 +1,255 @@ +"""Rerun every software result in this report from a pinned cA2A commit. + +Steps, all on a fresh export of the pinned commit (git archive) in a temporary directory: + +1. The selected response-authentication tests (57 tests, some over loopback HTTP). +2. Two control removals, each on an isolated copy: the response-MAC comparison and the + single-use consumption of pending state. Each must make the selected tests fail. +3. race_and_caller_mac.py: 20 rounds of 16 concurrent verifications, and a MAC built with + the caller's own key. +4. transport_cut.py: separate peer, relay and caller processes with the connection cut at + several points, then its control (a client that resends once on transport failure), + which must fail the criterion. + +Results are compared with reference-2026-09.json. Local paths are replaced with placeholders. + + python experiments/run.py --source --output experiments/results/rerun-YYYY-MM-DD + +Run it with the interpreter of a virtual environment built from the pinned +requirements/dev.txt. Every listener binds to 127.0.0.1. No cloud, container or repository writes. +""" +from __future__ import annotations + +import argparse +import hashlib +import importlib.metadata +import io +import json +import os +from pathlib import Path +import platform +import re +import shutil +import subprocess +import sys +import tarfile +import tempfile +import time +import xml.etree.ElementTree as ET + +HERE = Path(__file__).resolve().parent +NO_WINDOW = getattr(subprocess, "CREATE_NO_WINDOW", 0) +PIN = "fc22c644846111c7f375446399e9097a73f93214" +SELECTION = ["tests/unit/test_response_authentication.py"] +MUTANTS = [ # (label, file, exact fragment, replacement) + ("response-mac", "src/ca2a_runtime/response.py", + "if not hmac.compare_digest(mac, _mac(key, envelope)):", "if False:"), + ("consumption", "src/ca2a_runtime/response.py", + 'self._used = True\n key, self._key = self._key, b""', + "self._used = False\n key = self._key"), +] +CONTROLS = [ # (label, file or None, fragment, replacement, extra args) + ("resend-on-transport-failure", "src/ca2a_runtime/transport/client.py", + " status, raw = _post_authenticated(\n" + ' f"{base_url}{AUTHENTICATED_TASK_PATH}", pending.request_bytes\n' + " )\n", + " try:\n" + " status, raw = _post_authenticated(\n" + ' f"{base_url}{AUTHENTICATED_TASK_PATH}", pending.request_bytes\n' + " )\n" + " except OSError:\n" + " status, raw = _post_authenticated(\n" + ' f"{base_url}{AUTHENTICATED_TASK_PATH}", pending.request_bytes\n' + " )\n", []), +] +VERSIONS = ["pytest", "pytest-asyncio", "cryptography", "pydantic", "rfc8785", "agent-manifest", + "agentrust-trace", "cedarpy", "pyyaml"] + + +def sh(cmd, **kw): + return subprocess.run(cmd, capture_output=True, text=True, creationflags=NO_WINDOW, **kw) + + +def main(): + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--source", type=Path, required=True, help="a git clone of agentrust-io/ca2a") + ap.add_argument("--commit", default=PIN) + ap.add_argument("--output", type=Path, required=True) + args = ap.parse_args() + out = args.output.resolve() + if out.exists() and any(out.iterdir()): + raise SystemExit("Use a new, empty output directory.") + out.mkdir(parents=True, exist_ok=True) + scratch = Path(tempfile.mkdtemp(prefix="handoff-responses-")) + src_repo = args.source.resolve() + pairs = sorted([(str(scratch), ""), (str(src_repo), ""), (str(out), ""), + (str(HERE), ""), (str(Path(sys.executable).parent.parent), ""), + (str(Path(sys.base_prefix)), ""), (str(Path.home()), ""), + (tempfile.gettempdir(), "")], key=lambda p: -len(p[0])) + + def scrub(text: str, xml: bool = False) -> str: + for real, label in pairs: + if xml: + label = label.replace("<", "<").replace(">", ">") + for form in {real, real.replace("\\", "/"), real.replace("\\", "\\\\")}: + text = re.sub(re.escape(form), label, text, flags=re.IGNORECASE) + host = "<host>" if xml else "" + return re.sub(r'hostname="[^"]*"', f'hostname="{host}"', text) + + git = ["git", "-c", f"safe.directory={src_repo.as_posix()}", "-C", str(src_repo)] + tree = sh(git + ["rev-parse", args.commit + "^{tree}"], check=True).stdout.strip() + snap = scratch / "ca2a" + snap.mkdir() + with tarfile.open(fileobj=io.BytesIO(subprocess.check_output(git + ["archive", args.commit], + creationflags=NO_WINDOW))) as tar: + tar.extractall(snap, filter="data") + env_base = {**os.environ, "PYTHONDONTWRITEBYTECODE": "1", "PYTEST_DISABLE_PLUGIN_AUTOLOAD": "1"} + env_base.pop("PYTHONPATH", None) + ref = json.loads((HERE / "reference-2026-09.json").read_text(encoding="utf-8")) + record = {"started_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + "subject": "agentrust-io/ca2a", "commit": args.commit, "tree": tree, + "python": platform.python_version(), "platform": platform.platform(), + "package_versions": {n: _version(n) for n in VERSIONS}, + "installed_distributions": {d.metadata["Name"]: d.version + for d in importlib.metadata.distributions()}, + "software_only": True, "pytest": {}, "boundary": None, "experiment": None, "controls": {}} + + def env_for(root): + return {**env_base, "PYTHONPATH": os.pathsep.join([str(root / "src"), str(root)])} + + def pytest(label, root): + report = out / f"pytest-{label}.xml" + cmd = [sys.executable, "-B", "-m", "pytest", "-p", "no:cacheprovider", "-p", "pytest_asyncio.plugin", + "-q", "--tb=short", f"--basetemp={scratch / (label + '-tmp')}", f"--junitxml={report}", *SELECTION] + begin = time.perf_counter() + done = sh(cmd, cwd=root, env=env_for(root), timeout=600) + (out / f"pytest-{label}.log").write_text(scrub(done.stdout + "\nSTDERR\n" + done.stderr), encoding="utf-8") + parsed = ET.parse(report) + counts = {k: 0 for k in ("tests", "failures", "errors", "skipped")} + for s in parsed.getroot().iter("testsuite"): + for k in counts: + counts[k] += int(s.attrib.get(k, "0")) + failed = sorted(c.attrib["name"] for c in parsed.iter("testcase") if c.find("failure") is not None) + report.write_text(scrub(report.read_text(encoding="utf-8"), xml=True), encoding="utf-8") + ET.parse(report) # the scrubbed report must still be well-formed XML + rec = {"exit_code": done.returncode, **counts, "failed_cases": failed, + "duration_seconds": round(time.perf_counter() - begin, 1), + "command": scrub(" ".join(["python"] + cmd[1:]))} + prior = ref["pytest"][label] + rec["matches_reference"] = (all(rec[k] == prior[k] for k in ("exit_code", "tests", "failures", "errors", "skipped")) + and failed == sorted(prior["failed_cases"])) + record["pytest"][label] = rec + print(json.dumps({label: {k: rec[k] for k in ("exit_code", "tests", "failures", "matches_reference")}}), + flush=True) + + def mutate(label, file, old, new): + root = scratch / ("copy-" + label) + shutil.copytree(snap, root) + path = root / file + text = path.read_text(encoding="utf-8") + if text.count(old) != 1: + raise SystemExit(f"{label}: expected exactly one match in {file}") + path.write_text(text.replace(old, new), encoding="utf-8") + return root, {"file": file, "old": old, "new": new} + + pytest("baseline", snap) + for label, file, old, new in MUTANTS: + root, mutation = mutate(label, file, old, new) + pytest(label, root) + record["pytest"][label]["mutation"] = mutation + + done = sh([sys.executable, "-B", str(HERE / "race_and_caller_mac.py"), str(scratch / "race")], + cwd=snap, env=env_for(snap), timeout=300) + boundary = json.loads(done.stdout) if done.returncode == 0 else {"stderr": scrub(done.stderr[-4000:])} + record["boundary"] = {"exit_code": done.returncode, "result": boundary, + "matches_reference": boundary == ref["boundary"], + "command": "python -B experiments/race_and_caller_mac.py "} + (out / "race-and-caller-mac.json").write_text(json.dumps(boundary, indent=2), encoding="utf-8") + print(json.dumps({"race-and-caller-mac": done.returncode}), flush=True) + + def experiment(label, root, extra): + cmd = [sys.executable, "-B", str(HERE / "transport_cut.py"), "run", str(scratch / ("exp-" + label)), *extra] + d = sh(cmd, cwd=root, env=env_for(root), timeout=900) + try: + result = json.loads(d.stdout) + except json.JSONDecodeError: + result = {"criterion_met": False, "stdout_tail": d.stdout[-3000:], "stderr_tail": d.stderr[-3000:]} + result = json.loads(scrub(json.dumps(result))) + (out / f"{label}.json").write_text(json.dumps(result, indent=2), encoding="utf-8") + rec = {"exit_code": d.returncode, "cases_passed": result.get("cases_passed"), + "cases_total": result.get("cases_total"), "criterion_met": result.get("criterion_met"), + "command": "python -B experiments/transport_cut.py run " + " ".join(extra)} + print(json.dumps({label: rec}), flush=True) + return rec, result + + rec, result = experiment("transport-cut", snap, []) + rec["valid"] = rec["exit_code"] == 0 and rec["criterion_met"] is True + rec["matches_reference"] = _same_outcomes(result, ref["experiment"]) + record["experiment"] = rec + for label, file, old, new, extra in CONTROLS: + root, mutation = mutate(label, file, old, new) if file else (snap, None) + rec, result = experiment("control-" + label, root, extra) + rec["mutation"] = mutation + rec["valid"] = rec["exit_code"] == 1 and rec["criterion_met"] is False + rec["matches_reference"] = _same_outcomes(result, ref["controls"][label]) + record["controls"][label] = rec + + p = record["pytest"] + record["summary"] = { + "baseline_passed": p["baseline"]["exit_code"] == 0 and p["baseline"]["failures"] == 0, + "baseline_tests": p["baseline"]["tests"], + "mutations_detected": {k: p[k]["failures"] for k in p if k != "baseline"}, + "experiment_cases": f'{record["experiment"]["cases_passed"]} of {record["experiment"]["cases_total"]}', + "controls_failing_as_required": sum(c["valid"] for c in record["controls"].values()), + "all_match_reference": (all(r["matches_reference"] for r in p.values()) + and record["boundary"]["matches_reference"] + and record["experiment"]["matches_reference"] + and all(c["matches_reference"] for c in record["controls"].values())), + } + (out / "commands.txt").write_text( + "# Environment (Python 3.12), from a clone of agentrust-io/ca2a\n" + f"git -C checkout --detach {args.commit}\n" + "python -m venv \n" + "/Scripts/python -m pip install --require-hashes -r /requirements/dev.txt\n" + "# Run (from this report's directory)\n" + f"/Scripts/python -B experiments/run.py --source --commit {args.commit} --output \n" + "# Individual commands, as executed\n" + + "\n".join([r["command"] for r in record["pytest"].values()] + [record["boundary"]["command"], + record["experiment"]["command"]] + [c["command"] for c in record["controls"].values()]) + "\n", + encoding="utf-8") + record["finished_utc"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + record["harness_sha256"] = {f.name: hashlib.sha256(f.read_bytes()).hexdigest() for f in sorted(HERE.glob("*.py"))} + (out / "run.json").write_text(scrub(json.dumps(record, indent=2)), encoding="utf-8") + shutil.rmtree(scratch, ignore_errors=True) + leftovers = [f.name for f in out.iterdir() if re.search(r"[A-Za-z]:[\\/]+Users|/home/|Users[\\/]+\w+[\\/]", + f.read_text(encoding="utf-8", errors="ignore"))] + sums = {f.name: hashlib.sha256(f.read_bytes()).hexdigest() for f in sorted(out.iterdir()) if f.is_file()} + (out / "SHA256SUMS.json").write_text(json.dumps(sums, indent=2), encoding="utf-8") + print(json.dumps({**record["summary"], "path_leftovers": leftovers}), flush=True) + ok = (record["summary"]["baseline_passed"] and not leftovers and record["experiment"]["valid"] + and all(p[k]["exit_code"] == 1 and p[k]["failures"] > 0 for k in p if k != "baseline") + and all(c["valid"] for c in record["controls"].values())) + raise SystemExit(0 if ok else 1) + + +def _version(name): + try: + return importlib.metadata.version(name) + except importlib.metadata.PackageNotFoundError: + return None + + +def _same_outcomes(new, old): + """Compare pass/fail outcomes and observed states; ignore timings.""" + def strip(x): + if isinstance(x, dict): + return {k: strip(v) for k, v in x.items() if not (k.endswith("_ms_after_start") or k.endswith("_ms_after_trip_start") + or k in ("observed_at", "experiment"))} + if isinstance(x, list): + return [strip(v) for v in x] + return x + return strip(new) == strip(old) + + +if __name__ == "__main__": + main() diff --git a/papers/handoff-response-authentication/experiments/transport_cut.py b/papers/handoff-response-authentication/experiments/transport_cut.py new file mode 100644 index 0000000..73fe6e4 --- /dev/null +++ b/papers/handoff-response-authentication/experiments/transport_cut.py @@ -0,0 +1,275 @@ +"""Multi-process transport cut, caller restart and no resubmission. + +Hypothesis: with the pinned cA2A client in strict response-authentication mode, +when the connection is cut after the peer has executed (or before dispatch, or +mid-response), the caller reports an unknown outcome, the client makes exactly +one submission, and a restarted caller cannot turn the peer's captured response +into an accepted result. The caller never learns which of the cut cases +happened, which is why the unknown outcome must be preserved. + +Topology: peer process (real cA2A HTTP server on an ephemeral port, with an +fsync'd execution journal), relay process (TCP relay that cuts the connection +at a chosen point and captures what the peer returned), and caller processes +(send_task, with an application journal written before sending). Control: a +client mutated to resend once on transport failure must violate the criterion. + +Usage: transport_cut.py run SCRATCH. Subcommands peer/relay/caller/recover. +""" +from __future__ import annotations + +import json +import os +from pathlib import Path +import socket +import subprocess +import sys +import threading +import time + +HERE = Path(__file__).resolve() +# Child processes never open a console window on Windows (0 elsewhere). +NO_WINDOW = getattr(subprocess, "CREATE_NO_WINDOW", 0) + + +def _append(path, obj): + with open(path, "a", encoding="utf-8") as fh: + fh.write(json.dumps(obj) + "\n") + fh.flush() + os.fsync(fh.fileno()) + + +def _lines(path): + p = Path(path) + return [json.loads(x) for x in p.read_text(encoding="utf-8").splitlines()] if p.exists() else [] + + +# --------------------------------------------------------------------- peer +def peer(root_issuer: str, journal: str, port_file: str) -> None: + from ca2a_runtime.node import PeerNode + from ca2a_runtime.policy import LocalPolicy + from ca2a_runtime.transport import server + from ca2a_runtime.transport.a2a_adapter import parse_peer_request + node = PeerNode(LocalPolicy.of({"read"}), trusted_root_issuers={root_issuer}) + original = node.handle + + def handle(message): + result = original(message) + _append(journal, {"executed": parse_peer_request(message).record_id, "t": time.time()}) + return result + node.handle = handle + http = server.serve(node, port=0) # never the default port + Path(port_file).write_text(str(http.server_port), encoding="utf-8") + http.serve_forever() + + +# -------------------------------------------------------------------- relay +def _read_request(conn): + data = b"" + while b"\r\n\r\n" not in data: + chunk = conn.recv(65536) + if not chunk: + return data + data += chunk + head, _, rest = data.partition(b"\r\n\r\n") + length = 0 + for line in head.split(b"\r\n")[1:]: + k, _, v = line.partition(b":") + if k.strip().lower() == b"content-length": + length = int(v.strip()) + while len(rest) < length: + rest += conn.recv(65536) + return head + b"\r\n\r\n" + rest + + +def relay(peer_port: str, mode: str, port_file: str, log: str, capture: str) -> None: + srv = socket.socket() + srv.bind(("127.0.0.1", 0)) + srv.listen(16) + Path(port_file).write_text(str(srv.getsockname()[1]), encoding="utf-8") + + def serve(conn): + with conn: + request = _read_request(conn) + is_post = request.startswith(b"POST") + if is_post: + _append(log, {"post": True, "t": time.time()}) + if is_post and mode == "cut_before_dispatch": + return + up = socket.create_connection(("127.0.0.1", int(peer_port))) + with up: + up.sendall(request) + response = b"" + while chunk := up.recv(65536): + response += chunk + if is_post: + with open(capture, "ab") as fh: + fh.write(len(response).to_bytes(4, "big") + response) + if is_post and mode == "cut_after_dispatch": + return + if is_post and mode == "hold": + time.sleep(120) + return + if is_post and mode == "truncate": + head, _, body = response.partition(b"\r\n\r\n") + conn.sendall(head + b"\r\n\r\n" + body[: len(body) // 2]) + return + conn.sendall(response) + + while True: + c, _ = srv.accept() + threading.Thread(target=serve, args=(c,), daemon=True).start() + + +# ------------------------------------------------------------------- caller +def _material(path): + from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey + from ca2a_runtime.delegation.credential import DelegationCredential + m = json.loads(Path(path).read_text(encoding="utf-8")) + chain = [DelegationCredential.from_dict(c) for c in m["chain"]] + return chain, Ed25519PrivateKey.from_private_bytes(bytes.fromhex(m["holder"])) + + +def caller(material: str, url: str, record_id: str, journal: str) -> None: + from ca2a_runtime.response import AuthenticatedPeerError, ResponseAuthenticationFailed + from ca2a_runtime.transport import client + chain, holder = _material(material) + _append(journal, {"record_id": record_id, "state": "submitted"}) + try: + result = client.send_task(url, chain, "read", record_id, holder_key=holder, + payload=b"synthetic-task", require_authenticated_response=True) + outcome = "accepted" if result.get("accepted") else "unexpected" + except ResponseAuthenticationFailed: + outcome = "unknown" + except AuthenticatedPeerError: + outcome = "authenticated-denial" + _append(journal, {"record_id": record_id, "state": outcome}) + print(json.dumps({"outcome": outcome})) + + +def recover(journal: str, capture: str, url: str) -> None: + """Restarted caller: keep unresolved operations unknown; try the captured reply.""" + from ca2a_runtime.response import PendingResponse, ResponseAuthenticationFailed + from ca2a_runtime.transport import client + states = {} + for e in _lines(journal): + states[e["record_id"]] = e["state"] + unresolved = {k: ("unknown" if v in ("submitted", "unknown") else v) for k, v in states.items()} + replay = "no-capture" + data = Path(capture).read_bytes() if Path(capture).exists() else b"" + if data: + size = int.from_bytes(data[:4], "big") + head, _, body = data[4:4 + size].partition(b"\r\n\r\n") + status = int(head.split(b" ")[1]) + # New process: re-appraise the same peer; new pending state, new occurrence. + pending = PendingResponse(client.fetch_verified_peer(url), {"record_id": "restart"}) + try: + pending.verify(status, body) + replay = "accepted" + except ResponseAuthenticationFailed: + replay = "refused" + print(json.dumps({"after_restart": unresolved, "captured_response_replay": replay})) + + +# ------------------------------------------------------------------- parent +def run(scratch: Path) -> dict: + from ca2a_runtime.delegation.credential import DelegationCredential, new_keypair + scratch.mkdir(parents=True, exist_ok=True) + root_priv, root_pub = new_keypair() + holder, holder_pub = new_keypair() + cred = DelegationCredential(credential_id="c0", issuer=root_pub, subject=holder_pub, + scope=frozenset({"read"}), depth=0).sign(root_priv) + body = {**cred.body(), "signature": cred.signature} + material = scratch / "material.json" + from cryptography.hazmat.primitives import serialization + raw = holder.private_bytes(serialization.Encoding.Raw, serialization.PrivateFormat.Raw, + serialization.NoEncryption()) + material.write_text(json.dumps({"chain": [body], "holder": raw.hex()}), encoding="utf-8") + procs = [] + + def start(args, port_file): + p = subprocess.Popen([sys.executable, "-B", str(HERE), *args], creationflags=NO_WINDOW) + procs.append(p) + deadline = time.time() + 30 + while not (Path(port_file).exists() and Path(port_file).read_text()): + if time.time() > deadline or p.poll() is not None: + raise RuntimeError("child did not start: " + args[0]) + time.sleep(0.05) + return Path(port_file).read_text() + + def run_caller(url, rid, journal, kill_when=None): + p = subprocess.Popen([sys.executable, "-B", str(HERE), "caller", str(material), url, rid, str(journal)], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, + creationflags=NO_WINDOW) + procs.append(p) + if kill_when: + deadline = time.time() + 60 + while not kill_when() and time.time() < deadline: + time.sleep(0.02) + p.kill() + out, err = p.communicate(timeout=120) + return json.loads(out)["outcome"] if out.strip() else "caller-killed" + + def run_recover(journal, capture, url): + out = subprocess.run([sys.executable, "-B", str(HERE), "recover", str(journal), str(capture), url], + capture_output=True, text=True, timeout=120, + creationflags=NO_WINDOW).stdout + return json.loads(out) + + cases = [] + expectations = { + "pass": ("accepted", 1), + "cut_before_dispatch": ("unknown", 0), + "cut_after_dispatch": ("unknown", 1), + "truncate": ("unknown", 1), + "hold": ("caller-killed", 1), + } + try: + for mode, (want_outcome, want_exec) in expectations.items(): + d = scratch / mode + d.mkdir() + peer_journal, relay_log, capture, app = d / "peer.jsonl", d / "relay.jsonl", d / "capture.bin", d / "app.jsonl" + peer_port = start(["peer", root_pub, str(peer_journal), str(d / "peer.port")], d / "peer.port") + relay_port = start(["relay", peer_port, mode, str(d / "relay.port"), str(relay_log), str(capture)], + d / "relay.port") + rid = f"business-op-{mode}" + kill_when = (lambda: len(_lines(peer_journal)) >= 1 and capture.exists()) if mode == "hold" else None + outcome = run_caller(f"http://127.0.0.1:{relay_port}", rid, app, kill_when) + time.sleep(0.3) + recovered = run_recover(app, capture, f"http://127.0.0.1:{relay_port}") + executions = sum(1 for e in _lines(peer_journal) if e["executed"] == rid) + posts = len(_lines(relay_log)) + case = {"mode": mode, "caller_outcome": outcome, "peer_executions": executions, + "submissions_seen_by_relay": posts, **recovered} + want_replay = "no-capture" if mode == "cut_before_dispatch" else "refused" + case["ok"] = (outcome == want_outcome and executions == want_exec and posts == 1 + and recovered["captured_response_replay"] == want_replay + and (mode == "pass" or recovered["after_restart"].get(rid) == "unknown")) + cases.append(case) + for p in procs: + if p.poll() is None: + p.kill() + p.wait(10) + finally: + for p in procs: + if p.poll() is None: + p.kill() + p.wait(10) + passed = sum(c["ok"] for c in cases) + return {"experiment": "transport-cut", "cases": cases, "cases_passed": passed, + "cases_total": len(cases), "criterion_met": passed == len(cases)} + + +if __name__ == "__main__": + cmd = sys.argv[1] + if cmd == "peer": + peer(*sys.argv[2:]) + elif cmd == "relay": + relay(*sys.argv[2:]) + elif cmd == "caller": + caller(*sys.argv[2:]) + elif cmd == "recover": + recover(*sys.argv[2:]) + else: + result = run(Path(sys.argv[2])) + print(json.dumps(result, indent=2)) + raise SystemExit(0 if result["criterion_met"] else 1) diff --git a/papers/handoff-response-authentication/manifest.json b/papers/handoff-response-authentication/manifest.json new file mode 100644 index 0000000..01b532b --- /dev/null +++ b/papers/handoff-response-authentication/manifest.json @@ -0,0 +1,33 @@ +{ + "README.md": "3d8f8d1e55413d1f5a3f54b214991acc9aef15734e3fac4d13ac8da6efc9640b", + "build_paper.py": "21bc7f8d2461152f40da909ac159fb131686b88fb77c3b63269636638f922dda", + "experiments/race_and_caller_mac.py": "0fd853851d2314b590d586327d2e02ce234b3d1315bb8a60e17a4fdc756ccdb5", + "experiments/reference-2026-09.json": "798991372cf38a0ffff6f61a61f4d82143d291ac04c0cc6113da019252028d52", + "experiments/results/main-19407fb-2026-10-01/SHA256SUMS.json": "92cd4375d134ed750a4ce88f1c65c16ed19c31656be5a99351649208609591ca", + "experiments/results/main-19407fb-2026-10-01/commands.txt": "09b030d1873c1632691fef78ea4550f054839a70614161ceac55203f1454360e", + "experiments/results/main-19407fb-2026-10-01/control-resend-on-transport-failure.json": "76869e36c4e2f734fadb18d51890ab319c1d8364dfc531e10731d3dd4e5fd403", + "experiments/results/main-19407fb-2026-10-01/pytest-baseline.log": "87913ff2c47b45e037f8f7ef7fac63f0a7e0ef4733718f5cac35fc6b170d4b0e", + "experiments/results/main-19407fb-2026-10-01/pytest-baseline.xml": "c319e47ee4bbd97c1a577846db314806c997f2074b12c73e5447010bd2d2c691", + "experiments/results/main-19407fb-2026-10-01/pytest-consumption.log": "9fa05122cb673c37befc8397fb7bcd6d3d7211a6e1faa86284babbea916c5c70", + "experiments/results/main-19407fb-2026-10-01/pytest-consumption.xml": "9758e1a009ed05402fe8e41807f7a6eb9307d1345f3d96611236fcf27ca2d3eb", + "experiments/results/main-19407fb-2026-10-01/pytest-response-mac.log": "ad1d4107067603868219d1bd00bb192b57d8ce3859f0aa7e98ec5893ddb81bd1", + "experiments/results/main-19407fb-2026-10-01/pytest-response-mac.xml": "7114f08c4c6e39f8e422783e4a62b66e3337ce7014f43a95ef75d2e65a5abe89", + "experiments/results/main-19407fb-2026-10-01/race-and-caller-mac.json": "f810ef7145c2371f36681737b4ae0a614fdc179d66bb532088efbe3ec87fe9fd", + "experiments/results/main-19407fb-2026-10-01/run.json": "4839df5a71fab27b768e26bb364b46360ac5b2dbb7ccbfdb41b9f5f92fc461a8", + "experiments/results/main-19407fb-2026-10-01/transport-cut.json": "b4908d253bd45fcc073306df3d612dc32450be6bb695d7c5285d63ac03a51ea1", + "experiments/results/rerun-2026-10-01/SHA256SUMS.json": "13ee3a5d6842a9511b08e80fef824f7a660ae0a615a0b02dd3526a60b06a5742", + "experiments/results/rerun-2026-10-01/commands.txt": "483d8cfa5bc4f0fc11fdc9cc81fb50ed3a3506f046d5fbca90b9d67172dac732", + "experiments/results/rerun-2026-10-01/control-resend-on-transport-failure.json": "76869e36c4e2f734fadb18d51890ab319c1d8364dfc531e10731d3dd4e5fd403", + "experiments/results/rerun-2026-10-01/pytest-baseline.log": "03fd19fccd29d8109ec333ebeabbca9d1179b4634adf6732b80ae83949e5ab27", + "experiments/results/rerun-2026-10-01/pytest-baseline.xml": "61fb5bc2b1fca5e2583970005be6c324eeb86ba3a503560fa4ce4c202d61a6ad", + "experiments/results/rerun-2026-10-01/pytest-consumption.log": "255afc340dc31df65ad4dea2f2a2123cdbfe482aef744949fb921105c9e3d628", + "experiments/results/rerun-2026-10-01/pytest-consumption.xml": "9951534aed77f68c88797a8d6272e526b1eacac79897e41e0ecc9545de448f2b", + "experiments/results/rerun-2026-10-01/pytest-response-mac.log": "47dee226d1693835d88086647c77eb7736c554efd967c8a57cdbf73d0ea03ef6", + "experiments/results/rerun-2026-10-01/pytest-response-mac.xml": "69f582b0fab9638122a16606a95910a9a57a13a8503bf52c6e4837f10893db10", + "experiments/results/rerun-2026-10-01/race-and-caller-mac.json": "f810ef7145c2371f36681737b4ae0a614fdc179d66bb532088efbe3ec87fe9fd", + "experiments/results/rerun-2026-10-01/run.json": "3b9f883d970357a9f65cd78cb073c55e6da2b5367f8b935d195c90523a8f8e75", + "experiments/results/rerun-2026-10-01/transport-cut.json": "b4908d253bd45fcc073306df3d612dc32450be6bb695d7c5285d63ac03a51ea1", + "experiments/run.py": "54e4737474c3894ad60056da3fc27e30f3c760fc27c9235e31fe1810249176e8", + "experiments/transport_cut.py": "c9a7316c50cc2088b38889108c7c71542d25e518daad5250d0b35278683e34f2", + "paper.md": "240e54b173baae65c1f7b855e1f254a7a1c5d4fa556073193a506f3376b2d8f3" +} diff --git a/papers/handoff-response-authentication/paper.md b/papers/handoff-response-authentication/paper.md new file mode 100644 index 0000000..65333e8 --- /dev/null +++ b/papers/handoff-response-authentication/paper.md @@ -0,0 +1,138 @@ +# Authenticating the Answer in an Agent Handoff + +## Single-use, request-bound response authentication and the outcome a caller cannot know + +Imran Siddique · OPAQUE Systems · October 1, 2026 + +Technical report, version 1 · Software experiments + +### Abstract + +One agent appraises a peer, delegates a task to it, and gets an HTTP response back. Appraising the peer before sending says nothing about who wrote that response. A relay can swap the body, replay an answer from another call, or turn an error into a success. We study the strict response-authentication mode of the cA2A runtime, which binds the appraised peer key, the complete request and a fresh request occurrence into a key that can verify exactly one response, once. It authenticates success and denial bodies, binds the status the caller will act on, and reports an unknown outcome whenever verification or transport fails, instead of guessing. On the pinned source, 57 selected tests pass, including loopback HTTP exchanges, and removing the MAC comparison or the single-use consumption makes 4 and 3 of them fail. In 20 rounds of 16 concurrent verifications, exactly one response is accepted per round. With separate caller, relay and peer processes and the connection cut before dispatch, after execution, mid-body, or with the caller killed, the client submits once, reports unknown, and a restarted caller cannot accept the captured response. A control client that resends once on a transport error makes the peer execute one business operation twice. The authentication is for the live caller: the caller can compute the same MAC, so a stored response is not proof of peer authorship to a third party. All results were rerun on October 1, 2026 from a fresh environment and match the earlier records, and the same harness passes on current cA2A main. + +**Scope of this edition.** This is an author-prepared technical report. It has not been peer reviewed, and no independent organization has run the experiments. Every result is software only, on one Windows host operated by the author, over loopback, with software peer assurance. Nothing here shows that a peer key is protected by hardware, and peer-key rotation on attested hardware was not tested. Changes to cA2A after the evaluated commit are listed in section 8. + +### 1. The response is the part nobody signed + +A lot of work in agent-to-agent protocols goes into the request direction: who is calling, what they are allowed to delegate, whether the callee is running what it claims. The answer coming back usually gets far less attention. In the A2A protocol [1] the answer is a task result over HTTP. If the caller verified the callee's attestation before sending, it is tempting to treat whatever comes back on that connection as the callee's word. + +It is not. TLS may terminate at a load balancer or sidecar. A relay, a retry layer or an error handler may produce the bytes the caller parses. And reusing a business record identifier across calls means a response that only names that identifier can be replayed into a later call for the same record. + +The practical questions for anyone running delegated agents are simple. When my agent acts on a peer's answer, did that peer produce it, for this request, this time? And when the answer does not arrive cleanly, what does my agent believe happened? + +### 2. System and threat model + +The caller holds an appraised peer public key (X25519) and the assurance level of that appraisal. For each request it generates a fresh X25519 key and a random 32-byte occurrence identifier. The peer holds the private key for the identity the caller appraised. Both derive the response key from their shared secret and the request digest. + +The adversary sits on the path. It may change bytes, the HTTP status, the request context, the peer identity, the profile or the response kind. It may replay a response into the same call or another one, send ambiguous JSON, or cut the network after the peer has executed. The caller's process, its local policy and its pending state are trusted. Peer appraisal is an input to this protocol: the response MAC proves that the holder of the appraised key answered, and the appraisal is what says who that is. Either party to the exchange can compute the MAC, which section 4 returns to. + +### 3. Protocol + +**Request.** The caller builds an envelope of `profile` (`ca2a-response-mac-v1`), `peer` (the appraised key), `recipient` (its fresh key), `session` (the random occurrence) and `request` (the complete task). The canonical JSON bytes [2] of that envelope are snapshotted once, so later changes to caller-owned objects cannot change what was bound, and their SHA-256 is the request digest. + +**Key and statement.** HKDF-SHA256 [3] derives the response key from the X25519 shared secret [4], with the request digest as salt and `ca2a/response-mac/v1/key` as context. The response statement holds the profile, request digest, status, kind (`provenance` for 200, `denial` for 4xx and 5xx) and body. HMAC-SHA256 [5] authenticates it under a separate `ca2a/response-mac/v1/statement` label. + +```text +appraised peer + fresh caller key + full request + occurrence + -> request digest and response key +peer validates context -> executes -> authenticated statement +caller pending state -> consume -> validate -> expose verified body +``` + +**Peer side.** The peer validates the envelope shape, the profile and that `peer` is its own key before it executes anything. A malformed context fails before admission. Only after execution does it compute the statement, so an authenticated denial is a real statement from the peer. + +**Caller side.** Verification takes a lock and consumes the pending state first, including on failure, and erases the key. It then checks the deadline (monotonic clock, at most 300 s), parses bounded JSON, requires the exact field set, compares the request digest and the HTTP status the caller observed, verifies the MAC in constant time, checks kind and body shape, and checks the deadline again. Only then does it return the body. The pending object refuses copy, deep copy and pickling, so state cannot be cloned or restored through the supported API. + +**Parsing.** The parser rejects duplicate keys, non-integer numbers, nesting deeper than 64 and input over 1 MiB. Two different readings of the same bytes cannot both be authenticated. + +**Failure semantics.** An authenticated denial is a peer statement; an unsigned error page is not. A failed verification raises `ResponseAuthenticationFailed` with the message "execution outcome is unknown", because a bad MAC does not prove the request failed to execute. The reference client never resubmits after an uncertain response. Losing pending state leaves the old outcome unknown, and a new request is a new occurrence even with the same business record identifier. + +### 4. Properties + +**Binding.** Because the key depends on the request digest, a valid statement from one occurrence fails under any other, under the usual HMAC and HKDF assumptions. Binding the status means a relay cannot turn a 403 into a 200 without breaking the MAC. + +**Single use.** Consumption under a lock means at most one verification attempt per pending state, so concurrent paths in the caller cannot each expose the same accepted response. + +**Audience.** The caller derives the same key, so it can build a statement its own verifier accepts. An exported MAC record therefore cannot prove authorship to an auditor. This is the deniability property that off-the-record messaging chose on purpose [6]. The runtime labels a verified response `audience: live-caller-only`, and that label is accurate. Portable proof of what a peer said needs a signature, which is a different design with different privacy consequences. + +### 5. Evaluation + +The subject is cA2A at commit `fc22c644846111c7f375446399e9097a73f93214`, the head of PR 187 [7]. That commit is not on a branch; its squash merge on main, `762f329`, has the identical tree (`9e06071`). Every number in this section comes from the rerun of October 1, 2026: Windows 11, Python 3.12.10, a fresh virtual environment built from the commit's hash-pinned `requirements/dev.txt`, and a fresh `git archive` export. The record, logs, JUnit reports and SHA-256 sums are in `experiments/results/rerun-2026-10-01/`. Every outcome is identical to the records of September 23 and 27, 2026. + +**Selected tests.** All **57 tests** in `tests/unit/test_response_authentication.py` pass. They include real loopback HTTP exchanges as well as verifier-level cases: an independent HKDF and HMAC calculation for a committed vector, substituted context fields, malformed JSON, deadlines, replay, peer rotation, strict versus legacy behavior and a response lost after execution. The file is a deliberate selection, not the whole cA2A suite. + +**Control removals.** Replacing the MAC comparison with `if False:` produced **4 failing tests**: two changed-response cases, the substituted response over real HTTP, and the committed vector's negative twins. Keeping pending state and its key for reuse produced **3 failing tests**: one-use, concurrent consumption and consumption on an invalid response. These counts show the selected tests depend on those controls. They are not a mutation-coverage claim. + +**Concurrency.** Twenty rounds of 16 verification attempts on eight worker threads, 320 attempts in all, accepted exactly one response per round. + +**Caller-built MAC.** The caller builds a statement with its own derived key and its verifier accepts it. This is the expected counterexample to reading the MAC as third-party evidence, not an outside attacker defeating the protocol; the experiment reads the private key attribute through a test seam. + +### 6. Cutting the connection + +Three kinds of process run on one host. The peer is the real cA2A HTTP server on an ephemeral loopback port, wrapped so that each executed task appends its record identifier to an fsync'd journal. A TCP relay between caller and peer counts submissions, captures the peer's full response, and cuts the caller's connection at a chosen point. Each caller is a new process that writes its own journal entry, then calls `send_task` in strict mode. After each case a new caller process reads the journal, re-appraises the same peer, and offers the captured response to a fresh pending state. + +The criterion: the client submits exactly once, reports unknown whenever the response cannot be verified, and a restarted caller cannot turn the captured response into an accepted result. + +| Relay behavior | Caller outcome | Peer executions | Submissions | Captured response in a new process | +|---|---|---|---|---| +| Pass through (control row) | accepted | 1 | 1 | refused | +| Cut before dispatch | unknown | 0 | 1 | nothing captured | +| Cut after the peer executed | unknown | 1 | 1 | refused | +| Truncate the response body | unknown | 1 | 1 | refused | +| Hold the response; caller killed | killed, journal says submitted | 1 | 1 | refused | + +All 5 cases met the criterion. The caller sees the same "unknown" when the peer never ran and when it ran once. Only the peer's journal tells them apart, which is exactly why the client must not turn unknown into a retry. + +The control changes the client, in an isolated copy, so that `send_task` resends the same request bytes once on `OSError`. Under it, cutting after execution produced 2 peer executions and 2 submissions for one business operation, and cutting before dispatch produced 2 submissions, so 3 of 5 cases met the criterion and 2 failed. The peer accepted the resend because the challenge it issues is stateless by design: an HMAC-protected token that any instance can check, valid until its 60 s expiry, which the cA2A source documents as at-most-once per window [8]. Single submission is therefore a property of the client. A deployment that needs exactly-once execution of a business operation needs deduplication on the peer side, which is what the expired IETF Idempotency-Key draft was written for at the HTTP layer [9]. + +### 7. Relation to other work + +HTTP Message Signatures lets a response signature cover components of the request through the `req` parameter, and requires the requester to keep those request values until it verifies the response [10]. OSCORE binds each response to its request by putting the request's key identifier and partial IV into the additional authenticated data [11]. The cA2A design uses the same idea at the application layer: the whole canonical request, including a fresh occurrence, feeds the key, and the caller's pending state is consumed on first use. What it adds for agent handoffs is the strict mode a peer cannot downgrade by omitting fields, the bound status and kind, and the explicit unknown outcome. The companion report *Confidentiality Across Agent Handoffs* [12] places this mechanism in a broader composition argument and reports it at the same commit; its 57-test figure is the same selection rerun here and must not be added to this report's count. + +### 8. Later changes on cA2A main + +Checked October 1, 2026 against cA2A main `19407fb`. The response module, the protocol requirements document and the selected test file are unchanged since the evaluated tree. PR 202 (merged September 25) fails closed on malformed wire input, including a malformed challenge MAC. PR 221 (merged October 1, release 0.4.0) mints caller offers in a caller role and adds a caller proof of possession over the request. Challenges are still stateless. The same harness, run against `19407fb` with that commit's own hash-pinned requirements, passes the same 57 tests, fails the same 4 and 3 tests under the two control removals, and reproduces every outcome of sections 5 and 6 (`experiments/results/main-19407fb-2026-10-01/`). The released `ca2a-runtime` 0.4.0 wheel carries `response.py` byte-identical to the pinned source apart from line endings; the harness was not run against the wheel. The evaluated result stays tied to `fc22c64`. + +### 9. Limits + +The response body is readable provenance or denial data; a MAC does not encrypt it, and returning confidential output needs a separate encryption decision. A compromised caller can fabricate its own accepted record, and a compromised peer can truthfully authenticate a wrong answer. The transport experiment ran on one host over loopback and measures no Internet latency or availability. Rotating the peer key on attested hardware and requiring a fresh appraisal before acceptance needs attested peer hardware and was not run. The test oracle and the implementation share some serializers and fixtures; the independent vector reduces that dependency only for the covered ASCII case. + +### 10. Reproduction + +From this directory, with a clone of agentrust-io/ca2a: + +```text +git -C fetch origin fc22c644846111c7f375446399e9097a73f93214 +git -C checkout --detach fc22c644846111c7f375446399e9097a73f93214 +python -m venv +/Scripts/python -m pip install --require-hashes -r /requirements/dev.txt +/Scripts/python -B experiments/run.py --source --output +``` + +If the PR head is ever unavailable, `--commit 762f329aeae589c28219b2cdc2d4a5d6e41407be` exports the identical tree from main. The runner exports the commit with `git archive`, runs the selection, both control removals, the concurrency and caller-MAC experiment, and the transport experiment with its control, and compares every outcome with `experiments/reference-2026-09.json`. Every listener binds to 127.0.0.1. It exits nonzero if a baseline fails, a control survives or a local path leaks into the output. + +### References + +[1] Agent2Agent (A2A) Protocol Specification, version 1.0.0. Accessed October 1, 2026. https://a2a-protocol.org/latest/specification/ + +[2] A. Rundgren, B. Jordan and S. Erdtman, "JSON Canonicalization Scheme (JCS)", RFC 8785, June 2020. https://www.rfc-editor.org/rfc/rfc8785.html + +[3] H. Krawczyk and P. Eronen, "HMAC-based Extract-and-Expand Key Derivation Function (HKDF)", RFC 5869, May 2010. https://www.rfc-editor.org/rfc/rfc5869.html + +[4] A. Langley, M. Hamburg and S. Turner, "Elliptic Curves for Security", RFC 7748, January 2016. https://www.rfc-editor.org/rfc/rfc7748.html + +[5] H. Krawczyk, M. Bellare and R. Canetti, "HMAC: Keyed-Hashing for Message Authentication", RFC 2104, February 1997. https://www.rfc-editor.org/rfc/rfc2104.html + +[6] N. Borisov, I. Goldberg and E. Brewer, "Off-the-Record Communication, or, Why Not To Use PGP", Workshop on Privacy in the Electronic Society (WPES), 2004, pp. 77 to 84. https://dl.acm.org/doi/10.1145/1029179.1029200 + +[7] AgenTrust project. cA2A PR 187, Authenticate live handoff responses and preserve timeout diagnostics, merged September 18, 2026; response implementation at commit fc22c64. https://github.com/agentrust-io/ca2a/pull/187 and https://github.com/agentrust-io/ca2a/blob/762f329aeae589c28219b2cdc2d4a5d6e41407be/src/ca2a_runtime/response.py + +[8] AgenTrust project. cA2A challenge primitive, src/ca2a_runtime/challenge.py. https://github.com/agentrust-io/ca2a/blob/762f329aeae589c28219b2cdc2d4a5d6e41407be/src/ca2a_runtime/challenge.py + +[9] J. Jena and S. Dalal, "The Idempotency-Key HTTP Header Field", Internet-Draft draft-ietf-httpapi-idempotency-key-header-07, expired. https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/ + +[10] A. Backman, J. Richer and M. Sporny, "HTTP Message Signatures", RFC 9421, February 2024, section 2.4. https://www.rfc-editor.org/rfc/rfc9421.html + +[11] G. Selander, J. Mattsson, F. Palombini and L. Seitz, "Object Security for Constrained RESTful Environments (OSCORE)", RFC 8613, July 2019, sections 5.4 and 7.1. https://www.rfc-editor.org/rfc/rfc8613.html + +[12] I. Siddique, "Confidentiality Across Agent Handoffs: Conditions for preserving an inference guarantee through tools and delegation", OPAQUE Systems technical report, version 2, September 30, 2026. DOI 10.5281/zenodo.23074670. https://agentrust-io.com/research/confidential-handoffs/