diff --git a/Makefile b/Makefile index 1c91fa6..f764cbf 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: validate-no-merge-duplication validate validate-json validate-yaml validate-quadlet validate-render validate-evidence validate-governance validate-policy-fabric validate-agent-registry validate-superconscious-runtime-plan validate-activation validate-supply-chain validate-release-bundle validate-sourceos-projections validate-package validate-cli validate-formula validate-runtime-install-receipts doctor probe validate-artifact-digest-honesty validate-consent-before-staging +.PHONY: validate-no-merge-duplication validate validate-json validate-yaml validate-quadlet validate-render validate-evidence validate-governance validate-policy-fabric validate-agent-registry validate-superconscious-runtime-plan validate-activation validate-attestation validate-supply-chain validate-release-bundle validate-sourceos-projections validate-package validate-cli validate-formula validate-runtime-install-receipts doctor probe validate-artifact-digest-honesty validate-consent-before-staging PYTHON ?= python3 RUBY ?= ruby @@ -22,7 +22,7 @@ DECIDED_AT := 2026-05-04T12:51:00Z PYCLI := PYTHONPATH=src $(PYTHON) -m agent_machine.cli PYMOD := PYTHONPATH=src $(PYTHON) -m -validate: validate-no-merge-duplication validate-json validate-yaml validate-quadlet validate-render validate-evidence validate-governance validate-policy-fabric validate-agent-registry validate-superconscious-runtime-plan validate-activation validate-supply-chain validate-release-bundle validate-sourceos-projections validate-package validate-cli validate-formula validate-runtime-install-receipts validate-artifact-digest-honesty validate-consent-before-staging +validate: validate-no-merge-duplication validate-json validate-yaml validate-quadlet validate-render validate-evidence validate-governance validate-policy-fabric validate-agent-registry validate-superconscious-runtime-plan validate-activation validate-attestation validate-supply-chain validate-release-bundle validate-sourceos-projections validate-package validate-cli validate-formula validate-runtime-install-receipts validate-artifact-digest-honesty validate-consent-before-staging validate-no-merge-duplication: $(PYTHON) scripts/validate-no-merge-duplication.py @@ -88,6 +88,9 @@ validate-activation: $(PYCLI) activate evaluate $(LOCAL_AGENTPOD) --policy-dir $(POLICY_DIR) --expected-status allowed --grant-dir $(GRANT_DIR) --grant-id urn:srcos:agent-machine:agent-registry-grant:active-loopback-activation --session-ref urn:srcos:session:local-bootstrap --deployment-receipt-id $(DEPLOYMENT_RECEIPT_ID) --agent-machine-id urn:srcos:agent-machine:m2-asahi-local --provider-id urn:srcos:agent-machine:inference-provider:asahi-llama-cpp --storage-receipt-dir $(RECEIPT_DIR) --decided-at $(DECIDED_AT) --decision-id urn:srcos:agent-machine:activation-decision:local-llama-cpp-allowed --pretty >/tmp/agent-machine-pycli-resolved-policy-grant-activation-allowed.json $(BOOTSTRAP_CLI) activate evaluate $(LOCAL_AGENTPOD) $(READY_POLICY) $(READY_GRANT) --deployment-receipt-id $(DEPLOYMENT_RECEIPT_ID) --storage-receipt-dir $(RECEIPT_DIR) --decided-at $(DECIDED_AT) --decision-id urn:srcos:agent-machine:activation-decision:local-llama-cpp-allowed --pretty >/tmp/agent-machine-bootstrap-evaluate-activation-allowed.json +validate-attestation: + $(PYTHON) scripts/validate-attestation.py + validate-supply-chain: $(PYTHON) scripts/validate-supply-chain.py $(PYMOD) agent_machine.supply_chain $(PINNED_AGENTPOD) --strict diff --git a/contracts/attestation-decision.schema.json b/contracts/attestation-decision.schema.json new file mode 100644 index 0000000..d793a18 --- /dev/null +++ b/contracts/attestation-decision.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:srcos:agent-machine:schema:attestation-decision:v0.1.0", + "title": "AttestationDecision", + "description": "Host attestation decision for whether an Agent Machine host may be activated. activation_permitted is true only if ALL blocking checks pass; a single blocking failure denies activation. Advisory checks (TELEMETRY_BOUNDARY/SEAM-013, DISPLAY_UUID_STABILITY/SEAM-008) warn only and never block. Field names are snake_case per Tranche Work Order T2-1.", + "type": "object", + "additionalProperties": false, + "required": ["specVersion","kind","attestation_id","host_id","attested_at","attested_by","activation_permitted","overall_status","check_results"], + "properties": { + "specVersion": { "type": "string", "const": "0.1.0" }, + "kind": { "type": "string", "const": "AttestationDecision" }, + "attestation_id": { "type": "string", "pattern": "^urn:srcos:agent-machine:attestation-decision:[a-z0-9][a-z0-9-]*$" }, + "host_id": { "type": "string", "minLength": 1 }, + "attested_at": { "type": "string", "format": "date-time" }, + "attested_by": { "type": "string", "minLength": 1 }, + "activation_permitted": { "type": "boolean" }, + "overall_status": { "type": "string", "enum": ["pass","fail","partial"] }, + "check_results": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["check_id","check_name","status","expected","actual","blocking"], + "properties": { + "check_id": { "type": "string", "minLength": 1 }, + "check_name": { "type": "string" }, + "status": { "type": "string", "enum": ["pass","fail","warn","skip"] }, + "expected": { "type": "string" }, + "actual": { "type": "string" }, + "blocking": { "type": "boolean" } + } + } + } + } +} diff --git a/docs/architecture/attestation-model.md b/docs/architecture/attestation-model.md new file mode 100644 index 0000000..0fed6b2 --- /dev/null +++ b/docs/architecture/attestation-model.md @@ -0,0 +1,46 @@ +# Attestation Model (v0.1) + +## Attestation vs activation +**Attestation** measures host trust (SIP, NVRAM, MDM, SEP, security-DB schema, +DNS, TLS, EFI) and emits an `AttestationDecision`. **Activation** (existing +`ActivationDecision`) decides whether an AgentPod may run. Attestation is a +*prerequisite input* to activation: `activation_permitted: false` from +attestation blocks activation regardless of policy/grant state. + +## Check taxonomy +- **Blocking** (a single failure denies activation): `SIP_STATUS`, + `NVRAM_INTEGRITY`, `MDM_PROFILE_AUDIT`, `SEP_PARTICIPATION`, + `SECURITY_DB_SCHEMA`, `DNS_INTEGRITY`, `TLS_CIPHER_AUDIT`, `EFI_SIGNATURE`. +- **Advisory** (warn only): `TELEMETRY_BOUNDARY` (SEAM-013), + `DISPLAY_UUID_STABILITY` (SEAM-008). + +`activation_permitted` is true **iff every blocking check passes** — enforced by +`scripts/validate-attestation.py`. + +## Failure-mode basis (SEAM references) +Each check maps to an observed diagnostic failure mode: +- `SIP_STATUS` — SIP disabled via NVRAM (csr-active-config=0x67) — SEAM-001. +- `NVRAM_INTEGRITY` — world-writable nvram.plist — SEAM-001. +- `MDM_PROFILE_AUDIT` — unauthorized MDM enrollment (IBM MaaS360) — SEAM-002. +- `SEP_PARTICIPATION` — SEP non-participation in keybag (ANOMALY-007) — SEAM-003. +- `SECURITY_DB_SCHEMA` — iCloud Keychain schema mismatch (v24.6 vs v25+; + missing ZSEQUENCENUMBER in ZPEER; broken escrow chain). +- `DNS_INTEGRITY` — resolution returning 102.165.31.x (adversary IPs) — SEAM-004. +- `TLS_CIPHER_AUDIT` — cipher downgrade (RC4_128_MD5) — SEAM-005. +- `EFI_SIGNATURE` — unsigned/tampered EFI — SEAM-001. + +## Telemetry boundary (SEAM-013) +`TELEMETRY_BOUNDARY` is **advisory, not blocking**: Claude Desktop +`sessionSampleRate:100` means sensitive output must not route through a +100%-sampled surface. Recorded as a warn signal in the decision, not a hard +gate. + +## Out-of-band ledger +The `attest` runtime CLI (T2-3, follow-up) must append each decision to +`$LEDGER_PATH` as NDJSON and never silently drop; `$LEDGER_PATH` must never be +local-only for a ledger-writing artifact. + +## References +`AttestationDecision` (T2-1, `contracts/attestation-decision.schema.json`); +`ActivationDecision` (existing). Runtime `attest` subcommand: T2-3 (deferred to a +macOS attestation environment — the schema/contract here is what T4-4 depends on). diff --git a/examples/attestation-decision.failed-mdm.json b/examples/attestation-decision.failed-mdm.json new file mode 100644 index 0000000..3f84ea3 --- /dev/null +++ b/examples/attestation-decision.failed-mdm.json @@ -0,0 +1,92 @@ +{ + "specVersion": "0.1.0", + "kind": "AttestationDecision", + "attestation_id": "urn:srcos:agent-machine:attestation-decision:failed-mdm", + "host_id": "host-alpha-001", + "attested_at": "2026-07-31T04:00:00Z", + "attested_by": "urn:srcos:agent-machine:attest", + "activation_permitted": false, + "overall_status": "fail", + "check_results": [ + { + "check_id": "SIP_STATUS", + "check_name": "System Integrity Protection", + "status": "pass", + "expected": "csr-active-config=0x00 (enabled)", + "actual": "0x00 (enabled)", + "blocking": true + }, + { + "check_id": "NVRAM_INTEGRITY", + "check_name": "NVRAM plist permissions", + "status": "pass", + "expected": "0644 root-owned", + "actual": "0644 root-owned", + "blocking": true + }, + { + "check_id": "MDM_PROFILE_AUDIT", + "check_name": "MDM configuration profile audit", + "status": "fail", + "expected": "no unrecognized profiles", + "actual": "IBM MaaS360 (unauthorized)", + "blocking": true + }, + { + "check_id": "SEP_PARTICIPATION", + "check_name": "Secure Enclave participation", + "status": "pass", + "expected": "SEP participates in keybag", + "actual": "SEP participates in keybag", + "blocking": true + }, + { + "check_id": "SECURITY_DB_SCHEMA", + "check_name": "iCloud Keychain schema", + "status": "pass", + "expected": "v25+ with ZSEQUENCENUMBER in ZPEER", + "actual": "v25+ with ZSEQUENCENUMBER in ZPEER", + "blocking": true + }, + { + "check_id": "DNS_INTEGRITY", + "check_name": "DNS resolution integrity", + "status": "pass", + "expected": "platform endpoints resolve normally", + "actual": "platform endpoints resolve normally", + "blocking": true + }, + { + "check_id": "TLS_CIPHER_AUDIT", + "check_name": "TLS cipher suite audit", + "status": "pass", + "expected": "TLS1.2+; no RC4/MD5/SHA1", + "actual": "TLS1.2+; no RC4/MD5/SHA1", + "blocking": true + }, + { + "check_id": "EFI_SIGNATURE", + "check_name": "EFI firmware signature", + "status": "pass", + "expected": "signed vendor firmware", + "actual": "signed vendor firmware", + "blocking": true + }, + { + "check_id": "TELEMETRY_BOUNDARY", + "check_name": "Claude Desktop telemetry boundary (SEAM-013)", + "status": "warn", + "expected": "sensitive output not routed through 100%-sampled surface", + "actual": "sessionSampleRate:100", + "blocking": false + }, + { + "check_id": "DISPLAY_UUID_STABILITY", + "check_name": "Display UUID stability (SEAM-008)", + "status": "warn", + "expected": "stable displayUUID", + "actual": "stable displayUUID", + "blocking": false + } + ] +} diff --git a/examples/attestation-decision.failed-sip.json b/examples/attestation-decision.failed-sip.json new file mode 100644 index 0000000..e7cb0b1 --- /dev/null +++ b/examples/attestation-decision.failed-sip.json @@ -0,0 +1,92 @@ +{ + "specVersion": "0.1.0", + "kind": "AttestationDecision", + "attestation_id": "urn:srcos:agent-machine:attestation-decision:failed-sip", + "host_id": "host-alpha-001", + "attested_at": "2026-07-31T04:00:00Z", + "attested_by": "urn:srcos:agent-machine:attest", + "activation_permitted": false, + "overall_status": "fail", + "check_results": [ + { + "check_id": "SIP_STATUS", + "check_name": "System Integrity Protection", + "status": "fail", + "expected": "csr-active-config=0x00 (enabled)", + "actual": "0x67 (SIP disabled via NVRAM)", + "blocking": true + }, + { + "check_id": "NVRAM_INTEGRITY", + "check_name": "NVRAM plist permissions", + "status": "pass", + "expected": "0644 root-owned", + "actual": "0644 root-owned", + "blocking": true + }, + { + "check_id": "MDM_PROFILE_AUDIT", + "check_name": "MDM configuration profile audit", + "status": "pass", + "expected": "no unrecognized profiles", + "actual": "no unrecognized profiles", + "blocking": true + }, + { + "check_id": "SEP_PARTICIPATION", + "check_name": "Secure Enclave participation", + "status": "pass", + "expected": "SEP participates in keybag", + "actual": "SEP participates in keybag", + "blocking": true + }, + { + "check_id": "SECURITY_DB_SCHEMA", + "check_name": "iCloud Keychain schema", + "status": "pass", + "expected": "v25+ with ZSEQUENCENUMBER in ZPEER", + "actual": "v25+ with ZSEQUENCENUMBER in ZPEER", + "blocking": true + }, + { + "check_id": "DNS_INTEGRITY", + "check_name": "DNS resolution integrity", + "status": "pass", + "expected": "platform endpoints resolve normally", + "actual": "platform endpoints resolve normally", + "blocking": true + }, + { + "check_id": "TLS_CIPHER_AUDIT", + "check_name": "TLS cipher suite audit", + "status": "pass", + "expected": "TLS1.2+; no RC4/MD5/SHA1", + "actual": "TLS1.2+; no RC4/MD5/SHA1", + "blocking": true + }, + { + "check_id": "EFI_SIGNATURE", + "check_name": "EFI firmware signature", + "status": "pass", + "expected": "signed vendor firmware", + "actual": "signed vendor firmware", + "blocking": true + }, + { + "check_id": "TELEMETRY_BOUNDARY", + "check_name": "Claude Desktop telemetry boundary (SEAM-013)", + "status": "warn", + "expected": "sensitive output not routed through 100%-sampled surface", + "actual": "sessionSampleRate:100", + "blocking": false + }, + { + "check_id": "DISPLAY_UUID_STABILITY", + "check_name": "Display UUID stability (SEAM-008)", + "status": "warn", + "expected": "stable displayUUID", + "actual": "stable displayUUID", + "blocking": false + } + ] +} diff --git a/examples/attestation-decision.passed.json b/examples/attestation-decision.passed.json new file mode 100644 index 0000000..f469cb9 --- /dev/null +++ b/examples/attestation-decision.passed.json @@ -0,0 +1,92 @@ +{ + "specVersion": "0.1.0", + "kind": "AttestationDecision", + "attestation_id": "urn:srcos:agent-machine:attestation-decision:passed", + "host_id": "host-alpha-001", + "attested_at": "2026-07-31T04:00:00Z", + "attested_by": "urn:srcos:agent-machine:attest", + "activation_permitted": true, + "overall_status": "pass", + "check_results": [ + { + "check_id": "SIP_STATUS", + "check_name": "System Integrity Protection", + "status": "pass", + "expected": "csr-active-config=0x00 (enabled)", + "actual": "0x00 (enabled)", + "blocking": true + }, + { + "check_id": "NVRAM_INTEGRITY", + "check_name": "NVRAM plist permissions", + "status": "pass", + "expected": "0644 root-owned", + "actual": "0644 root-owned", + "blocking": true + }, + { + "check_id": "MDM_PROFILE_AUDIT", + "check_name": "MDM configuration profile audit", + "status": "pass", + "expected": "no unrecognized profiles", + "actual": "no unrecognized profiles", + "blocking": true + }, + { + "check_id": "SEP_PARTICIPATION", + "check_name": "Secure Enclave participation", + "status": "pass", + "expected": "SEP participates in keybag", + "actual": "SEP participates in keybag", + "blocking": true + }, + { + "check_id": "SECURITY_DB_SCHEMA", + "check_name": "iCloud Keychain schema", + "status": "pass", + "expected": "v25+ with ZSEQUENCENUMBER in ZPEER", + "actual": "v25+ with ZSEQUENCENUMBER in ZPEER", + "blocking": true + }, + { + "check_id": "DNS_INTEGRITY", + "check_name": "DNS resolution integrity", + "status": "pass", + "expected": "platform endpoints resolve normally", + "actual": "platform endpoints resolve normally", + "blocking": true + }, + { + "check_id": "TLS_CIPHER_AUDIT", + "check_name": "TLS cipher suite audit", + "status": "pass", + "expected": "TLS1.2+; no RC4/MD5/SHA1", + "actual": "TLS1.2+; no RC4/MD5/SHA1", + "blocking": true + }, + { + "check_id": "EFI_SIGNATURE", + "check_name": "EFI firmware signature", + "status": "pass", + "expected": "signed vendor firmware", + "actual": "signed vendor firmware", + "blocking": true + }, + { + "check_id": "TELEMETRY_BOUNDARY", + "check_name": "Claude Desktop telemetry boundary (SEAM-013)", + "status": "pass", + "expected": "sensitive output not routed through 100%-sampled surface", + "actual": "sensitive output not routed through sampled surface", + "blocking": false + }, + { + "check_id": "DISPLAY_UUID_STABILITY", + "check_name": "Display UUID stability (SEAM-008)", + "status": "pass", + "expected": "stable displayUUID", + "actual": "stable displayUUID", + "blocking": false + } + ] +} diff --git a/scripts/validate-attestation.py b/scripts/validate-attestation.py new file mode 100644 index 0000000..2ce2b9d --- /dev/null +++ b/scripts/validate-attestation.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +"""Validate AttestationDecision contract + examples (T2-1, T2-4/5). + +Asserts the fail-closed activation rule by construction: + * every example validates against contracts/attestation-decision.schema.json; + * all eight required BLOCKING checks are present; + * activation_permitted is true IFF every blocking check passed — a single + blocking failure must deny activation; + * overall_status is consistent (pass = all checks pass; fail = a blocking + check failed; partial otherwise); + * the passed/failed-sip/failed-mdm fixtures carry the expected verdicts. +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +try: + import jsonschema +except ImportError: # pragma: no cover + print("ERR: jsonschema not installed", file=sys.stderr) + sys.exit(2) + +ROOT = Path(__file__).resolve().parents[1] +SCHEMA = ROOT / "contracts" / "attestation-decision.schema.json" +EXAMPLES = { + "passed": True, + "failed-sip": False, + "failed-mdm": False, +} +REQUIRED_BLOCKING = { + "SIP_STATUS", "NVRAM_INTEGRITY", "MDM_PROFILE_AUDIT", "SEP_PARTICIPATION", + "SECURITY_DB_SCHEMA", "DNS_INTEGRITY", "TLS_CIPHER_AUDIT", "EFI_SIGNATURE", +} + + +def load(p: Path) -> dict: + return json.loads(p.read_text(encoding="utf-8")) + + +def fail(msg: str) -> None: + print(f"FAIL: {msg}", file=sys.stderr) + sys.exit(1) + + +def main() -> int: + schema = load(SCHEMA) + jsonschema.Draft202012Validator.check_schema(schema) + validator = jsonschema.Draft202012Validator(schema) + + for name, expect_permitted in EXAMPLES.items(): + path = ROOT / "examples" / f"attestation-decision.{name}.json" + doc = load(path) + errs = sorted(validator.iter_errors(doc), key=lambda e: list(e.path)) + if errs: + fail(f"{path.name} schema-invalid: {errs[0].message}") + + checks = doc["check_results"] + blocking = [c for c in checks if c["blocking"]] + blocking_ids = {c["check_id"] for c in blocking} + missing = REQUIRED_BLOCKING - blocking_ids + if missing: + fail(f"{path.name}: missing required blocking checks: {sorted(missing)}") + + all_blocking_pass = all(c["status"] == "pass" for c in blocking) + any_blocking_fail = any(c["status"] == "fail" for c in blocking) + all_pass = all(c["status"] == "pass" for c in checks) + + # activation_permitted IFF all blocking checks pass. + if doc["activation_permitted"] != all_blocking_pass: + fail( + f"{path.name}: activation_permitted={doc['activation_permitted']} but " + f"all_blocking_pass={all_blocking_pass} (fail-closed rule violated)" + ) + # overall_status consistency. + expected_overall = "fail" if any_blocking_fail else ("pass" if all_pass else "partial") + if doc["overall_status"] != expected_overall: + fail(f"{path.name}: overall_status={doc['overall_status']} but expected {expected_overall}") + # fixture-level expectation. + if doc["activation_permitted"] != expect_permitted: + fail(f"{path.name}: expected activation_permitted={expect_permitted}") + + print( + f"OK: AttestationDecision schema + {len(EXAMPLES)} examples valid; " + f"fail-closed activation rule and 8 blocking checks enforced" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/agent_machine/contracts.py b/src/agent_machine/contracts.py index 5b12bd3..fd41498 100644 --- a/src/agent_machine/contracts.py +++ b/src/agent_machine/contracts.py @@ -47,6 +47,7 @@ def schema_by_kind(root: Path | None = None) -> dict[str, Path]: return { "A2AStateMachine": base / "a2a-state-machine.schema.json", "ActivationDecision": base / "activation-decision.schema.json", + "AttestationDecision": base / "attestation-decision.schema.json", "ArtifactConsentRecord": base / "artifact-consent-record.schema.json", "AgentMachine": base / "agent-machine.schema.json", "AgentPlaneRuntimeEvidence": base / "agentplane-runtime-evidence.schema.json",