Skip to content

v0.8.1 — Security hardening

Choose a tag to compare

@requie requie released this 02 Jul 18:06
· 53 commits to main since this release
12b7ff3

A security-hardening release. It closes every finding from a full adversarial audit of the v0.8.0 surface — the attestation trust model, policy enforcement, deserialization, resource bounds, information disclosure, credential transport, and on-disk permissions. No new capabilities; drop-in from 0.8.0 except for the two changes under Breaking changes and Migration. Every fix ships with tests (Python 611 passing, full TypeScript suite green, mypy --strict clean).

Security

  • [Critical] Signature-aware chain verification with a trust anchor. verify_chain() only proves hash linkage, and content_hash is an unkeyed SHA-256 — an attacker who controls a serialized chain can edit any record, recompute the links, and pass verify_chain(). Per-record verify() also checked the signature against the key embedded in that same record, so a chain forged with an attacker key self-verified. New AttestationChain.verify_signatures(trusted_keys=…) verifies every Ed25519 signature and rejects records whose embedded key isn't in a pinned set.
  • [Critical] verify-decisions CLI no longer blesses unsigned chains. It previously printed chain valid: yes / verified: yes and exited 0 for an unsigned or forged chain. It now reports signature status, prints unsigned records as unsigned, accepts repeatable --trusted-key <pub.hex>, and exits non-zero unless signatures verify.
  • [High] Enforcement fails closed on escalate, not only block. Under enforce=True, the built-in REQUIRE_APPROVAL policies (high-risk tool, code-execution, financial threshold, multi-agent), cortical drift, and recovery chain-tamper all emit escalate, which enforcement ignored — "require approval" silently proceeded. _BaseAdapter and IntegrityMonitor now take an approval_handler: block always denies, escalate denies unless the handler approves it (no handler ⇒ fail closed).
  • [High] Embedding-similarity cache moved from pickle to JSON. The cache was unpickled before its signature check, so a filesystem-write attacker (T-T2) could poison it into arbitrary code execution on load. JSON deserializes to inert data.
  • [High] chain_valid no longer overstates the guarantee — see Breaking changes.
  • [Medium] Bounded context buffers (memory-exhaustion). All accumulating adapter buffers are capped at 1000/session via a shared helper (previously only broadcasts were capped); overflow emits a one-time <channel>_overflow event.
  • [Medium] Exception text no longer leaks into serialized records. The exported capture_failure event and a crashing governance rule's reason now carry only the exception class, not the raw message (which can embed tokens/URLs/PII); full detail stays in local logs.
  • [Medium] Allow-list validation for filesystem-bound identifiers. checkpoint_id, agent_id, and role now use a non-empty ASCII alphanumeric/underscore/hyphen allow-list, closing empty-id → .json, dotfiles, NUL bytes, reserved names, and a __ role-separator collision (baseline misattribution).
  • [Low] TLS-gated Bearer token in the TypeScript reporter. The Authorization header is sent only over HTTPS or loopback; a misconfigured http://<remote> no longer leaks the token.
  • [Low] Restrictive store-file permissions. Freshly-created store dirs are 0700 and sqlite DB files 0600 (best-effort; pre-existing dirs untouched).
  • [Low] Race-free store reads/deletes. load/delete now read/unlink-then-catch FileNotFoundError instead of exists()-then-act (TOCTOU).

Added

  • AttestationChain.verify_signatures(trusted_keys=…) — trust-anchored chain verification.
  • approval_handler on _BaseAdapter and IntegrityMonitor.
  • GovernanceLayer(sensitive_tools=…) + per-call context["sensitive_tools"]; exported DEFAULT_SENSITIVE_TOOLS.
  • --trusted-key <pub.hex> on python -m agentegrity verify-decisions.

Changed

  • BREAKING — session-summary field chain_valid renamed to chain_hash_linked across the Python adapters, the TypeScript client, and schemas/exporter/common.json. The old name read as cryptographic proof; the value only reflects hash linkage.
  • Enforcement under enforce=True treats escalate as blocking-pending-approval, not advisory.

CI / tooling

  • Added a dependency-audit CI job (pip-audit + bun audit, advisory) and Dependabot config for pip, npm, and github-actions.

Migration from 0.8.0

  • chain_validchain_hash_linked: update any exporter/get_summary() consumer (Python + TS) reading the field; value unchanged, key renamed. Backends validating against schemas/exporter/common.json must adopt the renamed required field.
  • enforce=True + escalating policies: pass an approval_handler(profile, score, action) -> bool if you need escalations to proceed; otherwise they now deny (fail closed).
  • Chain verification: verify_chain() is unchanged. Trust-boundary consumers should adopt verify_signatures(trusted_keys=…) with an out-of-band key; the CLI needs --trusted-key to report a chain as cryptographically verified.