Hitchhiker's Guide — Phase 00: Standing Up an Authorized, Deny-by-Default Range

Read this if you understand why authorization, ROE, OPSEC, and deconfliction matter (that's the WARMUP) and now need to operate them: build an isolated range you can practice in without ever touching something you don't own, run the ROE/OPSEC/deconfliction workflow on every action, handle evidence, and tear it all down. Derivations and the from-zero treatment are in the WARMUP; this is the operator's checklist.


0. The 30-second mental model

You never act without four answers: owner, scope, stop, deconflict. The range is the place where all four are trivially true — you own it, the scope is the range, you can pull the plug, and there is no third party to deconflict with because nothing reaches the outside. The whole skill of Phase 00 is making that boundary mechanical: a deny-by-default guard (Lab 01) and an OPSEC/deconfliction planner (Lab 02) you run before every action, and a range whose egress is deny-by-default so a mistake fails closed instead of reaching the internet.

Deny-by-default everywhere. Network egress, authorization, scope — all default-deny. A forgotten rule should mean "nothing happened," never "it reached production."


1. The range — owned, isolated, deny-by-default

The track README §7 has the full diagram. For Phase 00 you need the operating rules, not yet the AD forest or the cloud accounts (those come online in Phases 05 and 09). The minimum viable range:

            Management network (isolated)
        Git · notes · evidence store · screenshots
                       |
        [host firewall / pfSense — DENY-BY-DEFAULT egress]
              /                 \
       operator VM          target VM(s)
       (your tooling)       (owned fixtures)
              \                 /
               isolated vSwitch (host-only)
   No bridge to home / corporate / public networks. Ever.

Hard rules (non-negotiable):

  • Host-only / internal networking. The victim and operator VMs share an isolated virtual switch with no NAT and no bridge to your home or corporate LAN. Verify with a connectivity test that a ping to 8.8.8.8 from inside the range fails (see §5).
  • Deny-by-default egress. If the range needs any outbound (it usually doesn't for Phase 00), it goes through one firewall with an explicit, minimal allowlist and default-drop. The default is "nothing leaves."
  • No real data, ever. No real credentials, customer data, or PII in any fixture, capture, screenshot, or commit. Flags are synthetic and clearly marked.
  • Snapshot before risky work. Take a clean snapshot of every VM before you touch it; the plan is always destroy-and-rebuild.
  • Name the owner. Every machine in the range is owned by you. If you can't name the owner, it's not in the range.

Before any exercise that involves vulnerable services, an AD forest, cloud accounts, C2 software, or malware-like samples, run templates/lab-acceptance-checklist.md top to bottom. Any unchecked box means stop.


2. The ROE / authorization workflow (Lab 01 in practice)

On a real engagement you fill in templates/rules-of-engagement.md with the client and both parties sign. For practice, you author your own ROE for the range. Either way, the operating loop per action is the same — and Lab 01 is that loop in code.

Author the ROE as a data object first. Before any action, the scope exists as a concrete object:

ROE(
  in_scope_cidrs   = ("10.20.0.0/16",)                       # the range's subnet
  in_scope_domains = ("corp.meridian-freight.test",)         # owned, .test TLD (never resolvable)
  in_scope_accounts= ("svc-emul-01",)                        # synthetic emulation identity
  out_of_scope_cidrs   = ("10.20.99.0/24",)                  # the protected carve-out
  out_of_scope_domains = ("payments.meridian-freight.test",)
  allowed_methods  = ("recon", "exploitation", "lateral-movement")
  window_start/end = the agreed interval (timezone PINNED)
  rate_limit       = e.g. 3 per 60s
  stop_engaged     = False                                    # the circuit breaker
)

Run the guard before every action. For each proposed action (target, method, timestamp):

decision = check_action(action, roe, history)
  ALLOW  -> proceed; append to history
  DENY   -> read EVERY reason; fix the action or get clarification; do NOT proceed

The guard is deny-by-default: out-of-scope overrides in-scope, the method must be on the allowlist, the timestamp must be inside the half-open window, the rate must be under the limit, and an engaged stop denies everything. It collects all failing reasons because your deconfliction notes need them.

Use a real range domain that cannot escape. Use the reserved .test TLD (RFC 6761) for range hostnames — it is guaranteed never to resolve on the public internet, so a misconfiguration cannot accidentally point at a real host. Never use a domain you don't own.


3. The OPSEC / deconfliction workflow (Lab 02 in practice)

Authorization says may I. OPSEC and deconfliction say how loud, and who do I warn. Run this on every action the guard allowed.

Score it before you run it.

assess(action) -> { score, band, indicators{host,network,identity}, requires_deconfliction }
  • Read the band. low/medium → proceed and log. high/critical → it's loud; plan accordingly.
  • Read the indicators. Know, before you act, exactly what telemetry each plane will show. If you can't name the indicators, you're not ready to run the action.
  • Pre-deconflict above the threshold. If requires_deconfliction is true, notify the deconfliction contact before acting. On the range there's no SOC, so you simulate it: write the heads-up to your engagement log so the muscle memory is real.

Log every action — always. Whether loud or quiet:

log.record(action, operator="op-a", note="pre-approved 02:00" )

The log is append-only and time-sorted. The "was that you?" drill:

# an alert fires at time T
log.lookup(T - delta, T + delta)
  match  -> "that was us"; read back operator + expected indicators; stand down
  empty  -> "NOT us"; treat as a real incident

Practice the drill until it's reflexive: pick a random timestamp, run the lookup, and narrate the answer out loud. That two-minute lookup is what replaces a real SOC's hours-long fire drill.


4. Evidence handling

Every artifact goes in templates/evidence-manifest.md at collection time. The non-negotiables:

  • Hash at collection. sha256sum artifact the instant you collect it; record the hash in the manifest. The hash is the integrity anchor.
  • Never edit an original. Work on copies; record every transformation (crop, redact) in the manifest.
  • Redact before it leaves the secure store. Any real credential, token, or PII is redacted, and the redaction is noted. On a clean range there is no real data — keep it that way.
  • Record source, time (with timezone), and collector for every artifact.
  • Minimum collection. Keep only what you need to explain and remediate. Set a destruction date.

At engagement end, fill the tooling-IOC handoff table: the on-disk names, named pipes/mutexes, network indicators (domains/IPs/JA3), and any persistence your tooling created — so the (simulated) SOC can verify cleanup.


5. Runtime verification — prove effective isolation, not intended config

Config files lie; verify the actual state.

ISOLATION   from inside the range: a connectivity test to a public IP must FAIL.
            (No internet egress means a mistake can't reach a third party.)
EGRESS      if any outbound is allowed, confirm the firewall default is DROP and the
            allowlist is exactly what you intend — test a denied destination is blocked.
SCOPE       run check_action against a known out-of-scope target -> must DENY.
            run it against the protected /24 inside the in-scope /16 -> must DENY.
WINDOW      run an action at exactly window_end -> must DENY (half-open).
RATE        replay limit+1 actions inside the window -> the last must DENY.
STOP        set stop_engaged and run any valid action -> must DENY.
LOG         record an action, then lookup its window -> must return it; lookup an empty
            window -> must be empty.

If any of these does not behave as expected, the boundary is not real yet. The Lab 01 and Lab 02 test suites are these checks — run LAB_MODULE=solution pytest -q in each lab to confirm the reference boundary holds, then implement lab.py until your own does.


6. Teardown and cost control

  • Destroy and rebuild. After risky work, revert to the clean snapshot or destroy the VMs entirely. Persistence never outlives the exercise without an explicit, logged teardown step.
  • No cloud yet. Phase 00 needs no cloud spend. When cloud comes online (Phase 09), the rule is: separate, budgeted accounts with quotas, billing alerts, and a verified teardown script before the first deployment.
  • Wipe evidence per the destruction date. Sanitized artifacts go to the portfolio; raw evidence is destroyed on schedule and the destruction is recorded.
  • Re-run the acceptance checklist before the next exercise; a torn-down range is rebuilt clean.

7. The evidence packet (what Phase 00 produces)

A complete, sanitized Phase 00 deliverable contains:

00-operating-rules/
├── roe.md                     # the filled ROE (range or Cedar Lattice), both-party signed
├── opsec-plan.md              # expected indicators per planned action; deconfliction contacts
├── deconfliction-procedure.md # the "was that you?" workflow + the contact + the stop channel
├── range-diagram.md           # the isolated, deny-by-default range
├── evidence-manifest.md       # hashed, sanitized artifacts + tooling-IOC handoff
└── tools/                     # the ROE guard + the OPSEC/deconfliction planner, with tests

This is the portfolio artifact: the proof that before any technique, you operate inside a boundary you can defend.


8. Common false claims (calibration)

  • "It's isolated" — but a connectivity test was never run, and the VM actually had NAT. Prove isolation with a failed egress test, don't assume it from the config.
  • "It's in scope" — based on a substring match that let a look-alike domain through. Scope is exact-or-subdomain and real CIDR math, with out-of-scope overriding in-scope.
  • "The SOC knows it's us" — but the deconfliction log was written up after the fact, so a mid-engagement "was that you?" had no answer. The log is live.
  • "That action is quiet" — with no indicator enumeration to back it. If you can't name the telemetry on each plane, you don't know how loud it is.
  • "We can stop any time" — but there's no emergency-stop channel and no stop latch, so "stop" is a hope, not a control. The stop denies everything, immediately.
  • "No real data was touched" — but a screenshot in the report shows a real hostname and a token. Redact before it leaves the store; collect the minimum.
  • "It's authorized" — on a verbal OK from someone without authority to grant access. The signatory must have authority, and it must be in writing, on file, and reachable.

The thread through all of these: a boundary you assert is not a boundary. A boundary you verified — with a failed egress test, a DENY from the guard, a non-empty log lookup — is.


9. War stories (calibration)

These are the failure patterns that turn a clean engagement into an incident. Each one is a reason a control in this phase exists.

  • "The scan followed a route off the agreed network." An operator chasing a pivot let a tool reach an IP that was inside a broad in-scope range but inside a carved-out, out-of-scope subnet — a third party's shared infrastructure. The fix was already in the ROE: out-of-scope overrides in-scope, and the guard denies the protected /24 even though it sits inside the in-scope /16. Run the guard; don't trust the tool's own target list.
  • "The SOC mobilized at 2 a.m. over our beacon." No pre-deconfliction, no live log. The on-call team spent the night chasing the red team's own C2, and nearly contained a production host. After: every high-OPSEC action is scored, pre-deconflicted above the threshold, and logged live — so the next alert was a two-minute "that was us" lookup.
  • "We said the range was isolated." It had NAT enabled and one of the tool VMs phoned home to a package mirror mid-exercise. Nothing leaked, by luck. After: a failed egress test (ping 8.8.8.8 must fail) is part of range bring-up, every time, before any action.
  • "The screenshot in the report had a real token." A capture from a misconfigured fixture carried a credential into the deliverable. After: redact-before-it-leaves-the-store became a hard gate, and fixtures use only synthetic, clearly-marked flags.
  • "We couldn't stop fast enough." A client asked to stand down during a real (unrelated) incident and the operator finished "one more action" first. After: the stop is a latch that denies everything the instant it's pulled — there is no "one more."

The calibration lesson across all five: the boundary is cheap to enforce before the action and ruinous to discover after it. Phase 00 is the cheap-before discipline, made mechanical.