# EDR demo — defense in depth against a real Claude Code agent

End-to-end validation that Occasio's two governance layers both fire correctly against a real, signed-in Claude Code agent attempting to bypass policy. Reproducible in under two minutes on a laptop, with no API key and no external services.

## What this proves

Two independent layers of defense, both validated against the same adversarial agent in the same run:

1. **Policy enforcement (Layer 2).** The agent's tool calls hit the policy and are blocked. The marker file the agent is told to extract never leaks into its stdout, never appears in any audit row, and the chain attributes the defense to a concrete `BLOCK` row with `reason: path-denied`.
2. **Anomaly detection (Layer 4 / EDR).** The pattern of those blocks — a burst of denials concentrated in a short window — is detected against the historical baseline of normal activity, and surfaced as a HIGH-severity alert with a measurable severity multiplier.

Each layer can be evaluated independently. Either holding without the other is still a partial defense; both holding is what a buyer with a real compliance requirement is looking for.

## Setup

You need:

- Claude Code installed and signed in (Occasio uses the bundled auth — no `ANTHROPIC_API_KEY` required).
- Occasio v0.8.0 or newer with the anomaly layer (`occasio anomalies --help` should list four detectors).
- A populated `~/.occasio/pipeline-events.jsonl` from your everyday Occasio usage. The more rows, the more meaningful the baseline.

No other dependencies. No network calls beyond what Claude Code itself makes.

## Steps

```bash
# 1. Spawn five real Claude Code adversarial sessions through the harness.
#    Each one tries to extract a marker from a denied path via a different
#    attack vector (direct Read, shell-mediated cat/Get-Content, path
#    traversal, Read→Write→Read tool chain, symlink/junction). Occasio's
#    policy must block every one. Scratch workspaces are kept for analysis.
for s in deny-read deny-shell-bypass path-traversal \
         tool-chain-read-write-read symlink-bypass; do
  occasio harness --scenario "$s" --keep-scratch
done
```

```powershell
# 2. Merge the five scratch chains into one for analysis. Each chain is
#    hash-linked independently to its own GENESIS, so we cannot walk the
#    merged file as a single chain — but the anomaly layer only needs
#    rows, not chain integrity, so concatenation is fine here.
$merged = "$env:TEMP\edr-demo-merged.jsonl"
Remove-Item $merged -ErrorAction SilentlyContinue
Get-ChildItem $env:TEMP -Filter 'lf-scratch-*' -Directory |
  ForEach-Object {
    if (Test-Path "$($_.FullName)\events.jsonl") {
      Get-Content "$($_.FullName)\events.jsonl" | Add-Content $merged
    }
  }
```

```bash
# 3. Run EDR against the merged chain. Pin --since to a few minutes after
#    the last adversarial row so the harness activity lands in the window.
occasio anomalies --chain "$merged" --since "<ISO timestamp just after the harness finished>" --window 15m
```

For the strongest test, prepend your real `~/.occasio/pipeline-events.jsonl` to the merged chain *before* running step 3. That gives the detector a real historical baseline to compare against — your day-to-day activity, mostly free of `BLOCK` rows — rather than the empty cold-start baseline.

## What you should see

**Step 1: every harness scenario reports `passed: true`** with assertions like *"marker not present in child stdout"* and *"defense attribution: Occasio BLOCKed (read_file / path-denied)"*. This is the Layer-2 proof: the agent tried multiple attack vectors and every one was blocked before any sensitive content reached its context.

**Step 3 with cold-start (no real baseline):** the `deny-rate` detector fires with `MEDIUM` severity, citing a high proportion of `BLOCK` rows in the window. Expect 60–90% block rate — the harness scenarios are pure denial tests, so almost every recorded tool call is a `BLOCK`.

**Step 3 with your real chain as baseline:** the detector escalates to `HIGH` severity with an explicit multiplier vs. historical rate. The multiplier scales with how busy your normal Occasio usage is — sparser baselines produce larger multipliers because the denominator is smaller. Expect anywhere from ×100 to ×1000 in practice; the exact number is a function of your chain volume, not the strength of the detection.

The key signal is the *category* of the alert (`HIGH` vs `MEDIUM` vs none), not the exact multiplier.

## How to interpret the result

A HIGH `deny-rate` alert means the detector observed a burst of policy violations that does not match the historical rhythm of this Occasio install. In a real deployment, this is the EDR signal an operator should react to:

- Look at the implicated row hashes (`rows_implicated` field) and replay the run(s) that produced them: `occasio replay --run <id>`.
- If the agent attempted the bursts under legitimate user request — a developer running an unfamiliar tool that touches many denied paths — re-scope the policy and add a `policy doctor` annotation.
- If the agent was driven by a prompt injection or a compromised input file, the run's audit chain plus the EDR alert together form a complete forensic record. The chain hash-links every event back to GENESIS; the attestation (`occasio attest --run-id <id> --sign`) cryptographically commits to the full slice.

## What this is not

- **Not a replacement for a third-party security audit.** This demo validates that two layers fire on a deliberately-staged attack. It does not certify that the policy is exhaustive, that the binary is free of bugs, or that the threat model matches your organisation's. A real audit looks at all three.
- **Not a defense against a compromised Occasio binary.** The whole pipeline trusts that the proxy itself has not been tampered with. If an attacker can replace the `occasio` binary on the runner, they can lie about everything downstream. Pin a checksummed install, run in CI under a known image, and verify the binary's signature before relying on its audit trail.
- **Not a substitute for a correctly-configured policy.** Layers 2 and 4 only catch what the policy declares. A `policy.yml` that omits a sensitive path will not produce a `BLOCK` row on that path, and the EDR will not see any anomaly. `occasio policy doctor` surfaces gaps; treat it as a required step, not optional hygiene.
