# Lab Specification — SDD-B05: IronCurtain Offensive Analysis

**Course**: Course 2B — Securing & Attacking Harnesses and LLMs
**Module**: SDD-B05 — IronCurtain Offensive Analysis
**Duration**: 45–60 minutes
**Environment**: Python 3.10+. No GPU, no network, no external API keys. The IronCurtain compilation pipeline, the V8 isolate boundary, the human approver, and the escalation channel are all SIMULATED so the lab runs deterministically offline. A text editor and `python3 -m json.tool` for validation.

---

## Learning objectives

By the end of this lab you will have:

1. **Built a compilation-fidelity fuzz harness** — generate N fuzzed constitution variants (introducing ambiguity, scenario coverage gaps, resolved-list poisoning), compile each through a simulated IronCurtain pipeline (with a mock verify-and-repair stage), and measure the compilation-fidelity drift rate.
2. **Constructed an escalation-fatigue simulator** — generate a stream of escalating tool calls (varying ambiguity, volume, timing), simulate a human approver with a fatigue model, and measure the approval-rate delta rested versus fatigued.
3. **Audited the V8-isolate dependency** — check a deployed version string against a known-CVE list and frame the finding as an escape-path analysis.
4. **Assembled the four-layer defense-in-depth prescription** — map each of IronCurtain's residuals to the layer that addresses it.
5. **Measured each break** — as a drift rate, a fatigue delta, or a CVE audit — never as an assertion.

This lab is the offensive counterpart to the IronCurtain defense (Course 1 DD-20) and the completion of the SDD-B04 prescription. You are attacking the strongest defense and proving why layers are necessary.

---

## Phase 0 — Setup (3 min)

```bash
mkdir sdd-b05-lab && cd sdd-b05-lab
python3 --version  # 3.10+ required
```

No venv, no dependencies, no GPU. The entire lab is self-contained Python with type hints.

---

## Phase 1 — The simulated IronCurtain compilation pipeline (12 min)

Build the build-time compilation pipeline that turns a constitution into deterministic rules — the surface Break 1 attacks.

### 1.1 The data model

Create `ironcurtain.py`:

```python
from dataclasses import dataclass, field
from typing import Literal, Optional
import json

Decision = Literal["ALLOW", "DENY", "ESCALATE"]

@dataclass
class CompiledRule:
    tool: str
    decision: Decision
    reason: str
    conditions: dict  # e.g. {"url_pattern": "...", "arg_class": "..."}

@dataclass
class CompiledPolicy:
    rules: list[CompiledRule]
    content_hash: str
    verified: bool  # did verify-and-repair pass?

@dataclass
class Constitution:
    text: str
    resolved_lists: dict[str, list[str]]  # e.g. {"trusted_endpoints": [...]}
    scenarios: list[str]  # test cases the policy must handle

@dataclass
class CompilationResult:
    policy: CompiledPolicy
    miscompiled: bool  # diagnostic: did a fidelity error occur?
    verify_caught: bool  # did verify-and-repair catch the miscompilation?
```

### 1.2 The simulated compilation stages

```python
def compile_stage(constitution: Constitution) -> list[CompiledRule]:
    """Mock the Compile LLM stage: translate English to if/then rules.

    A real IronCurtain uses an LLM here; the simulation models the
    probabilistic fidelity gap: ambiguous constitutions, missing scenarios,
    and poisoned lists can produce a PERMISSIVE rule that the constitution
    intends to deny.
    """
    rules: list[CompiledRule] = []
    # ... classify tool args, translate clauses, resolve lists ...
    return rules

def verify_and_repair(policy: CompiledPolicy, scenarios: list[str]) -> CompiledPolicy:
    """Mock the Verify & Repair LLM stage: test the compiled policy against
    scenarios, repair failures up to 2 rounds. Build fails if unverified.

    A real IronCurtain uses an LLM here; the simulation models the residual:
    a miscompilation the verifying LLM AGREES with passes.
    """
    # ... run scenarios, repair, mark verified ...
    return policy
```

### 1.3 Your task — implement the fidelity gap

The simulation must model three things:
- **Constitution ambiguity**: if the constitution text contains ambiguous phrasing (e.g. "internal channels", "routine updates" without definition), `compile_stage` may resolve it permissively — producing a rule that `ALLOW`s an action the constitution intended to deny. Model this as a permissiveness check on the text.
- **Scenario coverage gaps**: if the scenario set does not include a test case for the target action, `verify_and_repair` does not test that path, and a permissive rule for it passes. Model this as a coverage check.
- **Resolved-list poisoning**: if a resolved list (e.g. `trusted_endpoints`) contains an attacker-controlled value, the compiled rule references it. Model this as a list-integrity check.

Set `miscompiled=True` when a fidelity error occurs, and `verify_caught=True` when verify-and-repair catches it. The residual (the Break 1 target) is `miscompiled and not verify_caught`.

The point: the compilation is probabilistic (simulated), verify-and-repair is a second probabilistic check (simulated), and two probabilistic systems agreeing is not a deterministic guarantee.

---

## Phase 2 — Break 1: the compilation-fidelity fuzz harness (12 min)

### 2.1 The fuzz harness

Write `fuzz_compilation.py`:

```python
import random
from ironcurtain import Constitution, compile_full_pipeline

def fuzz_constitution(seed: int) -> Constitution:
    """Generate a fuzzed constitution variant. Introduce one of:
    - ambiguity (loosely-worded clause)
    - scenario coverage gap (remove the test for the target action)
    - resolved-list poisoning (add attacker domain to trusted_endpoints)
    """
    rng = random.Random(seed)
    # ... generate the variant based on rng.choice of the three vectors ...
    return constitution

def run_fuzz_harness(n: int, target_action: str) -> dict:
    """Run N fuzzed constitutions through the compilation pipeline.
    Measure the compilation-fidelity drift rate: the % that produce a
    compiled policy PERMITTING the target_action that the un-fuzzed
    constitution denies.
    """
    results = []
    for seed in range(n):
        constitution = fuzz_constitution(seed)
        result = compile_full_pipeline(constitution)
        permits_target = any(
            r.tool == target_action and r.decision == "ALLOW"
            for r in result.policy.rules
        )
        results.append({
            "seed": seed,
            "miscompiled": result.miscompiled,
            "verify_caught": result.verify_caught,
            "permits_target": permits_target,
        })
    drift_count = sum(1 for r in results if r["permits_target"] and r["miscompiled"] and not r["verify_caught"])
    return {
        "n": n,
        "drift_count": drift_count,
        "drift_rate": drift_count / n,
        "results": results,
    }
```

### 2.2 Run it

Run the harness with N=500 (or N=200 for speed). Print the drift rate.

### 2.3 The point

The drift rate is the measured effectiveness claim for Break 1 — the build-time analogue of the InjecAgent runtime delta. A low rate (e.g. 2%) means verify-and-repair catches most; a high rate means it does not. Record: "Break 1 demonstrates the compilation-fidelity residual; the drift rate is the honest measurement, not an assertion."

### 2.4 The prescription check

Implement the human review gate: for each drifted (miscompiled, not-caught) constitution, check whether a simple human-review heuristic (diff the compiled rule against the un-fuzzed baseline; flag any rule that newly permits a previously-denied action) would catch it. Measure the residual drift rate WITH the human review gate. The delta (drift rate before vs after the gate) is the effectiveness of Layer 1.

---

## Phase 3 — Break 3: the escalation-fatigue simulator (10 min)

### 3.1 The data model

Add to `ironcurtain.py`:

```python
@dataclass
class EscalationEvent:
    tool: str
    ambiguity_score: float  # 0.0 (clear) to 1.0 (maximally ambiguous)
    is_attacker_intended: bool  # is this a call the attacker wants approved?
    similar_to_prior_approved: bool  # contextual fatigue trigger

@dataclass
class HumanApprover:
    fatigue: float = 0.0  # 0.0 (rested) to 1.0 (max fatigued)
    base_approve_rate: float = 0.04  # rested approval of attacker-intended
    max_approve_rate: float = 0.31  # fully fatigued

def approve_decision(event: EscalationEvent, approver: HumanApprover) -> bool:
    """Model the human's allow/deny decision with a fatigue model.
    Approval probability rises with fatigue and with similarity to prior calls.
    """
    # ... model the fatigue curve ...
    pass
```

### 3.2 The simulator

Write `escalation_fatigue.py`:

```python
def simulate_escalation_stream(
    events: list[EscalationEvent],
    volume_per_hour: float,
    approver: HumanApprover,
) -> dict:
    """Simulate a stream of escalations. Fatigue accumulates with volume.
    Measure the approval rate of attacker-intended actions."""
    # ... accumulate fatigue, make approve_decision calls ...
    return {
        "total_events": len(events),
        "attacker_intended_count": sum(1 for e in events if e.is_attacker_intended),
        "attacker_intended_approved": ...,  # count
        "approval_rate": ...,
        "final_fatigue": approver.fatigue,
    }
```

### 3.3 Run the rested vs fatigued comparison

Run the simulator under two conditions:
- **Rested**: low volume (5 escalations/hour), approver starts at fatigue=0.0.
- **Fatigued**: high volume (50 escalations/hour), approver fatigue accumulates.

Record the approval-rate delta. This is the honest measurement of the escalation-fatigue surface.

### 3.4 The rate-limiting control

Add a rate-limit to the simulator: cap escalations at N/hour; above the cap, the deterministic engine denies-by-default. Re-run the fatigued condition with the rate-limit. Measure the residual approval rate. The delta (fatigued-uncontrolled vs fatigued-rate-limited) is the effectiveness of Layer 3.

---

## Phase 4 — Break 2: the V8-isolate CVE audit (8 min)

This phase is an audit, not a simulation.

### 4.1 The CVE list

Create `v8_cve_audit.py` with a (simulated, illustrative) known-CVE database:

```python
@dataclass
class IsolateCVE:
    cve_id: str
    affected_versions: str  # e.g. "isolated-vm < 5.0.0"
    escape_class: str  # "type_confusion", "bounds_error", "jit_bug", "gc_edge"
    severity: str  # "critical", "high"

KNOWN_CVES: list[IsolateCVE] = [
    # Simulated entries — illustrative of the escape-CVE classes.
    # In a real engagement, source these from the V8 and isolated-vm
    # security advisories at the deployed versions.
]

def audit_isolate_version(deployed_version: str) -> list[IsolateCVE]:
    """Return the list of known escape CVEs affecting the deployed version."""
    return [cve for cve in KNOWN_CVES if version_matches(deployed_version, cve.affected_versions)]
```

### 4.2 Run the audit

Audit three deployed versions (current, one-version-behind, several-versions-behind). For each, report the known escape CVEs and the escape class.

### 4.3 The point

Frame the finding as an escape-path analysis: the credential quarantine holds only as long as the isolate does. A known escape CVE in the deployed version means the quarantine is one exploit from defeat. The measurement is the audit result, not a benchmark number. The prescription is sandbox defense-in-depth (Layer 2): isolate → seccomp/capability → egress controls, so an escape defeats three layers, not one.

---

## Phase 5 — The four-layer prescription (5 min)

Write `prescription.md` mapping each of IronCurtain's residuals to the layer that addresses it:

```markdown
## IronCurtain residuals and the layered prescription

### Residual 1 — Compilation fidelity (Break 1)
- Drift rate measured: [X]% over N=500 fuzzed constitutions.
- With human review gate (Layer 1): drift rate drops to [Y]%.
- Layer: HUMAN REVIEW GATE — review compiled-policy.json before deploy,
  diff vs last-reviewed. Catches the residual verify-and-repair agreed with.

### Residual 2 — V8 isolate boundary (Break 2)
- CVE audit: deployed version [V] has [N] known escape CVEs.
- Layer: SANDBOX DEFENSE-IN-DEPTH — isolate (inner) → seccomp/capability
  (middle) → egress controls (outer). Escape defeats 3 layers, not 1.

### Residual 3 — Escalation fatigue (Break 3)
- Fatigue delta: approval rate [A]% rested → [B]% fatigued.
- With rate-limiting (Layer 3): fatigued approval drops to [C]%.
- Layer: ESCALATION RATE-LIMITING — cap/hour + batching + two-person rule.

### Layer 4 — Probabilistic second
- An LLM-as-judge for ESCALATED cases only, positioned AFTER the deterministic
  layer. Handles ambiguous cases the deterministic rules cannot resolve.
  Judge injectability = residual on the escalated subset, not the primary surface.
```

The point: the deterministic runtime is the strongest single layer — keep it. The prescription layers around its residuals. No single layer is load-bearing.

---

## Phase 6 — Stretch: the full defense-in-depth delta (optional, 5 min)

If time remains, combine all four layers and measure the aggregate residual:
- Compilation fidelity residual (after Layer 1): [Y]%
- Escalation fatigue residual (after Layer 3): [C]%
- Isolate boundary: contained by Layer 2 (3-layer sandbox)

Frame the aggregate as the defense-in-depth that SDD-B03 measures reaching single-digit injection rates. The point: IronCurtain alone has residuals; the layered prescription closes more surface than IronCurtain alone.

---

## Deliverables

- `ironcurtain.py` — the simulated compilation pipeline, fidelity-gap model, escalation approver (Phases 1, 3)
- `fuzz_compilation.py` — the compilation-fidelity fuzz harness + drift rate + human-review-gate check (Phase 2)
- `escalation_fatigue.py` — the escalation-fatigue simulator + rested/fatigued delta + rate-limiting control (Phase 3)
- `v8_cve_audit.py` — the isolate dependency-CVE audit (Phase 4)
- `prescription.md` — the four-layer prescription mapped to residuals (Phase 5)
- (optional) `defense_in_depth_delta.md` — the aggregate layered residual (Phase 6)

## Success criteria

- [ ] The compilation pipeline models the three fidelity vectors (ambiguity, coverage gaps, list poisoning) and verify-and-repair as a second probabilistic check.
- [ ] The fuzz harness reports a compilation-fidelity drift rate over N≥200 fuzzed constitutions, and the human-review-gate check shows a reduced residual.
- [ ] The escalation-fatigue simulator reports an approval-rate delta rested vs fatigued, and the rate-limiting control shows a reduced fatigued approval rate.
- [ ] The V8-isolate CVE audit reports known escape CVEs for the deployed versions, framed as an escape-path analysis.
- [ ] `prescription.md` correctly maps each residual to its layer and states that the deterministic runtime is the strongest single layer to keep.
- [ ] Every measurement is a drift rate, a fatigue delta, or a CVE audit — no assertion-only claims.
