# Attack J Security Fix (MC #101149)

# Attack J Security Fix (MC #101149)

**MC:** #101149 (Attack J — evidence-ledger writer identity spoof)  
**Parent:** [Reality Anchor Doctrine Phase 2](https://docs.alai.no/books/system-architecture/page/reality-anchor-doctrine-v1-final)  
**Owner:** CodeCraft / Petter Graff  
**Date Shipped:** 2026-05-17  
**Proveo Verdict:** PARTIAL → PASS (after fix)

---

## The Vulnerability (Attack J)

The evidence-ledger writer identity could be spoofed via the `--actor` CLI parameter. The ledger write path used:

```javascript
const agentId = process.env.CLAUDE_AGENT_ID || actor || 'unknown';

```

This allowed an agent to bypass the Writer ≠ Witness gate by supplying:

```bash
mc.js ready 101149 --evidence /tmp/evidence-101149/ --actor "different-agent-id"

```

The ledger would record `writer_agent_id = "different-agent-id"`, then at `mc.js done` the same agent could close the task because the writer\_id check passed (different agent on paper, same agent in reality).

> **Reality Anchor Invariant:** "The agent that produces evidence cannot be the agent that closes the task."

This is the Writer = Witness antipattern — the bypass allowed a single agent to both produce and validate its own evidence.

---

## The Fix

Remove `|| actor` from the identity fallback chain. The agent identity MUST come from `CLAUDE_AGENT_ID` environment variable (set by the Claude Code runtime, not user-supplied).

### Changed Lines in mc.js

**Line 2843 (done gate — read ledger):**

```diff
- const currentAgentId = process.env.CLAUDE_AGENT_ID || actor || 'unknown';
+ const currentAgentId = process.env.CLAUDE_AGENT_ID || 'unknown';

```

**Line 3538 (ready command — write ledger for --evidence):**

```diff
- const agentId = process.env.CLAUDE_AGENT_ID || actor || 'unknown';
+ const agentId = process.env.CLAUDE_AGENT_ID || 'unknown';

```

**Line 3574 (deprecation log):**

```diff
- agent_id: process.env.CLAUDE_AGENT_ID || actor || 'unknown',
+ agent_id: process.env.CLAUDE_AGENT_ID || 'unknown',

```

**Line 3589 (ready command — write ledger for --probe-evidence):**

```diff
- const agentId = process.env.CLAUDE_AGENT_ID || actor || 'unknown';
+ const agentId = process.env.CLAUDE_AGENT_ID || 'unknown';

```

---

## Symmetric Enforcement

The fix ensures symmetric identity enforcement across both READ (line 2843) and WRITE (lines 3538, 3589) paths:

<table id="bkmrk-operation-line-ident"><thead><tr><th>Operation</th><th>Line</th><th>Identity Source</th></tr></thead><tbody><tr><td>Read ledger (done gate)</td><td>2843</td><td>`CLAUDE_AGENT_ID` only</td></tr><tr><td>Write ledger (ready --evidence)</td><td>3538</td><td>`CLAUDE_AGENT_ID` only</td></tr><tr><td>Write ledger (ready --probe-evidence)</td><td>3589</td><td>`CLAUDE_AGENT_ID` only</td></tr><tr><td>Deprecation log</td><td>3574</td><td>`CLAUDE_AGENT_ID` only</td></tr></tbody></table>

The `--actor` CLI parameter remains available for legacy MC operations (comments, audit log human-readable labels) but is NEVER used as a substitute for `CLAUDE_AGENT_ID` in the P2.2 writer≠closer gate.

---

## Why This Matters

Without this fix, the entire Reality Anchor Phase 2 evidence ledger was bypassable. An agent could:

1. Generate evidence file
2. Write to ledger with spoofed writer\_id via `--actor`
3. Close task with `mc.js done` (same agent, but ledger shows different writer)
4. Gate passes because `writer_agent_id != closer_agent_id`

This is a **privilege escalation via identity forgery**. The fix closes the gap by enforcing that only the runtime-provided `CLAUDE_AGENT_ID` is authoritative.

---

## Proveo Test Update

**Before fix:** Proveo verdict for MC #101149 = PARTIAL (Attack J bypass demonstrated)  
**After fix:** Proveo re-ran test → verdict updated to PASS

Test scenario:

1. Builder agent produces evidence for task #101149
2. Builder attempts `mc.js ready 101149 --evidence /tmp/evidence-101149/ --actor "fake-verifier-id"`
3. **Expected:** Ledger records writer\_agent\_id = builder's real CLAUDE\_AGENT\_ID (NOT "fake-verifier-id")
4. Builder attempts `mc.js done 101149`
5. **Expected:** Gate BLOCKS because writer\_agent\_id == closer\_agent\_id

**Result:** PASS — gate correctly blocked self-closure.

---

## Writer ≠ Witness Invariant (Now Enforced)

The invariant is now enforced symmetrically in both read and write paths:

> **Invariant:** The agent\_id that writes evidence to the ledger MUST differ from the agent\_id that calls `mc.js done`. Identity MUST be derived from `CLAUDE_AGENT_ID` environment variable, NOT from user-supplied `--actor` parameter.

---

## Audit Trail

All evidence ledger entries at `~/system/state/evidence-ledger.jsonl` now contain:

- `writer_agent_id` — from `CLAUDE_AGENT_ID` only
- `sha256` — content hash
- `task_id` — MC reference
- `timestamp` — write time
- `event_type` — "ready" or "done"

The gate at `mc.js done` verifies:

1. Ledger entry exists for task\_id
2. `writer_agent_id != closer_agent_id`
3. SHA-256 hash matches file content
4. Timestamp within task execution window

---

## Related

- **MC:** #101149 (Attack J fix)
- **Parent:** Reality Anchor Phase 2 (MC #100823–#100827)
- **Code:** `~/system/tools/mc.js` lines 2843, 3538, 3574, 3589
- **Proveo Test:** MC #101149 validation — writer≠witness gate attack (PASS after fix)
- **Doctrine:** [Reality Anchor v1 Final](https://docs.alai.no/books/system-architecture/page/reality-anchor-doctrine-v1-final)