Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,59 @@ All notable changes to Immunity Agent (Prismor Warden) are documented here.
The format loosely follows [Keep a Changelog](https://keepachangelog.com/)
and the project uses [Semantic Versioning](https://semver.org/).

## [1.3.0] — 2026-05-03

Signed audit log. Every Warden decision (allow / observe / block) is now
recorded as a hash-chained, optionally Ed25519-signed record so that a third
party with the public key can verify what the agent attempted, what was
blocked, and why — and prove the log has not been tampered with.

### Added

- **`warden/audit_log.py`** — append-only NDJSON decision log at
`.prismor-warden/audit/<session>.ndjson`. Each record carries `seq`,
`prev_hash`, `record_hash` (SHA-256 of canonical bytes), the redacted event,
the decision, the matching findings, and pinned `policy_hash` and
`feed_hash` so a verifier can reconstruct the policy that was in effect at
decision time. Pinned policy and feed snapshots are written once per hash
under `audit/policies/<hash>/` and `audit/feed/<hash>.json`.
- **`warden/signing.py`** — Ed25519 keygen, sign, verify, and key fingerprint
helpers. Uses the `cryptography` library when present; falls back to
`openssl pkeyutl` (the same approach as `pipeline/sign_feed.sh`) so the
package keeps working in minimal environments.
- **`warden audit-log` CLI** — `keygen`, `pubkey`, `list`, `show`, `verify`,
`seal`, `register-pubkey`, `replay`. `verify` walks the chain, recomputes
hashes, and validates signatures, exiting non-zero on tampering. `seal`
writes a signed manifest of the head hash on session close.
- **Per-decision sink-friendly record** — the audit log is written for every
decision (not just blocks), so an external verifier or SIEM consumer can
audit the full agent trajectory rather than only the denied actions.

### Security properties

- **Hash-chained**. Modifying any record breaks `record_hash` for that record
and `prev_hash` for the next; `warden audit-log verify` flags both.
- **Signed (opt-in)**. Generate a keypair with `warden audit-log keygen`; from
then on every record carries an Ed25519 signature over its canonical bytes,
and tampering invalidates the signature even if an attacker recomputes the
hash chain.
- **Privacy-preserving by default**. Sensitive event fields (`command`,
`path`, `url`, `prompt`, `content`, `response`) are stored as SHA-256
digests plus length, not plaintext. Set `audit.include_raw: true` under
`settings` in `policy.yaml` to retain raw text for orgs that need it.
- **Replay-ready**. Pinned `policy_hash` and `feed_hash` make decisions
reproducible: `warden audit-log replay` checks that all referenced policy
snapshots are present on disk.

### Changed

- `PolicyEngine` exposes `audit_settings` parsed from `settings.audit` in
`policy.yaml`, used by the dispatcher to decide whether to retain raw
evidence in audit records.
- `warden hook-dispatch` now writes one audit record per decision after
running the policy engine and before applying the block. Failures are
logged to stderr and never block the user's tool call.

## [1.2.0] — 2026-04-27

Tier 3 — Scoped Agent and Session-Based Learning. Adds per-session rule
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ flowchart TD
- 🛜 [Network Isolation](docs/network-isolation.md) covers egress allowlists, raw IP detection, and tunnel blocking
- 🔍 [Skill Scanner](docs/skill-scanner.md) covers MCP server and skill risk scanning across supported agents
- 🔐 [Sweep and Cloak](docs/sweep-and-cloak.md) covers secret prevention at tool boundaries and cleanup for leaked secrets
- 📜 [Audit Log](docs/audit-log.md) covers the hash-chained, Ed25519-signed decision log for governance and replay
- 🐳 [Docker and Containers](docs/docker.md) covers container hardening, prerequisites, and known limitations

---
Expand Down
146 changes: 146 additions & 0 deletions docs/audit-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Audit Log

Warden writes a tamper-evident, optionally signed record for every decision
(allow / observe / block) it makes. The log is what governance and audit
teams ask for when they want to know "what did the agent attempt, what was
blocked, and why — and can you prove it?"

## What gets recorded

One NDJSON record per decision, appended to
`.prismor-warden/audit/<session_id>.ndjson`:

```json
{
"v": 1,
"alg": "sha256",
"seq": 0,
"ts": "2026-05-03T19:03:24.139Z",
"session_id": "...",
"agent": "claude",
"mode": "enforce",
"workspace_id": "5a25e2b3711fcbf2",
"event": {
"type": "shell",
"agent_event": "PreToolUse",
"command_hash": "1de700c2...",
"command_len": 6
},
"decision": "allow",
"findings": [],
"policy_hash": "a0066d3f...",
"feed_hash": "fb010dd8...",
"agent_version": "warden 1.3.0",
"prev_hash": "GENESIS",
"record_hash": "9286523ecb239dc5...",
"sig": {
"alg": "ed25519",
"key_id": "0d835451d9e089ff",
"value": "<base64 signature>"
}
}
```

- **`prev_hash` / `record_hash`** — every record links to its predecessor.
Modifying any field changes `record_hash`; modifying `record_hash` breaks
`prev_hash` on the next record. `warden audit-log verify` walks the chain
and reports both kinds of break.
- **`policy_hash` / `feed_hash`** — fingerprint of the policy and threat feed
that were in effect when the decision was made. The first time we see a
hash, we copy the source files into `audit/policies/<hash>/` and
`audit/feed/<hash>.json`, so replay has the exact rule set the decision was
derived from.
- **`sig`** — present when an Ed25519 signing key is configured (see below).
Signs the canonical bytes of the record (excluding `record_hash` and `sig`
itself).

## Privacy: hashed evidence by default

Sensitive event fields — `command`, `path`, `url`, `prompt`, `content`,
`response`, and the `evidence` field of every finding — are stored as
SHA-256 digests plus length only. The raw text is **not** written to the
audit log. This makes the log safe to forward to a SIEM without leaking
secrets.

To retain raw text (for orgs that need full text in their audit trail), set
`audit.include_raw: true` under `settings` in `.prismor-warden/policy.yaml`:

```yaml
settings:
audit:
include_raw: true
```

## Enabling signatures

Hash chaining is on by default — no setup. To turn on Ed25519 signing:

```bash
warden audit-log keygen
```

This writes:

- `~/.prismor/keys/audit-signer.key` (mode 0600, parent dir 0700)
- `~/.prismor/keys/audit-signer.pub` (the public key to distribute)

From the next decision onward, every record carries a signature. The public
key fingerprint (`key_id`, first 16 hex chars of SHA-256 over the raw key
bytes) is written to each record so a verifier with multiple pubkeys can
pick the right one.

To use a centrally-managed key (for example a key issued from a KMS or
mounted from a secret manager in CI), set:

```bash
export WARDEN_AUDIT_SIGNING_KEY=/path/to/private.pem
export WARDEN_AUDIT_SIGNING_PUBKEY=/path/to/public.pem
```

If neither the env var nor the default path is set, records are still
hash-chained but unsigned.

## CLI

```bash
warden audit-log keygen [--out-dir DIR] [--force]
warden audit-log pubkey [--key PATH] # print PEM + key_id
warden audit-log list # all sessions in workspace
warden audit-log show <session_id> # human-readable trace
warden audit-log verify [--session-id ID] [--json] # exit 2 on tamper
warden audit-log seal <session_id> # write signed manifest
warden audit-log register-pubkey <pubkey.pem> # add a verifier key
warden audit-log replay <session_id> [--json] # check pinned policy presence
```

`verify` is the workhorse: for each record it recomputes the hash from
canonical bytes, checks `prev_hash` against the previous record, and (if
present) verifies the Ed25519 signature against the registered public key.
Any failure produces a structured report and a non-zero exit code.

## Verifying from outside the workspace

Any third party with the public key can verify the chain themselves:

```bash
# Copy the audit dir + the public key off the host
scp -r host:.prismor-warden/audit ./audit-dump/
scp host:~/.prismor/keys/audit-signer.pub ./

# Register the pubkey in the dump and verify
warden --workspace ./audit-dump audit-log register-pubkey ./audit-signer.pub
warden --workspace ./audit-dump audit-log verify
```

This is the answer to the question "are you generating a signed, replayable
log of what gets blocked and why?" — yes, and a verifier doesn't need to
trust the host to confirm it.

## Sealing on session close

`warden audit-log seal <session_id>` writes a manifest at
`.prismor-warden/audit/<session_id>.seal` containing the record count, the
head record hash, and (if signing is on) a signature over the manifest. The
seal is the single artifact a downstream system needs to keep — anyone
holding the seal and the audit file can later prove that no records were
appended, removed, or modified after the seal was written.
Loading