Configuration¶
Runtime Prerequisites¶
Overwatch targets Node.js >= 20 (matches package.json#engines). Older
runtimes will be rejected by the npm install step. A few capabilities are
"soft" optional and require system-level packages installed by the operator:
- Interactive PTY sessions (
open_session/send_to_sessionwith a pseudo-terminal): requirenode-pty, which compiles a native binding at install time and needs the platform's build toolchain (Xcode CLI tools on macOS,build-essential+python3on Debian/Ubuntu). - Password-based SSH sessions: require
sshpasson$PATHso the session-control layer can drive interactive password prompts non-interactively.
If node-pty is not built, Overwatch falls back to non-PTY pipes for shell
sessions; if sshpass is missing, password-based SSH sessions are reported
as a precheck failure rather than silently hanging.
Engagement Config (engagement.json)¶
The engagement config defines scope, objectives, and OPSEC policy. It's loaded at server startup from the path specified by OVERWATCH_CONFIG (defaults to ./engagement.json).
Schema¶
{
"id": "string (required)",
"name": "string (required)",
"created_at": "ISO 8601 timestamp (required)",
"profile": "goad_ad | single_host | cloud | web_app | hybrid | network (optional, inferred if omitted; network must be set explicitly)",
"scope": {
"cidrs": ["CIDR notation strings"],
"domains": ["domain names"],
"exclusions": ["IPs or hostnames to exclude"],
"hosts": ["additional in-scope hostnames"],
"aws_accounts": ["AWS account IDs"],
"azure_subscriptions": ["Azure subscription IDs"],
"gcp_projects": ["GCP project IDs"],
"url_patterns": ["URL glob patterns"]
},
"objectives": [
{
"id": "string (required)",
"description": "string (required)",
"target_node_type": "host | service | credential | ...",
"target_criteria": { "property": "value" },
"achieved": false
}
],
"opsec": {
"name": "string (required)",
"max_noise": 0.7,
"enabled": true,
"approval_mode": "auto-approve | approve-critical | approve-all",
"approval_timeout_ms": 300000,
"time_window": {
"start_hour": 8,
"end_hour": 22
},
"blacklisted_techniques": ["zerologon"],
"notes": "Free-form notes"
},
"phases": [
{
"id": "recon",
"name": "Recon",
"order": 1,
"strategies": ["enumeration"],
"entry_criteria": [{ "type": "always" }],
"exit_criteria": [{ "type": "objective_achieved", "objective_id": "host-discovery-complete" }],
"opsec_overrides": { "max_noise": 0.3 },
"approval_overrides": { "mode": "auto-approve" }
}
],
"tape": {
"enabled": false,
"dir": "./tapes",
"file": null
},
"hash_chain_enabled": true,
"engagement_nonce": "64-char-hex-string (auto-generated for new engagements)",
"engagement_signing_key_id": "optional-key-id-for-signed-checkpoints",
"subagent_isolation": "in_process | process"
}
Scope¶
| Field | Type | Description |
|---|---|---|
cidrs |
string[] |
CIDR ranges in scope (e.g., 10.10.10.0/24) |
domains |
string[] |
Domain names in scope (e.g., target.local) |
exclusions |
string[] |
IPs or hostnames explicitly excluded |
hosts |
string[] |
Additional in-scope hostnames not covered by CIDRs |
aws_accounts |
string[] |
AWS account IDs in scope |
azure_subscriptions |
string[] |
Azure subscription IDs in scope |
gcp_projects |
string[] |
GCP project IDs in scope |
url_patterns |
string[] |
URL glob patterns in scope (e.g., https://app.target.com/**) |
Lab Profile¶
The optional profile field selects the lab preflight profile, which controls which checks run and what tools are required.
| Profile | Description |
|---|---|
goad_ad |
Active Directory lab — requires BloodHound, NXC, nmap. Checks domain scope. |
single_host |
Single-target HTB machine — minimal scope, focused checks. |
network |
Network-only engagement — requires nmap. BH/NXC optional. Checks CIDR scope, not domains. |
web_app |
Web application assessment — checks URL patterns, recommends web scanners. |
cloud |
Cloud-only engagement — checks cloud account scope, recommends cloud tools (pacu, prowler). |
hybrid |
Combined infrastructure + cloud — checks all scope types. |
If omitted, the profile is inferred with the following precedence: hybrid when both domains and cloud accounts are present, cloud when cloud accounts are scoped (even if URL patterns also exist), web_app when URL patterns are scoped (takes precedence over domains), goad_ad when scope.domains is non-empty, single_host otherwise. Note: network is never inferred — it must be set explicitly.
Objectives¶
Each objective describes a goal. The engine tracks progress by matching graph nodes against target_criteria.
| Field | Type | Description |
|---|---|---|
id |
string |
Unique objective identifier |
description |
string |
Human-readable goal |
target_node_type |
NodeType |
Node type to match (optional) |
target_criteria |
object |
Property key-value pairs to match against nodes |
achieved |
boolean |
Automatically set when criteria are met |
OPSEC Profiles¶
The name field is free-form text; common values include ctf, pentest, assumed_breach, redteam.
| Profile | max_noise |
Description |
|---|---|---|
ctf |
1.0 | No restrictions. Speed over stealth. |
pentest |
0.7 | Standard internal pentest. Some noise acceptable. |
assumed_breach |
0.5 | Start with access. Focus on objectives. |
redteam |
0.3 | Stealth engagement. Quiet techniques preferred. |
max_noise is a hard ceiling (0.0–1.0). Actions with opsec_noise above this value are filtered from the frontier and rejected by validate_action.
enabled (boolean, default false for legacy compatibility) gates the OPSEC ceiling enforcement on direct run_bash/run_tool actions. With enabled: false the noise pipeline is fully inert — no substitution, no recording, no rejection. Turn it on for engagements where the ceiling needs to bite.
time_window (optional) restricts action execution to specific hours (0–23). Useful for engagements with business-hours-only authorization.
blacklisted_techniques are rejected outright by validate_action regardless of noise level.
approval_mode controls the per-action approval gate:
auto-approve(default) — actions run without operator review.approve-critical— actions whose noise would exceed half the ceiling, or whose technique is blacklisted, or under defensive pressure, are queued for operator approval.approve-all— every action is queued.
approval_timeout_ms (default 300000) bounds how long the queue waits for an operator response. On timeout, the action proceeds but is stamped auto_approved: true and unattended_execute: true so retros and OPSEC logs surface unattended decisions.
Phase-Aware Policy¶
Engagement phases declared in phases can override OPSEC and approval policy while they're active. The active phase is the one whose entry_criteria are met but whose exit_criteria are not.
"phases": [
{
"id": "exploit",
"name": "Exploitation",
"order": 2,
"strategies": ["post_exploitation"],
"entry_criteria": [{ "type": "objective_achieved", "objective_id": "initial-access" }],
"exit_criteria": [{ "type": "objective_achieved", "objective_id": "domain-admin" }],
"opsec_overrides": { "max_noise": 0.4 },
"approval_overrides": {
"mode": "approve-all",
"blacklisted_techniques": ["zerologon"]
}
}
]
opsec_overridesisPartial<OpsecProfile>— only the fields you specify get overridden; others fall through to the engagement-level OPSEC.approval_overrides.blacklisted_techniquesextends the engagement-level list (cannot weaken operator-level safety).- Phase transitions emit
phase_entered/phase_exitedevents to the activity log. These are hash-chained.
Deterministic ID and Replay¶
For engagements created after the foundations work shipped:
engagement_nonce(64-char hex, auto-generated byengagement-manageron creation) flips action and event ID generation fromuuidv4tosha256(nonce | agent_id | timestamp | command_signature | sequence). Same inputs → same IDs across runs.hash_chain_enableddefaults totrue. The activity-chain emits signed checkpoints every 500 events / 30 minutes; verifiers resume from the latest checkpoint instead of replaying genesis.- Mutation journal (
<state-file>.journal.jsonl) is a write-ahead log appended before each graph mutation. On load, the engine replays journal entries past the last snapshot. Snapshot rotation truncates the journal up to the snapshot's seq. - Golden-master replay: tests can record a tape (in
src/__tests__/golden-master/fixtures/), replay against a fresh engine, and assert byte-identical state. Two replays of the same tape produce the samegraph_hash— seesrc/services/golden-replay.ts.
Strict migration. Engagements created before the foundations work (no engagement_nonce field) keep uuidv4 IDs forever. We do not retroactively recompute IDs. If you need replay/audit guarantees on an existing engagement, create a fresh engagement and re-run.
Sub-agent Isolation (Scaffolded)¶
subagent_isolation controls where sub-agents run:
'in_process'(default) — sub-agents share memory with the engine and call MCP tools directly. Current production behavior.'process'— sub-agents run as child Node processes communicating with the engine over JSON-over-stdio persrc/services/subagent-ipc.ts. Scaffolded and proven on the recon-scoping role. Other roles fall back to in-process even when this flag is set, until follow-up work fills out coverage.
Choosing a Profile¶
- CTF / Lab — Use
ctfwhen speed matters and there's no defender. GOAD labs, HTB machines, practice ranges. - Internal pentest — Use
pentestfor standard authorized testing. Allows port scanning, enumeration, and most exploitation techniques. - Assumed breach — Use
assumed_breachwhen you start with valid credentials. Skips noisy discovery and focuses on lateral movement and privilege escalation. - Red team — Use
redteamfor stealth engagements with active defenders. Limits to quiet techniques: targeted queries, Kerberoasting, careful lateral movement. Blocks mass scanning and brute force.
Scope Violation Behavior¶
When an action targets something outside scope:
- Frontier filtering —
next_tasknever returns frontier items targeting out-of-scope hosts or services - Validation rejection —
validate_actionreturnsinvalidwith error:"Target is out of scope" - No graph pollution —
report_findingaccepts out-of-scope nodes (they may be discovered passively) but they won't generate frontier items - URL fallback — When
url_patternsis not configured,validate_actionfalls back to checking the URL's hostname againstscope.domains. URLs with hostnames not matching any scoped domain are rejected.
Example: Multi-Domain Engagement¶
{
"id": "eng-multi",
"name": "Multi-Domain Assessment",
"created_at": "2026-03-20T00:00:00Z",
"scope": {
"cidrs": ["10.10.10.0/24", "10.10.20.0/24", "192.168.1.0/24"],
"domains": ["corp.local", "dev.corp.local", "partner.org"],
"exclusions": ["10.10.10.1", "10.10.20.1", "192.168.1.254"],
"hosts": ["jumpbox.corp.local", "vpn.partner.org"]
},
"objectives": [
{
"id": "obj-da-corp",
"description": "Domain Admin on corp.local",
"target_node_type": "credential",
"target_criteria": { "privileged": true, "cred_domain": "corp.local" },
"achieved": false
},
{
"id": "obj-da-partner",
"description": "Domain Admin on partner.org",
"target_node_type": "credential",
"target_criteria": { "privileged": true, "cred_domain": "partner.org" },
"achieved": false
}
],
"opsec": {
"name": "pentest",
"max_noise": 0.7,
"blacklisted_techniques": ["zerologon", "printnightmare"],
"notes": "Authorized internal pentest. No production disruption."
}
}
Example: Red Team with Time Window¶
{
"id": "eng-redteam",
"name": "Red Team - Financial Corp",
"created_at": "2026-03-20T00:00:00Z",
"scope": {
"cidrs": ["10.0.0.0/8"],
"domains": ["fincorp.com"],
"exclusions": ["10.0.0.1", "10.0.0.2"],
"hosts": []
},
"objectives": [
{
"id": "obj-ceo-mail",
"description": "Access CEO mailbox",
"target_node_type": "service",
"target_criteria": { "service_name": "exchange", "hostname": "mail.fincorp.com" },
"achieved": false
}
],
"opsec": {
"name": "redteam",
"max_noise": 0.3,
"time_window": {
"start_hour": 8,
"end_hour": 18
},
"blacklisted_techniques": ["zerologon", "printnightmare", "petitpotam", "mass_scan"],
"notes": "Active SOC monitoring. Business hours only. Avoid triggering EDR alerts."
}
}
Time window enforcement
When time_window is set, validate_action will return a warning (not an error) if the current hour is outside the window. This is a soft constraint — the operator can choose to proceed.
Environment Variables¶
| Variable | Default | Description |
|---|---|---|
OVERWATCH_CONFIG |
./engagement.json |
Path to engagement configuration file |
OVERWATCH_SKILLS |
./skills |
Path to skill library directory |
OVERWATCH_DASHBOARD_PORT |
8384 |
Port for live dashboard (set to 0 to disable) |
OVERWATCH_TRANSPORT |
stdio |
Transport mode: stdio or http |
OVERWATCH_HTTP_PORT |
3000 |
Port for HTTP/SSE transport (when OVERWATCH_TRANSPORT=http) |
OVERWATCH_HTTP_HOST |
127.0.0.1 |
Bind address for HTTP/SSE transport |
OVERWATCH_TAPE |
unset | Force the in-process JSON-RPC tape recorder on (1/true/on) or off (0/false/off). Overrides engagement.tape.enabled. See Tape Recording. |
OVERWATCH_TAPE_DIR |
./tapes |
Directory for auto-named tape files when the recorder is enabled |
OVERWATCH_TAPE_FILE |
unset | Explicit tape file path (overrides OVERWATCH_TAPE_DIR) |