Warmup Guide — From "I Have a Technique" to "I Have Authorization"

Zero-to-principal primer for Phase 00. It builds, from first principles, the discipline that wraps every offensive technique you will ever run: the authorization boundary, the Rules of Engagement (scope, methods, time, rate, data, stop conditions), deny-by-default authorization, OPSEC (the noise and indicators an action emits), and deconfliction with the client's SOC. Assumes only that you can program. By the end you should be able to scope an engagement from a blank page, decide ALLOW/DENY for any proposed action and explain why, score an action's OPSEC risk and name the telemetry it leaves, and answer the SOC's 3 a.m. "was that you?" without ending the engagement.

Table of Contents


Chapter 1: The Authorization Boundary — Knowledge Is Not Permission

Zero background. A red team consultant is a person a company pays to attack it. That sentence is strange until you internalize the single idea this whole phase exists to make reflexive: the same action — port-scanning a host, phishing an employee, dumping credentials — is either professional security work or a felony, and the only thing that decides which is authorization. Not skill. Not intent in your head. A signed contract, a named owner, and a written scope.

What it is. The authorization boundary is the line between actions you may take and actions you may not, defined by who owns the target and what they have agreed in writing to let you do. For this work it has four load-bearing parts you must be able to state out loud before any action:

OWNER        — who owns this asset, and who signed authority to let me test it?
SCOPE        — exactly which assets, methods, and time are permitted?
STOP         — what halts the engagement immediately, and who can pull that lever?
DECONFLICT   — who does the SOC call to ask "is this you?" — and how do I answer?

If you cannot name all four, you do not take the action. Full stop.

Why it exists. Computer-misuse law (the U.S. CFAA, the UK Computer Misuse Act, and equivalents in the Gulf states where this role operates) criminalizes unauthorized access — and "I was just testing," "it was reachable," or "I didn't change anything" are not defenses. The entire value a red team delivers depends on the engagement being lawful and the client being able to learn from it; an unauthorized action destroys both at once. The boundary is not bureaucracy bolted onto the fun part — it is the profession.

Under the hood — the distinction that matters. Separate three things people constantly conflate:

  • Knowledge — understanding how a technique works. Legal, and the entire point of this curriculum.
  • Capability — possessing the means to run it. Also legal to build in an owned lab.
  • Authorization — being permitted to run it against a specific target. This is the boundary.

A red teamer and a criminal can have identical knowledge and capability. What differs is the third column. This is why this repo teaches the full adversary lifecycle but ships analyzers, planners, and guards over synthetic data — knowledge and (safe) capability, never an unauthorized action.

Telemetry it emits. The authorization boundary is a human/legal control, but it has an artifact: the signed authorization letter (the "get-out-of-jail" letter) and the ROE, kept reachable for the whole engagement. Its absence is the telemetry — an action with no traceable authorization is the thing an audit, a client, or a court will find.

How a defender deconflicts it. From the blue team's side, the boundary is what lets them treat a detected intrusion as possibly authorized. When the SOC sees suspicious activity, the first question in a mature shop is "is there an active engagement, and is this in its scope and window?" That question only has an answer if you wrote the boundary down and gave them the deconfliction contact.

Engagement significance. In Operation Cedar Lattice this is Phase 00's entire job: before you touch Meridian Freight International, you and Meridian sign the ROE, fix the scope, agree the stop conditions, and exchange deconfliction contacts. Every later phase inherits this boundary — the recon in Phase 03, the AD attack in Phase 05, the C2 in Phase 08 are all only lawful because of what you nailed down here.

Common misconceptions.

  • "It's only illegal if I cause damage." No — unauthorized access is the offense; damage is an aggravating factor, not the threshold.
  • "The system is on the public internet, so it's fair game." Reachability is never authorization.
  • "I have a verbal OK from someone on the team." The signatory must have authority to grant access, and it must be in writing. Verbal scope is no scope.

Zero background. The Rules of Engagement (ROE) is the document that turns "we hired a red team" into a precise, enforceable boundary. It is the contract the operator obeys and the client relies on. The track ships a template — templates/rules-of-engagement.md — and the discipline of this phase is filling every field, because the governing rule is: ambiguity defaults to out-of-scope.

What it is. A complete ROE answers seven questions:

SectionThe question it answers
AuthorizationWho owns this, who signed, and is the get-out-of-jail letter on file?
ScopeWhich assets are in scope, which are explicitly out, and what is "success"?
MethodsWhich techniques are permitted, and which are forbidden?
Time & RateWhen may I act (window, hours) and how fast (rate, concurrency)?
Data handlingWhere is evidence stored, and what about real/personal data?
Deconfliction & safetyWho is the 24/7 contact, what are the stop conditions, how do I emergency-stop?
ReportingWhat deliverables, by when?

Why it exists. Three failure modes it prevents, each a real incident pattern:

  1. The scope-creep crime — an operator follows an attack path off the agreed assets onto a third party's infrastructure. The ROE's explicit out-of-scope list and deny-by-default scope stop this.
  2. The business-impact incident — testing during month-end financial close knocks over an ERP, or an aggressive scan saturates a link. The time window, permitted-hours, and rate limits prevent it.
  3. The false-alarm fire drill — the SOC mobilizes a real incident response over the red team's own activity. The deconfliction contact and procedure prevent it.

Under the hood — why "ambiguity defaults to out-of-scope." A natural-language scope is full of gaps: a /16 that contains a subnet nobody meant to include; "the corp domain" that someone reads as covering a sibling brand; "during business hours" with no timezone. The only safe interpretation rule is the deny-by-default one: if it is not explicitly, unambiguously in scope, it is out. The ROE is written so that every gap resolves to don't.

Telemetry it emits. The ROE itself is the primary record; combined with the authorization letter and the evidence manifest it forms the paper trail an engagement is judged by. A mature engagement can show, for any action, the ROE clause that permitted it.

How a defender deconflicts it. The client's trusted agent (the small set of people who know the test is happening) holds the ROE; when the SOC escalates, the trusted agent checks the action against the ROE's scope, methods, and window to confirm "yes, that is authorized red team activity."

Engagement significance. For Cedar Lattice, the ROE names Meridian's in-scope corp.* domain and 10.20.0.0/16, carves out payments.* and the 10.20.99.0/24 PCI subnet as out-of-scope, permits recon/exploitation/lateral-movement, forbids DoS and touching real customer data, sets a one-week window with a rate cap, and names the SOC deconfliction contact. Lab 01 is this ROE made executable.

Common misconceptions.

  • "The ROE is the sales team's problem." The operator lives or dies by it; read and challenge every ambiguous clause before you sign.
  • "More scope is better." No — tighter, clearer scope protects you. Vague breadth is liability.
  • "The forbidden list is obvious." Write it down anyway: DoS, destructive actions, persistence past the window, real-data access, and social-engineering of named individuals are each a clause.

Chapter 3: Scope — What "In Scope" Actually Means

Zero background. Scope is the set of assets you are permitted to act against. It sounds simple — "these IPs and this domain" — but the precise mechanics of matching a target against a scope are where engagements go wrong. This chapter is the mechanism behind in_scope() in Lab 01.

What it is. A scope is three kinds of allowlist plus three matching denylists:

  • CIDRs — network ranges, e.g. 10.20.0.0/16. A target IP is in scope if it is a member of an in-scope CIDR and not a member of an out-of-scope CIDR.
  • Domains — e.g. corp.meridian-freight.test. A hostname is in scope if it matches an in-scope domain by exact-or-subdomain and does not match an out-of-scope domain.
  • Accounts — emulation identities, e.g. svc-emul-01, matched by exact id.

Why exact-or-subdomain, not substring. Here is the single most important mechanism in Lab 01. The naive scope check is a substring test:

if scope_domain in target:   # WRONG — catastrophically permissive
    ...

This passes evil-meridian-freight.test for an in-scope meridian-freight.test, and worse, an attacker (or your own typo) can craft corp.meridian-freight.test.evil.example to look in-scope. The correct rule is:

target matches d  iff  target == d  OR  target endswith "." + d

So host1.corp.meridian-freight.test matches corp.meridian-freight.test (it is a subdomain), but evil-meridian-freight.test does not (it does not end with .corp.meridian-freight.test, and is a different registrable name entirely). This is the same logic browsers use for cookie domains and that TLS uses for wildcard certs — and getting it wrong is the same class of bug as a same-site cookie leak.

Under the hood — CIDR membership is real math, not strings. Do not compare IP strings. Use the ipaddress stdlib:

import ipaddress
ip = ipaddress.ip_address("10.20.99.10")
ip in ipaddress.ip_network("10.20.0.0/16")    # True
ip in ipaddress.ip_network("10.20.99.0/24")   # True  -> out-of-scope wins

10.20.99.10 is inside the in-scope /16 and inside the out-of-scope /24. Because out-of-scope overrides in-scope (Chapter 4), the target is denied — exactly the protected-subnet carve-out a real ROE has.

Telemetry it emits. A scope decision is logged with its reasons (Lab 01 returns a reason trail). That log, paired with the deconfliction log (Chapter 9), lets anyone reconstruct what was authorized.

How a defender deconflicts it. When the SOC asks "did the red team touch host X?", the answer is a scope lookup plus a deconfliction-log lookup. If host X was out of scope, and the team logged no action there, the SOC knows the activity is not the red team — a real finding.

Engagement significance. The scope is the spine of Lab 01 and of every later phase's target selection. Carry the Cedar Lattice scope object forward unchanged.

Common misconceptions.

  • "The /16 covers everything inside it." Not if a sub-range is carved out — out-of-scope overrides.
  • "Subdomains of an in-scope domain are obviously in scope." Only if your ROE says so and they are not separately carved out; encode it explicitly.
  • "I can match domains with endswith alone." endswith("corp.com") matches evilcorp.com; you need the leading dot: endswith(".corp.com") plus the exact-equality case.

Chapter 4: Deny-by-Default — Why Authorization Must Fail Closed

Zero background. A guard can default two ways. Default-allow (denylist) permits everything except what is on a blocklist. Default-deny (allowlist) forbids everything except what is on an allowlist. This chapter argues — and Lab 01 enforces — that an authorization guard must be deny-by-default.

What it is. Deny-by-default means: an action is allowed only if it affirmatively matches an in-scope entry and passes every gate (scope, method, time, rate, no stop). Anything unrecognized, ambiguous, or merely "not obviously forbidden" is denied. The decision fails closed.

Why it exists. Consider the alternative. A denylist guard says "deny actions on the out-of-scope list, allow the rest." The moment the ROE author forgets to list a sensitive host — which they will, because you cannot enumerate every host you don't know about — that host is silently allowed. Deny-by-default inverts the failure: a forgotten entry means a false denial (annoying, you ask for clarification) instead of a false authorization (a crime). You want your mistakes to fail in the safe direction.

This is the same principle as a firewall's default-drop egress policy, an IAM explicit-deny, and a WAF positive-security model. It is the single most important design rule in all of security, and Phase 00 makes you live it before you attack anything.

Under the hood — the gate structure. Lab 01's check_action is a conjunction of gates that collects every failure rather than short-circuiting:

ALLOW  iff  NOT stop_engaged
       AND  in_scope(target)              # explicit in-scope, out-of-scope overrides
       AND  method_allowed(method)        # allowlist
       AND  within_window(timestamp)      # [start, end)
       AND  rate_ok(timestamp, history)   # count in rolling window <= limit
otherwise DENY with the list of every failing reason

Two design choices matter. First, out-of-scope overrides in-scope inside in_scope() — the denylist is consulted before the allowlist, so a carve-out always wins. Second, the guard does not short-circuit: it reports all failing gates, because a deconfliction report and an audit need to see every reason an action was refused, not just the first.

Telemetry it emits. Every decision yields {decision, reasons}. The reason trail is the audit artifact — it is what you attach to the engagement log and hand to the trusted agent.

How a defender deconflicts it. Deny-by-default is also the control you will recommend to the client: the engagement that succeeds by bypassing a default-allow egress rule writes itself into the report as "implement deny-by-default egress." The pattern you enforce on yourself is the pattern you sell.

Engagement significance. Lab 01 is a deny-by-default guard end to end. Internalize it: by the time you reach cloud IAM (Phase 09) you will read Deny overriding Allow as the same idea.

Common misconceptions.

  • "Default-allow is fine if my denylist is good." Your denylist is never complete; you cannot list the unknown.
  • "Fail-closed just means more friction." It means the friction lands on safe mistakes, not dangerous ones — which is the entire point.
  • "Short-circuit on the first failure for speed." For an authorization guard, completeness of reasons beats microseconds; collect them all.

Chapter 5: Time Windows and Rate Limits — The Other Two Gates

Zero background. Scope answers where; the window and the rate answer when and how fast. Both are gates in their own right — an in-scope, permitted action run at the wrong time or too aggressively is still a violation and still a business risk.

What it is.

  • A time window is a [start, end) interval: a start (inclusive) and an end (exclusive). The half-open convention matters — an action at exactly end is outside the window. (This is the same convention as a Python range or a half-open interval in math; using it avoids the off-by-one where two adjacent windows both claim the boundary instant.)
  • A rate limit caps how many actions may occur within a rolling time window, e.g. "no more than 3 actions per 60 seconds." It is the engagement-OPSEC and business-safety throttle: it keeps you from hammering a service into an outage and from generating a wall of alerts.

Why it exists. Two distinct risks. The window prevents out-of-hours business impact (no testing during the change freeze, the financial close, or peak operational hours) and bounds the engagement legally in time — your authorization is not open-ended. The rate limit prevents denial-of-service by accident (a scan that saturates a link is a DoS even if you didn't mean it) and keeps your activity below the threshold where it becomes operationally disruptive.

Under the hood — the rolling-window rate check. Lab 01 counts prior actions in the half-open interval (t - W, t] from a supplied history and compares count + 1 (including the new action) to the limit:

window = (timestamp - rate_window_secs, timestamp]
count  = number of history actions with ts in that window
ALLOW  iff  count + 1 <= rate_limit

Because the history and timestamp are supplied by the caller, the check reads no clock and is perfectly reproducible — the same inputs always give the same decision. (A production guard would source history from a persisted action log; here it is a parameter so tests are deterministic.)

Telemetry it emits. Rate-limited actions appear as gaps in your own action log; from the defender's side, an engagement that respects a rate limit produces a characteristically paced signal, which is itself a deconfliction aid ("the spike at 02:14 was 200 req/s — not the red team, who are capped at 3/min").

How a defender deconflicts it. The window is the SOC's first filter: an alert outside the agreed window is, by default, not the red team and should be treated as a real incident. This is why the window must be exact and timezone-pinned.

Engagement significance. In Cedar Lattice the window is one week with a per-minute rate cap; acting at exactly the window-end instant is denied, and a 4th action inside a minute is denied. Both are tested in Lab 01.

Common misconceptions.

  • "The window is closed at both ends." It is half-open; end is excluded by convention to avoid boundary ambiguity. Be explicit either way and test the boundary.
  • "Rate limits are just politeness." An uncapped scan is an accidental DoS — a forbidden method by another name.
  • "Timezone is obvious." It is the single most common scope-window bug. Pin it in the ROE.

Chapter 6: Stop Conditions and the Emergency Stop

Zero background. A stop condition is anything that must halt the engagement immediately. The emergency stop is the procedure that does it. Together they are the engagement's circuit breaker — the acknowledgment that even a well-scoped test can go wrong and must be haltable in seconds.

What it is. Stop conditions are written into the ROE and typically include: a production outage or degradation, the discovery of a real (non-red-team) intrusion, a safety risk to people, an inadvertent reach into out-of-scope or real customer data, or a client request to halt. When any fires, all activity stops until the trusted agent and the client agree to resume.

Why it exists. Two reasons. First, harm limitation: if your activity is causing a real business problem, no objective is worth continuing. Second, incident clarity: if a real attacker is in the environment, the red team's noise makes the defender's job impossible — you stop so the real incident can be handled cleanly, and so your activity is never confused with the adversary's.

Under the hood — the latch. In Lab 01 the stop condition is a stop_engaged latch on the ROE. When set, check_action denies every action unconditionally, regardless of scope, method, window, or rate — the stop gate is checked first and dominates. This models the operating reality: when the stop is pulled, nothing is authorized, no exceptions, until it is explicitly cleared.

if stop_engaged:  every action -> DENY ("stop-condition-engaged")

Telemetry it emits. The emergency stop is logged with its trigger, time, and who pulled it — it is a first-class event in the engagement record, because "we stopped at 14:32 when the ERP degraded" is exactly what the after-action and the client trust depend on.

How a defender deconflicts it. The stop condition is bidirectional: the client can pull it too. The deconfliction contact is also the emergency-stop channel — the SOC calls, says "we have a real incident, stand down," and the operator latches the stop. The red team that cannot be reached to stop is a red team that will not be hired again.

Engagement significance. For Cedar Lattice the stop conditions are: any Meridian production outage, any sign of a real intruder, any reach toward the PCI subnet or real customer data, and any client stand-down. Lab 01's stop latch enforces the first principle: a stopped engagement authorizes nothing.

Common misconceptions.

  • "Stopping means I failed." Stopping correctly is a mark of a senior operator; the failure is not stopping.
  • "I'll finish this one action first." When the stop is pulled, you stop now — the latch denies everything precisely so there is no "one more."
  • "Only the client can stop." Either side can; you must monitor your own activity for stop triggers too.

Chapter 7: OPSEC — How Loud Is This, and Who Hears It?

Zero background. OPSEC (operational security) is the discipline of controlling the observable signal your activity produces. For a red teamer it answers: how loud is this action, how unusual does it look, how much does it touch, and how hard is it to undo? Every action sits somewhere on those axes, and the senior skill is choosing where — not stumbling there.

What it is. Lab 02 models OPSEC risk as a transparent weighted sum over five attributes:

AttributeMeaningWhy it raises risk
noisehow much telemetry the action generatesmore logs/alerts to trip a detection
noveltyhow anomalous it looks vs. a baselineanomaly is detection bait
blast radiushow many hosts/identities/services it toucheswider footprint, more chances to be seen, more to clean up
time of daybusiness hours vs. off-hoursoff-hours activity stands out against a quiet baseline
irreversibilityhow hard it is to undoa stop-condition multiplier; the harm if it goes wrong
score = 3*noise + 2*novelty + 3*blast_radius + 1*time_of_day + 2*irreversibility
band  = low (<4) | medium (<9) | high (<15) | critical (>=15)

Why it exists. OPSEC is what makes an emulation realistic (a real adversary minimizes detection) and what keeps the engagement controlled (you decide what to trip, deliberately, so the blue team's response is a measured outcome and not chaos). The weighted score turns a gut feeling ("this is loud") into a number you can compare, threshold, and defend in the report.

Under the hood — why a transparent linear model, not a black box. The weights are exposed as a module constant precisely so a reviewer (and a test) can see exactly what drives a decision. A calibrated ML model might score more accurately, but a red team decision must be explainable: "this action scored critical because blast radius and noise dominate" is something you can defend to a client and tune to their detection maturity. The model is a defensible default, not a claim of precision — and the README says so.

Telemetry it emits. This is the meta-point of the whole chapter: a high-OPSEC-risk action is, by definition, one that emits a lot of telemetry. Chapter 8 enumerates exactly what. The score and the indicators are two views of the same thing — what will the defender see?

How a defender deconflicts it. The OPSEC score directly drives the deconfliction decision (Chapter 9): above a threshold, the action is loud enough that the SOC must be warned in advance or they will (correctly) treat it as an incident. Quiet recon does not need a phone call; a noisy lateral move to a domain controller does.

Engagement significance. Every Cedar Lattice action gets an OPSEC assessment before it runs. Lab 02 is that assessment: score, band, indicators, deconfliction decision — the operator's pre-flight check.

Common misconceptions.

  • "OPSEC means never getting caught." No — for a red team it means controlling what gets caught, on purpose, to produce a measured detection outcome.
  • "Off-hours is stealthier." Often the opposite — your activity stands alone against a quiet baseline. Blending into business-hours noise can be quieter.
  • "A black-box score is more rigorous." For a defensible decision, transparency beats accuracy.

Chapter 8: Indicators — The Telemetry Every Action Emits

Zero background. An indicator is an observable trace an action leaves behind. Every offensive action emits indicators on one or more planes: the host (process, file, registry, memory events), the network (connections, protocols, traffic shape), and the identity plane (authentication, ticket requests, sign-in anomalies). Naming them is the act that converts offensive knowledge into a detection-paired asset.

What it is. Lab 02 carries an INDICATOR_CATALOG mapping each method to the indicators it characteristically leaves, split by plane. A slice:

MethodHostNetworkIdentity
reconconnection burst to many ports/hosts; scanner fingerprint
executionnew process (Sysmon EID 1); script-block log (PowerShell 4104)
credential-accessLSASS handle/read (Sysmon EID 10)many SPN ticket requests (Kerberoasting, EID 4769)
lateral-movementservice install; named-pipe creationSMB/WinRM/RDP to a new hostlogon type 3/10 from an unusual source (EID 4624)
c2persistent outbound processbeacon: periodic same-size requests, jittered; rare domain/JA3
exfiltrationlarge reads / archive creationlarge/asymmetric outbound to a web service

Why it exists. This is the bridge from offense to defense and the reason this curriculum is a hireable one. For every technique you can perform, you must be able to say what a sensor sees. That single ability is what lets you (a) deconflict honestly with the SOC, (b) write the detection in the report, and (c) predict — in Phase 07 — exactly what the client's EDR, AMSI, and ETW telemetry will and won't catch. An operator who cannot enumerate indicators is running offensive theater.

Under the hood — the three planes, and why they matter. Detection lives in different telemetry sources, and a technique is only caught if the right plane is instrumented:

  • Host — Sysmon, ETW, EDR. Catches process creation, injection, LSASS access. The richest plane, but only if the agent is deployed and the events are shipped.
  • Network — Zeek, Suricata, NetFlow, proxy logs. Catches scanning, C2 beaconing, large exfiltration. Survives even when the host agent is blind, which is why C2 OPSEC obsesses over it.
  • Identity — domain-controller logs (EID 4624/4769), cloud sign-in logs. Catches Kerberoasting, pass-the-ticket, impossible-travel sign-ins. The plane most often under-instrumented and the one AD attacks (Phase 05) live in.

The catalog annotates several indicators with their concrete event id (Sysmon EID 1, PowerShell 4104, Kerberos 4769, logon 4624) so the leap to a Sigma rule in Phase 07 is mechanical.

Telemetry it emits. The chapter is the telemetry. The point: an indicator list is the deliverable that lets a defender write the detection — indicators() in Lab 02 is the seed of every Sigma rule the engagement will produce.

How a defender deconflicts it. When the SOC sees one of these indicators and asks "was that you?", the operator's deconfliction log (Chapter 9) carries the expected indicators for each logged action — so the SOC can match the alert to the action and confirm.

Engagement significance. Lab 02's indicators() is the first appearance of the detection-pairing discipline that every later phase deepens, culminating in the Phase 07 telemetry-gap map and the Phase 12 durable detections.

Common misconceptions.

  • "If I evade the EDR, I'm invisible." You may still light up the network and identity planes; a technique is only invisible if every instrumented plane misses it.
  • "Indicators are IOCs (hashes/IPs)." Those are the brittle bottom of the Pyramid of Pain; behavioral indicators (the events above) are what durable detections target.
  • "I'll figure out the telemetry later." You enumerate it before you act, because it drives the deconfliction decision.

Chapter 9: Deconfliction — Answering "Was That You?"

Zero background. Deconfliction is the process that lets the client's defenders distinguish the red team's activity from a real adversary's. It has two halves: pre-deconfliction (warning the SOC before a noisy action) and the deconfliction log (the record that answers "was that you?" after the fact). It is the single most important operating relationship in an engagement.

What it is. Concretely:

  • A deconfliction contact — a 24/7 channel (a trusted agent at the client) the SOC calls to ask "is this activity yours?"
  • Pre-deconfliction — for high-risk actions (above an OPSEC threshold), you notify the contact before you act, so a predictable alert doesn't trigger a real incident response.
  • The deconfliction log — an append-only record of every action: timestamp, operator, method, the indicators it was expected to emit, and a note. This is what answers a retrospective "was that you?" for any time window.

Why it exists. Without deconfliction, two expensive failures happen. (1) The SOC sees the red team's activity, doesn't know it's authorized, and launches a real incident response — pulling people out of bed, possibly containing systems and breaking the engagement. (2) Worse, the red team's noise masks a real intrusion happening at the same time, and the genuine attacker is lost in the red team's traffic. The deconfliction log prevents both by letting the SOC instantly separate "ours" from "not ours."

Under the hood — the log and the lookup. Lab 02's DeconflictionLog is append-only and kept sorted by (timestamp, name) so lookups are deterministic regardless of insertion order. The core operation is the time-windowed lookup:

SOC alert fires at time T  ->  log.lookup(T - delta, T + delta)
  returns every logged action in the window, with its operator and expected indicators
  empty result  =>  "not us"  =>  treat as a real incident
  match         =>  "that was us"  =>  confirm indicators, stand down

requires_deconfliction(action) decides the pre-deconfliction side: it returns true when the OPSEC score (Chapter 7) is at or above the threshold. Loud, novel, wide-blast actions get a phone call first; quiet recon does not.

Telemetry it emits. The log is itself the engagement's operating telemetry, and at engagement end it feeds the tooling-IOC handoff (templates/evidence-manifest.md) — the list of your own indicators (files, named pipes, domains, JA3) the SOC uses to clean up after you.

How a defender deconflicts it. This is the defender's process. The maturity test for a SOC is exactly: "when you see suspicious activity, can you check it against an authorized engagement before escalating?" The red team makes that possible by keeping the log accurate and the contact reachable.

Engagement significance. In Cedar Lattice, before the noisy psexec-to-domain-controller move you pre-deconflict with Meridian's SOC; afterward, when an EID 4624 alert fires, the SOC queries the log, sees your logged action with its expected indicators, and stands down in minutes instead of mounting an incident. Lab 02 is this entire workflow in code.

Common misconceptions.

  • "Deconfliction ruins the test of the SOC's detection." No — you can still measure detection (did the alert fire?) while preventing a wasteful response. You're testing the sensor, not the 3 a.m. fire drill, unless the ROE specifically scopes a response test.
  • "I'll log actions at the end." The log must be live; a retrospective "was that you?" can come mid-engagement.
  • "Only loud actions need logging." Pre-deconfliction is for loud actions; the log records everything, because any action might generate an alert.

Chapter 10: Evidence and Chain-of-Custody

Zero background. Everything you collect — a log, a capture, a screenshot, a synthetic flag — is evidence, and a client (or a court) will only trust your narrative if that evidence has an unbroken chain of custody: a record of where it came from, when, who collected it, and any change made to it.

What it is. The track's evidence-manifest.md logs, for every artifact: the source (host/account/range), the collection time with timezone, the collector, a SHA-256 hash taken at collection, any transformations (e.g. redaction), and notes. The governing rules: hash at collection; never edit an original (work on copies and record the transformation); redact any real credential, token, or personal data before the artifact leaves the secure store.

Why it exists. Two reasons. Trust — a finding backed by a hashed, timestamped artifact is defensible; one backed by "I remember seeing this" is not. Safety — the redaction and no-real-data rules keep the engagement from accumulating exactly the sensitive data a breach of your storage would expose. The evidence discipline is the data-minimization principle applied to offense.

Under the hood — hashing at collection. The SHA-256 hash, taken the instant the artifact is collected, is the integrity anchor: it proves the artifact you present is the artifact you collected, unaltered. Any later transformation (cropping a screenshot, redacting a token) is done on a copy and noted, so the chain is intact.

Telemetry it emits. The manifest is your own audit trail; combined with the deconfliction log it is the complete operating record of the engagement — what you did, when, and what you collected.

How a defender deconflicts it. At engagement end the tooling-IOC handoff section of the manifest gives the SOC the indicators of your own tooling (on-disk names, named pipes/mutexes, network indicators, persistence created) so they can verify cleanup — the courteous, professional close that turns a one-off test into a repeat client.

Engagement significance. For Cedar Lattice, every artifact — from the Phase 03 pivot map to the Phase 09 cloud IAM finding — lands in this manifest, hashed and sanitized, and the public portfolio ships only the sanitized version. The raw evidence never leaves the encrypted store.

Common misconceptions.

  • "I'll hash it later." Later is too late — the hash must anchor the artifact as collected.
  • "Screenshots are harmless." They routinely leak real hostnames, tokens, and PII; redact before they leave the store.
  • "More evidence is better." Collect the minimum needed to explain and remediate; every extra artifact is extra risk.

Lab Walkthrough

Two labs, in order. Do Lab 01 first — authorization is the gate everything else sits behind.

Lab 01 — Engagement ROE Authorization Guard. Order of TODOs:

  1. _is_ip and _ip_in_cidrs — get CIDR membership right with ipaddress before anything else; everything depends on it. Test 10.20.99.10 against both the /16 and the /24.
  2. _domain_match — the exact-or-subdomain matcher. This is the one with the subtle bug; write the substring trick into your head (evil-meridian-freight.test must fail) before you code it.
  3. in_scope — compose the two: out-of-scope denylists first (they override), then in-scope allowlists, for IPs, accounts, and domains. Deny-by-default for anything unmatched.
  4. method_allowed and within_window — the two simple gates; remember the window is half-open.
  5. _rate_count — count history actions in the rolling (t - W, t] window.
  6. check_action — assemble the gates, collecting every failing reason, stop-condition first. ALLOW only when the reason list is empty.

Lab 02 — OPSEC Risk Scorer + Deconfliction Log. Order of TODOs:

  1. score_action — the weighted sum; match the WEIGHTS constant exactly.
  2. risk_band — map the score through BANDS; mind the exclusive upper bounds and the top band.
  3. indicators — look the method up in INDICATOR_CATALOG, return lists per plane, empty for unknown methods.
  4. requires_deconfliction — the threshold comparison (>=).
  5. assess — bundle the four into one dict.
  6. DeconflictionLog.record / lookup / was_this_us — append-and-sort, inclusive-window lookup, non-empty check. Keep it sorted so lookups are deterministic.

Run LAB_MODULE=solution pytest -q to see the reference pass, then implement lab.py until pytest -q is green.

Success Criteria

You have understood this phase — not just passed the tests — when you can, without notes:

  • State the four things you must name before any action, and explain why authorization (not knowledge) is the boundary.
  • Explain why an authorization guard must be deny-by-default and why out-of-scope overrides in-scope, with the protected-subnet example.
  • Write the exact-or-subdomain rule and explain the substring trick it defeats.
  • Score an action's OPSEC risk, name the band, and enumerate the indicators it emits on each of the three planes.
  • Decide whether an action needs pre-deconfliction and explain what the deconfliction log answers.
  • Walk the SOC's 3 a.m. "was that you?" call end to end, from alert to lookup to stand-down.

Common Mistakes / OPSEC Failures

  • Substring scope matchingin target lets look-alike domains through. Exact-or-subdomain only.
  • In-scope superset hiding a carve-out — out-of-scope must override, or you hit a protected subnet.
  • String-comparing IPs — always use ipaddress membership; "10.20.99.10".startswith("10.20") is not CIDR math.
  • Closed window — the window is half-open; acting at exactly end is out, and the timezone must be pinned.
  • Short-circuiting the guard — collect every failing reason; the deconfliction report needs them.
  • Acting before deconfliction — a predictable loud action with no heads-up burns the SOC's night.
  • No indicator enumeration — "I can't say what that emits" means it can't be deconflicted or detected.
  • Logging at the end — the deconfliction log must be live for a mid-engagement "was that you?".
  • Practicing on something reachable — reachability is never authorization.
  • Not stopping when the stop is pulled — the latch denies everything precisely so there is no "one more action."

Interview Q&A

Q1. What separates a red team engagement from a crime, and what four things must you be able to state before any action? Authorization and intent, not knowledge or skill. The same action — scanning, phishing, credential dumping — is professional work under a signed contract and a felony without one. Before any action I must be able to name four things: the owner (and a signatory with authority to grant access), the scope (which assets, methods, and time are permitted), the stop conditions (what halts the engagement and who can pull that lever), and the deconfliction contact (who the SOC calls to ask "is this you?"). If I cannot name all four, I do not act. Computer-misuse law criminalizes unauthorized access — "it was reachable" and "I caused no damage" are not defenses — so the authorization, written and signed, is the entire boundary.

Q2. Why must an authorization guard be deny-by-default, and why does out-of-scope override in-scope? Because you cannot enumerate the unknown. A default-allow (denylist) guard silently authorizes any sensitive host the ROE author forgot to list — and they will forget, because nobody can list every host they don't know about. Deny-by-default inverts the failure mode: a forgotten entry becomes a false denial (you ask for clarification) instead of a false authorization (a crime). You want mistakes to fail in the safe direction. Out-of-scope overrides in-scope for the same reason: a real ROE carves protected sub-ranges out of broad in-scope ranges — a PCI /24 inside an in-scope /16 — and the only safe interpretation is that the explicit denylist always wins, so a careless superset never silently authorizes a protected asset.

Q3. How do you stop a scope check from being fooled by a look-alike domain? Never use substring matching. scope in target passes evil-meridian-freight.test for an in-scope meridian-freight.test, and even endswith("corp.com") passes evilcorp.com. The correct rule is exact-or-subdomain: target == d or target ends with "." + d. That makes host1.corp.example match corp.example (a true subdomain) while evilcorp.example and corp.example.attacker.test both fail. For IPs the analogous mistake is string comparison; use real CIDR membership via the ipaddress stdlib so 10.20.99.10 is correctly recognized as inside both the in-scope /16 and the out-of-scope /24 — and denied because out-of-scope wins.

Q4. What is OPSEC, and how would you score the risk of a planned action? OPSEC is controlling the observable signal your activity produces — for a red team, deliberately choosing what to trip rather than stumbling into it. I score an action with a transparent weighted sum over five attributes: noise (telemetry generated), novelty (how anomalous it looks against a baseline — anomaly is detection bait), blast radius (how many hosts/identities it touches), time of day (off-hours stands out against a quiet baseline), and irreversibility (how hard it is to undo, which multiplies the harm if it goes wrong). The sum maps to a band — low/medium/high/critical — and the band drives the deconfliction decision. I keep the model linear and the weights visible precisely because a red team decision must be explainable to a client and tunable to their detection maturity; a black-box score I can't defend is worse than a transparent one I can.

Q5. What is deconfliction, what is the deconfliction log for, and what goes wrong without it? Deconfliction is what lets the client's defenders tell my activity from a real adversary's. It has two halves: pre-deconfliction — warning the SOC before a high-OPSEC action so a predictable alert doesn't trigger a real response — and the deconfliction log — an append-only, time-sorted record of every action with its operator, method, and expected indicators, so the SOC can answer a retrospective "was that you?" for any window. Without it, two expensive things happen: the SOC launches a real incident response over my authorized activity (people out of bed, systems possibly contained, the engagement broken), or — worse — my noise masks a genuine intrusion happening at the same time and the real attacker is lost in my traffic. The log lets the SOC instantly separate "ours" from "not ours."

Q6. The SOC calls at 3 a.m. about an alert — walk me through how you answer "was that you?". I take the alert's timestamp and query the deconfliction log for a window around it. If the log returns a matching action, I read back its operator, method, and the indicators it was expected to emit, and confirm they match what the SOC is seeing — "yes, that EID 4624 from the jump host at 02:14 was operator B's lateral move, here are the expected indicators, please stand down." If the log returns nothing in that window, my answer is "that is not us — treat it as a real incident," which is just as valuable, because I've told them where the boundary of my activity is. Either way the log, kept live and time-sorted, turns a potential hours-long fire drill into a two-minute lookup. And if the SOC's call is actually a stop request, I latch the stop condition and every further action is denied until we agree to resume.

Q7. How do you handle evidence so a client trusts your findings? Chain of custody. Every artifact is logged at collection time in the evidence manifest with its source, collection time and timezone, collector, and a SHA-256 hash taken at collection — the integrity anchor that proves the artifact is unaltered. I never edit an original; transformations (cropping, redaction) happen on copies and are recorded. I redact any real credential, token, or PII before an artifact leaves the secure store, and I collect the minimum needed to explain and remediate — every extra artifact is extra risk if my storage is ever breached. At engagement end I hand the SOC the tooling-IOC list (my own files, pipes, domains, persistence) so they can verify cleanup. A finding backed by a hashed, timestamped, sanitized artifact is defensible; one backed by memory is not.

Q8. The client wants you to test their detection. How do you reconcile that with deconfliction — won't telling them ruin the test? No, because detection and response are different things, and I scope which one I'm testing. I can pre-deconflict an action and still measure whether the sensor fired — did the Sigma rule trigger, did the alert reach the SOC queue? — without forcing a wasteful 3 a.m. response. If the ROE specifically scopes a response test (a "did the SOC react correctly" exercise), I keep a tighter trusted-agent circle and a clear stop channel, but even then the deconfliction log records everything so that if a real incident happens during the test, we can separate it instantly. Deconfliction never blinds the sensor; it just keeps the human response measured and reversible. The durable deliverable is the same either way: the detection-gap map and the Sigma rules the client keeps.

References

Primary sources first.

  • Vest & Tubberville — Red Team Development and Operations. The operating discipline: ROE, deconfliction, OPSEC, the engagement cadence.
  • PTES — Penetration Testing Execution Standard, Pre-engagement Interactions. Scope, rules of engagement, and the authorization ("get-out-of-jail") letter.
  • NIST SP 800-115 — Technical Guide to Information Security Testing and Assessment. Rules of engagement, coordination, handling of sensitive data.
  • MITRE ATT&CK — Data Sources / Data Components. The mapping from technique to the telemetry that detects it; the basis for Lab 02's indicator catalog (revisited in Phase 07).
  • MITRE D3FEND and the Pyramid of Pain (David Bianco). Why behavioral indicators beat brittle IOCs — the durable-detection argument behind Chapter 8.
  • OSSTMM and CREST engagement-management guidance. The consulting-side discipline for scoping and running assessments.
  • CFAA (U.S.), the UK Computer Misuse Act, and Gulf-state computer-crime statutes. The legal frame that makes authorization, not knowledge, the boundary.
  • The track templatesrules-of-engagement.md, evidence-manifest.md, lab-acceptance-checklist.md — reuse this vocabulary verbatim.