Skip to content

Commit 4f300ea

Browse files
committed
examples(cognitive-attestation-governed): address automated review feedback
Incorporates the actionable items from the code-reviewer and security-scanner bot reviews on this PR. Six fixes: 1. Add 'timestamp' field to the envelope and bind it into the canonical form. Replay defence: a reused envelope still carries its original timestamp, which a verifier can reject against a freshness policy. 2. Clarify tamper-detection output wording — 'PASS (tampering detected, envelope rejected)' and 'FAIL (tampering not detected, envelope accepted)'. No ambiguity about which outcome is desirable. 3. Expand the policy-evaluator docstring to state unambiguously that it is a minimal placeholder, not a substitute for the AGT policy engine. 4. Add a prominent note above the minimal JCS implementation flagging that it is NOT a full RFC 8785 implementation and pointing at spec-conformant libraries (jcs on PyPI, the APS SDK reference impl). 5. Add a 'Security notes' section to the README covering: key management (OS keychain, Vault, HSM options), the JCS-minimal disclaimer, and the policy-placeholder disclaimer. 6. Document the feature-activation sort order rationale ('feature_id, activation_statistic' is required by the spec for cross-impl reproducibility) and cite the exact spec section. No em dashes. Both verification and tamper-rejection still PASS. Kept it deliberately self-contained; did not introduce Pydantic or pytest dependencies for a single-file example.
1 parent 11ada54 commit 4f300ea

2 files changed

Lines changed: 53 additions & 10 deletions

File tree

examples/cognitive-attestation-governed/README.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ Policy enforcement answers whether an action is permitted. It does not explain t
1313
- `action_ref`: content-addressed hash of the action being attested
1414
- `feature_activations`: sparse-autoencoder features with activation statistics, canonically sorted
1515
- `dictionary_ref`: which SAE dictionary produced the features (reproducibility pointer)
16-
- `canonical_hash`: RFC 8785 JCS canonicalization over the envelope
16+
- `timestamp`: ISO 8601 UTC timestamp, bound into the signature for replay defence
17+
- `canonical_hash`: [RFC 8785](https://datatracker.ietf.org/doc/html/rfc8785) JCS canonicalization over the envelope
1718
- Ed25519 signature over the canonical form
1819

1920
The envelope is small (~1-3 KB), JCS-canonical, and verifiable offline with a single Ed25519 public key.
@@ -23,9 +24,9 @@ The envelope is small (~1-3 KB), JCS-canonical, and verifiable offline with a si
2324
1. AGT evaluates a policy (allow/deny) before execution
2425
2. If allowed, the agent produces an action
2526
3. A Cognitive Attestation envelope is built over that action, carrying SAE feature activations that represent the decomposed model state
26-
4. The envelope is Ed25519-signed and JCS-canonicalized
27+
4. The envelope is Ed25519-signed and JCS-canonicalized with timestamp bound into the signature
2728
5. A second party verifies the envelope offline using only the public key and the canonical schema
28-
6. A tampered envelope is rejected by the verifier
29+
6. A tampered envelope is rejected by the verifier with the reason surfaced
2930

3031
## Install
3132

@@ -41,7 +42,7 @@ The Cognitive Attestation primitive used here is a small self-contained implemen
4142
python getting_started.py
4243
```
4344

44-
Expected output: an AGT-style policy decision, then a signed Cognitive Attestation envelope, then a passing offline verification, then a passing tamper rejection.
45+
Expected output: an AGT-style policy decision, then a signed Cognitive Attestation envelope, then a passing offline verification, then a tamper rejection that explicitly reports detection.
4546

4647
## How it composes
4748

@@ -60,16 +61,25 @@ Agent action
6061

6162
Policy and attestation are separate layers. A decision can be permitted by policy yet produce a revealing attestation (for audit), or denied by policy and produce nothing. The two are complementary.
6263

64+
## Security notes
65+
66+
The Ed25519 private key in `getting_started.py` is generated on the fly for demonstration. Production deployments MUST store signing keys securely. Options that are appropriate in order of increasing assurance: OS keychain (Keychain on macOS, DPAPI on Windows, libsecret on Linux); HashiCorp Vault or cloud KMS; an HSM or TPM-backed key store such as Azure Key Vault Managed HSM, AWS CloudHSM, or YubiHSM. The example does not prescribe one, but a signing key that ends up in a container image, a git repo, or an unencrypted disk defeats the purpose of the attestation chain.
67+
68+
The minimal JCS implementation in `getting_started.py` covers the field types used by this envelope but is NOT a full RFC 8785 implementation. Production code should use a spec-conformant library (the [`jcs`](https://pypi.org/project/jcs/) PyPI package, or the APS SDK's implementation which is tested against cross-language conformance vectors).
69+
70+
The policy evaluator in the example is a minimal placeholder to keep the example self-contained. It is NOT a substitute for AGT's real policy engine.
71+
6372
## Prior art and attribution
6473

6574
- Paper: *Cognitive Attestation: Signing Interpretable Decompositions of Latent Model State in AI Agent Governance*, Zenodo DOI [10.5281/zenodo.19646276](https://doi.org/10.5281/zenodo.19646276), April 2026.
66-
- Reference implementation: [aeoess/agent-passport-system](https://github.com/aeoess/agent-passport-system) (Apache 2.0, v2.1.0 on npm and PyPI).
75+
- Reference implementation: [aeoess/agent-passport-system](https://github.com/aeoess/agent-passport-system) (Apache 2.0, v2.2.0 on npm and PyPI). Full primitive lives in `src/v2/cognitive-attestation/` with 29 tests including cross-language conformance vectors.
6776
- Schema: `papers/paper-4/poc/schema/cognitive_attestation.schema.json` in the reference repo.
68-
- Canonicalization: RFC 8785 (JCS).
77+
- Canonicalization: [RFC 8785](https://datatracker.ietf.org/doc/html/rfc8785) (JCS).
6978
- Signature: Ed25519 via standard libraries.
7079

7180
## Limitations
7281

7382
- Interpretability depends on the underlying SAE dictionary. Choosing a dictionary is a governance decision; this example uses a fixed placeholder dictionary ref for reproducibility.
7483
- Feature labels are dictionary-author-assigned and not independently verified by the attestation itself. A v1.1 validation pass is on the reference-implementation roadmap.
84+
- The minimal JCS here does not handle Unicode normalization edge cases or all IEEE 754 special values. See the "Security notes" above.
7585
- This example is community-contributed, not part of AGT's core runtime. Treat outputs as experimental.

examples/cognitive-attestation-governed/getting_started.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from dataclasses import dataclass, field
1818
from typing import Any
1919

20+
from datetime import datetime, timezone
21+
2022
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
2123
Ed25519PrivateKey,
2224
Ed25519PublicKey,
@@ -31,6 +33,14 @@
3133
# ---------------------------------------------------------------------------
3234
# RFC 8785 JCS canonicalization (minimal subset sufficient for this envelope)
3335
# ---------------------------------------------------------------------------
36+
# NOTE: This is a MINIMAL JCS implementation that covers the field types used
37+
# in this example envelope. It does not implement the full RFC 8785 edge
38+
# cases (Unicode normalization, certain IEEE 754 special values, etc.).
39+
# Production code should use a fully-tested JCS library such as `jcs` on PyPI
40+
# or the reference implementation at github.com/cyberphone/json-canonicalization.
41+
# The APS SDK at github.com/aeoess/agent-passport-system ships a spec-conformant
42+
# implementation used for all real signatures.
43+
# ---------------------------------------------------------------------------
3444

3545
def canonicalize_jcs(value: Any) -> bytes:
3646
"""RFC 8785 JSON Canonicalization Scheme (minimal)."""
@@ -88,17 +98,31 @@ def build_envelope(
8898
features: list[FeatureActivation],
8999
dictionary_ref: str,
90100
signer_role: str = "agent",
101+
timestamp: str | None = None,
91102
) -> dict[str, Any]:
92-
"""Build the unsigned envelope (canonical form, ready to sign)."""
103+
"""Build the unsigned envelope (canonical form, ready to sign).
104+
105+
The `timestamp` field is included in the canonical form and therefore
106+
in the signature. This prevents replay of a valid envelope into a
107+
different point in time: any attempt to reuse a previously-signed
108+
envelope will still carry the original timestamp, which a verifier
109+
can reject against freshness policy.
110+
"""
93111
action_bytes = canonicalize_jcs(action)
94112
action_ref = "sha256:" + hashlib.sha256(action_bytes).hexdigest()
95113

96-
# Canonical sort: (feature_id, activation_statistic) as spec requires
114+
# Canonical sort: (feature_id, activation_statistic). This order is
115+
# required by the Cognitive Attestation spec (Zenodo 10.5281/zenodo.19646276,
116+
# Section 3.2) so that two independently-produced envelopes over the
117+
# same feature set produce identical canonical bytes.
97118
sorted_features = sorted(
98119
features,
99120
key=lambda f: (f.feature_id, f.activation_statistic),
100121
)
101122

123+
if timestamp is None:
124+
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
125+
102126
envelope = {
103127
"spec_version": "1.0",
104128
"action_ref": action_ref,
@@ -112,6 +136,7 @@ def build_envelope(
112136
for f in sorted_features
113137
],
114138
"signer_role": signer_role,
139+
"timestamp": timestamp,
115140
}
116141
canonical = canonicalize_jcs(envelope)
117142
envelope["canonical_hash"] = "sha256:" + hashlib.sha256(canonical).hexdigest()
@@ -156,7 +181,15 @@ def verify_envelope(signed: dict[str, Any]) -> bool:
156181
# ---------------------------------------------------------------------------
157182

158183
def evaluate_policy(action: dict[str, Any], policy: dict[str, Any]) -> dict[str, Any]:
159-
"""Minimal policy check. In production, use agent-governance-toolkit."""
184+
"""Minimal policy check for demonstration purposes ONLY.
185+
186+
This is NOT a substitute for the AGT policy engine. It intentionally
187+
implements only exact-match tool name rules so this example is fully
188+
self-contained and does not pull AGT as a heavy dependency. Real
189+
deployments MUST replace this with `agent-governance-toolkit`'s
190+
policy engine, which supports regex matches, nested conditions,
191+
temporal rules, obligations, and the full AGT rule schema.
192+
"""
160193
tool = action.get("tool", "")
161194
for rule in policy.get("rules", []):
162195
match = rule.get("match", {}).get("tool", {})
@@ -256,7 +289,7 @@ def main() -> None:
256289
tampered = json.loads(json.dumps(signed))
257290
tampered["feature_activations"][0]["activation_statistic"] = 0.99
258291
ok2 = verify_envelope(tampered)
259-
print(f"Tamper detection: {'PASS (rejected)' if not ok2 else 'FAIL (accepted)'}")
292+
print(f"Tamper detection: {'PASS (tampering detected, envelope rejected)' if not ok2 else 'FAIL (tampering not detected, envelope accepted)'}")
260293

261294

262295
if __name__ == "__main__":

0 commit comments

Comments
 (0)