ravage

AI Web Operator Guide

ai-web is the first real model-driven ravage agent. It runs a loop:

  1. send scoped context to the model,
  2. require one JSON action,
  3. execute one scoped tool,
  4. append the observation back to the transcript,
  5. repeat until final or the turn budget is reached.

It runs against localhost targets by default. In the active CLI, public/DNS targets are rejected before a scan or attack starts; expose the authorized app on 127.0.0.1 or localhost and list that URL in scope.in_scope. Tools must stay on the same target origin and inside scope. Findings are only written after a matching typed probe produces confirmed, replayable evidence for the same class, endpoint, and parameter.

HTTP redirects are validated before they are followed. Docker-backed tool runtime records orchestrator/scope_firewall_plan_generated, applies the container egress firewall before supported external tools run, and then records orchestrator/scope_firewall_rules_applied with a rule digest. Host runtime records the plan only and relies on application-layer scope checks. Firewall plan destinations are IP/CIDR-only; hostname entries are skipped rather than resolved once by iptables or supported with broad DNS egress.

Use ai-web only for local research, authorized defensive testing, and controlled benchmarks. Do not run it against public internet targets, third-party systems, production services, or any environment where you do not have explicit written authorization and agreed rules of engagement.

The default runtime skill is:

packages/ravage/src/ravage/agent_skills/web-pentest/skills.md

The skill path and SHA-256 are recorded in audit rows, workspace events, and benchmark JSON.

Preconditions

Install Ravage from this source checkout and run commands from the repository root. If the CLI is not on PATH, use .venv/bin/ravage.

Start a legal local target. For the bundled Acme lab:

ravage lab up ravage-acme-box

Start a model endpoint. For Ollama:

ollama serve

Verify the model server:

curl -sS http://127.0.0.1:11434/v1/models

If this command returns connection refused, ai-web cannot run with local-ollama.

Run ai-web

RAVAGE_OLLAMA_MODEL=qwen2.5-coder:32b \
OLLAMA_BASE_URL=http://localhost:11434/v1 \
ravage attack examples/labs/ravage-acme-box/brief.yaml \
  --model-profile local-ollama \
  --model-tier mid \
  --memory off \
  --max-turns 12

The wrapper prints the run directory, stdout path, report path, and final status. Detailed model turns and tool observations are written to runs/<brief>-<timestamp>/agent.stdout.

Expected detailed trace shape:

[plan] mode=ai-agent agent=ai-web model_profile=local-ollama model_tier=mid
[kill-chain] 1/7 authorization_scope detail=...
[kill-chain] 4/7 hypothesis_generation detail=...
[ai:turn] 1 stage=2/7 reconnaissance action=discover_attack_surface
[tool] discover_attack_surface routes=...
[observation] OBSERVATION ...

When an observation is appended to the model transcript, Ravage wraps it in a BEGIN_RAVAGE_UNTRUSTED_TOOL_OBSERVATION envelope using ravage-observation-envelope-v1. Target-derived strings that look like agent action JSON, confirmation fields, or reporting commands are escaped in the transcript; the raw evidence payload remains in audit/workspace events.

If a confirmed SQLi is found, stdout includes:

[finding] confirmed sql_injection endpoint=... param=...

Stop the lab when finished:

ravage lab down ravage-acme-box

Run Deterministic DAST

Use ravage scan when you want a reproducible DAST pass without spending model turns. It reuses the same scoped route discovery, typed probes, audit store, and report writer as ai-web, but chooses recommended probes deterministically.

ravage scan examples/labs/ravage-acme-box/brief.yaml \
  --run-dir runs/acme-scan \
  --probe surface_map \
  --probe secret_sweep \
  --tool-runtime auto

Use --all-probes to run the full deterministic catalog, or repeat --probe for a focused run. Inspect available probes with:

ravage scan --help
ravage tools list

Each run writes:

runs/<brief>-scan-<timestamp>/
  audit.db
  report.json
  scan.stdout
  workspace/

The scan command is deterministic and should normally use a new --run-dir for each run. External tool runtime selection is accepted for command compatibility; the current built-in scan probes run in-process:

ravage scan examples/labs/ravage-acme-box/brief.yaml \
  --all-probes \
  --tool-runtime auto

Install or build tools where Ravage runs. For a host runtime, that means the same shell, VM, WSL environment, or PATH that launches Ravage. For Docker runtime, build the image with scripts/install_tools.sh --method docker --execute. From a source checkout, scripts/install_tools.sh --execute is the direct fix for missing-tool preflight output. The helper sets .tools/bin, .tools/go-root/bin, and repo-local HOME/XDG state directories before running ravage tools check. The host installer writes Go-based tools to .tools/bin and can bootstrap a repo-local Go toolchain under .tools/go-root when the system has no go. A separate vulnerable target VM does not need those tools unless Ravage itself is running inside that VM.

Current CLI Shape

The current public entry point uses explicit subcommands such as ravage attack, ravage scan, ravage xben, and ravage competitors. Historical YAML run-loader examples are not active operator instructions.

For attack runs, pass the brief and run settings directly:

ravage attack examples/labs/ravage-acme-box/brief.yaml \
  --model-profile hosted-openai \
  --model-tier low \
  --tool-runtime auto \
  --memory off \
  --allow-paid-models

The public CLI currently accepts --memory off only. Active retrieval, writes, review, and promotion are design-stage. See Memory Design for the planned local SQLite model and review policy.

Authorized Remote Target

Use this only for a site you own or have written permission to test. The target URL must match an entry in the brief scope.

# examples/remote_authorized_brief.yaml
engagement_id: "99999999-9999-4999-8999-999999999999"
scope:
  in_scope:
    - "https://staging.example.test"
  out_of_scope: []
roe:
  max_rps: 5
  no_destructive_actions: true
  data_handling: "placeholders_only"
objectives:
  - "web_application_assessment"
budget:
  max_cost_usd: 5.0
  max_runtime_min: 20

For a normal pentest, prefer web_application_assessment or api_security_assessment. Do not ask operators to guess a vulnerability class such as xss or jwt before testing. Use specific objectives only for a targeted retest where the class is known from prior evidence.

Recommended objective patterns:

If the actual issue is XSS but a user accidentally writes jwt, Ravage should not treat that label as proof. Prefer the broad objective for first-pass testing so live route, response, browser, source-guided, and tool signals drive the workflow.

For a user-owned staging app, publish the service to localhost and generate a brief from that URL:

ravage brief template --target-url http://127.0.0.1:8080 > brief.yaml
ravage attack brief.yaml \
  --target-url http://127.0.0.1:8080 \
  --model-profile hosted-openai \
  --model-tier low \
  --allow-paid-models \
  --max-turns 12

ravage attack runs bounded tool recon by default; pass --no-tool-recon to skip it. The attack wrapper defaults to --tool-runtime auto; in scoped runs, auto uses the Docker-backed path for supported external tools so the container egress firewall is present. Use --tool-runtime host only when host-only, application-layer enforcement is acceptable, or --tool-runtime docker when the Docker image must be required explicitly.

docker build -t ravage-kali:latest -f sandbox/kali.Dockerfile sandbox
OPENAI_API_KEY=... \
ravage attack examples/labs/ravage-acme-box/brief.yaml \
  --model-profile hosted-openai \
  --model-tier low \
  --tool-runtime auto \
  --tool-image ravage-kali:latest \
  --max-turns 20 \
  --allow-paid-models

For normal new attacks, leave --run-dir unset so Ravage creates a timestamped state directory. Reuse a prior attack directory only with --resume.

External tool runtimes are still localhost-only; remote authorized targets keep scanner wrappers disabled until a separate remote tool policy exists.

Tools The Model Can Call

The model must emit exactly one JSON object per turn:

{
  "action": "discover_attack_surface",
  "kill_chain_stage": "reconnaissance",
  "args": {},
  "rationale": "map routes"
}

Supported actions:

report_sqli and report_finding are gated. If the model tries to report without prior confirmed tool evidence for the same class, endpoint, and parameter, ravage records finding_rejected_no_evidence and does not write a finding.

Persistent terminal actions are for approved local tools that need interaction or long-running output. Prefer one-shot tools for simple probes.

Browser tools are localhost-only in the current runtime. Use browser_login for ordinary login forms:

{
  "action": "browser_login",
  "args": {
    "path": "/login",
    "username_selector": "#user",
    "username": "bee",
    "password_selector": "#pass",
    "password": "bug",
    "submit_selector": "button[type=submit]",
    "success_markers": ["logout", "dashboard"]
  },
  "rationale": "establish an authenticated browser session"
}

Successful browser login syncs same-origin browser cookies into later HTTP tools. Cookie and localStorage observations expose names/keys only; raw values stay inside the runtime session.

{
  "action": "terminal_start",
  "kill_chain_stage": "controlled_exploitation",
  "args": {"session": "sqlmap1", "tool": "sqlmap", "path": "/search?q=test"},
  "rationale": "start a bounded interactive SQL test"
}

Then read or stop the session:

{"action":"terminal_read","args":{"session":"sqlmap1"},"rationale":"collect output"}
{"action":"terminal_stop","args":{"session":"sqlmap1"},"rationale":"close session"}

Approved terminal tools are allowlisted and scoped by the runtime. The current set includes nmap, whatweb, gobuster, dirb, ffuf, katana, nuclei, sqlmap, nikto, dig, ncat/nc, openssl s_client, and curl.

Tool Runtime Install Manager

Use ravage tools check to see which tools are available on the host and in the Docker tool image:

ravage tools check

Capability Preflight

Runs can require capabilities instead of a specific scanner binary. Ravage maps those capabilities to available host or Docker providers, writes the result to workspace/capabilities.json, and gives the same state to the AI agent before the first model action.

context:
  required_capabilities:
    - port_scan
    - dir_bruteforce
  optional_capabilities:
    - crawl
    - template_scan

Capability mapping:

If a required capability is missing, ravage attack and ravage scan fail before model spend. The report explains what is missing and includes install guidance. To intentionally continue without required tools:

ravage attack examples/labs/ravage-acme-box/brief.yaml --allow-degraded

For scan runs, check tools first with ravage tools check and either install the missing optional tool path or run a focused in-process probe selection. Optional missing tools are recorded as degraded capabilities, and unavailable tool observations include error_type, capability, fallbacks_available, and install_hint fields.

Installing Ravage from this source checkout gives you the ravage CLI. The Python install does not install external scanners such as nmap, gobuster, sqlmap, nikto, katana, or nuclei; those are host or Docker runtime tools.

Use scripts/install_tools.sh from a source checkout to preview or install external tools before running model-driven attacks:

scripts/install_tools.sh --method apt
scripts/install_tools.sh --method apt --execute
ravage tools check

From a source checkout, prefer the wrapper when you want the script to manage repo-local tool paths, state directories, installation, and verification:

scripts/install_tools.sh --method apt --execute
scripts/install_tools.sh --method docker --execute

The apt path installs apt-available tools such as nmap, whatweb, gobuster, dirb, nikto, and sqlmap, then installs Go-based tools such as ffuf, katana, and nuclei into .tools/bin. If Go is missing, the helper bootstraps it under .tools/go-root.

The install manager is a dry run by default. Add --execute only when you want Ravage to run the selected Docker, apt, or Homebrew commands:

scripts/install_tools.sh --method docker --execute

Install methods:

Tools need to be available wherever the Ravage process runs: the host shell, WSL, a Kali VM, or the Docker tool image. They do not need to be installed inside the vulnerable target VM unless Ravage itself is running there.

Host checks use RAVAGE_<TOOL>_BIN overrides first, then PATH. This covers custom install paths such as RAVAGE_NMAP_BIN=C:\Program Files\Nmap\nmap.exe on Windows. Source-checkout host checks also look in .tools/bin. After installing or setting overrides, run ravage tools check again before starting the attack.

Local Lab Boxes

For more realistic local targets, use the lab boxes under labs/. They are deliberately vulnerable applications with working websites, real services, seeded flags, and intended attack chains.

Do not deploy them outside a local authorized lab. They are research targets, not production services.

Available boxes:

ravage lab up ravage-acme-box

Then run the agent:

ravage attack examples/labs/ravage-acme-box/brief.yaml \
  --model-profile hosted-openai \
  --model-tier low \
  --tool-runtime auto \
  --tool-image ravage-kali:latest \
  --memory off \
  --allow-paid-models

The intended assisted chain is initial access with the seed account in OPERATOR_NOTES.md, invoice IDOR, catalog SQL injection, weak JWT admin escalation, and SSRF to the internal metadata service. The live website does not display credentials. The agent must still capture flags from live observations; the manifest and notes are operator context, not proof.

Stop the box when finished:

ravage lab down ravage-acme-box

For the harder Go box, substitute ravage-forgeops-box:

ravage lab up ravage-forgeops-box
ravage attack examples/labs/ravage-forgeops-box/brief.yaml \
  --model-profile hosted-openai \
  --model-tier low \
  --tool-runtime auto \
  --tool-image ravage-kali:latest \
  --memory off \
  --allow-paid-models
ravage lab down ravage-forgeops-box

For the Node market box, substitute ravage-node-market-box:

ravage lab up ravage-node-market-box
ravage attack examples/labs/ravage-node-market-box/brief.yaml \
  --model-profile hosted-openai \
  --model-tier low \
  --tool-runtime auto \
  --tool-image ravage-kali:latest \
  --memory off \
  --allow-paid-models
ravage lab down ravage-node-market-box

Coverage Strategy

ai-web uses retrieved memory and heuristic recommendations as hints, but coverage is still driven by tool evidence. A failed probe is classified by outcome: normal negative evidence closes that candidate for the current run, while authentication or privilege denial is tracked as blocked. When access state changes through a cookie, header, browser login, accepted default credential, or accepted JWT tamper, blocked candidates are released and can be retried under the new context.

Bounded multi-path probes, such as JWT tampering against several admin-like routes, mark every attempted path in the coverage ledger. This avoids spending later turns on the same failed variant family. JSON account, profile, preferences, and config routes are also eligible for a bounded business-logic probe that checks for mass-assignment or merge-style privilege changes, then verifies impact through separate routes before accepting evidence.

Local capture-flag labs also get bounded proof follow-ups after confirmed command injection or local file inclusion. Those follow-ups are read-only, run only against localhost targets, and use generic flag file names such as flag.txt rather than lab-specific paths. A flag is still accepted only when it appears in tool evidence.

Live Dashboard

For a local run, start the dashboard before or during the agent execution:

ravage dashboard \
  --workspace-dir runs/test-repo-ai-web.workspace \
  --db-path runs/test-repo-ai-web.db \
  --stdout-path runs/test-repo-ai-web.stdout \
  --lab-manifest examples/labs/ravage-acme-box/ravage-lab.yaml \
  --port 8787

Open http://127.0.0.1:8787. The page polls the local workspace and audit DB for the stage graph, active agents, run timeline, work charts, findings, masked flags, transcript, audit rows, terminal sessions, and stdout. It does not call the model or target by itself.

To attach to a conventional run directory:

ravage observe runs/brief-YYYYMMDDHHMMSS \
  --lab-manifest examples/labs/ravage-acme-box/ravage-lab.yaml

Artifacts

Single-run default artifacts:

Benchmark-run artifacts:

Resume A Run

Use --run-dir with --resume to continue an existing attack workspace. Without --resume, Ravage refuses to reuse an existing attack state:

ravage attack examples/labs/ravage-acme-box/brief.yaml \
  --run-dir runs/acme-attack \
  --resume \
  --model-profile hosted-openai \
  --model-tier low \
  --memory off \
  --allow-paid-models

Inspect report.json, agent.stdout, workspace/events.jsonl, and transcript.jsonl before resuming so the resumed run has a clear operator reason.

Inspect The Audit DB

List audit actions:

sqlite3 runs/test-repo-ai-web.db \
  'select id, actor, action from audit_log order by id;'

List findings:

sqlite3 runs/test-repo-ai-web.db \
  'select finding_id, vuln_class, status, validator_vote from findings;'

Pretty-print the latest finding payload:

sqlite3 -json runs/test-repo-ai-web.db \
  'select payload_json from findings limit 1;' | jq .

Inspect The Workspace

Show event kinds:

jq -r '.kind' .ravage/workspaces/<engagement_id>/events.jsonl

Show model actions:

jq 'select(.kind == "agent_action")' \
  .ravage/workspaces/<engagement_id>/events.jsonl

Show kill-chain progress:

jq 'select(.kind == "kill_chain_stage")' \
  .ravage/workspaces/<engagement_id>/events.jsonl

Show loaded skill metadata:

jq 'select(.kind == "agent_skill_loaded")' \
  .ravage/workspaces/<engagement_id>/events.jsonl

Show model replies:

jq 'select(.kind == "model_reply")' \
  .ravage/workspaces/<engagement_id>/events.jsonl

Show transcript roles:

jq -r '.role' .ravage/workspaces/<engagement_id>/transcript.jsonl

Show terminal sessions:

ls .ravage/workspaces/<engagement_id>/terminal

Inspect one terminal transcript:

jq . .ravage/workspaces/<engagement_id>/terminal/<session>.jsonl

Common Failures

connection refused

The model endpoint is not running or *_BASE_URL points to the wrong port.

invalid_model_action

The model returned prose or invalid JSON. The agent sends a correction observation and continues.

tool target escaped target origin

The model tried to call a URL outside the configured target origin. The tool is blocked and the model receives an error observation.

target must be localhost

The active CLI received a public/DNS target. Publish the authorized app to 127.0.0.1 or localhost, update scope.in_scope, and rerun.

finding_rejected_no_evidence

The model tried to report a vulnerability without confirmed proof. This is expected behavior and should not be relaxed.

Safety Boundaries

Do not remove these without an explicit design change and tests: