ai-web is the first real model-driven ravage agent. It runs a loop:
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.
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.
ai-webRAVAGE_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
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.
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.
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:
objectives: ["web_application_assessment"]objectives: ["api_security_assessment"]objectives: ["xss"], ["jwt"], ["sql_injection"], or
another known class.capture_flag; use the challenge description and live
target evidence rather than vulnerability-class labels.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.
The model must emit exactly one JSON object per turn:
{
"action": "discover_attack_surface",
"kill_chain_stage": "reconnaissance",
"args": {},
"rationale": "map routes"
}
Supported actions:
discover_attack_surfaceinspect_sourcesource_guided_probehttp_requesthttp_gethttp_post_jsonform_postset_cookieset_headergraphql_requestxml_requestbrowser_openbrowser_fillbrowser_clickbrowser_submitbrowser_loginbrowser_extract_formsbrowser_snapshotbrowser_cookiesbrowser_local_storagenmap_scanwhatweb_scangobuster_dirdirb_dirffuf_dirkatana_crawlnuclei_scansqlmap_scannikto_scandns_lookupshodan_lookupterminal_startterminal_sendterminal_readterminal_stoptest_sqli_paramtest_xss_paramtest_ssti_paramtest_command_injection_paramtest_lfi_paramtest_idor_candidatetest_jwt_tampertest_xxe_endpointtest_ssrf_paramtest_file_uploadtest_nosqli_paramtest_default_credentialstest_race_conditionreport_sqlicapture_flagreport_findingfinalreport_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.
Use ravage tools check to see which tools are available on the host and in
the Docker tool image:
ravage tools check
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:
port_scan: nmap_scanweb_fingerprint: whatweb_scandir_bruteforce: ffuf_dir, gobuster_dir, or dirb_dircrawl: katana_crawltemplate_scan: nuclei_scansqlmap: sqlmap_scanweb_vuln_scan: nikto_scandns_lookup: dns_lookuptls_probe: terminal openssltcp_connect: terminal ncat or nchttp_client: terminal curlIf 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:
auto: chooses Docker first, then Homebrew on macOS, apt on Linux/Kali/WSL,
then manual guidance.docker: builds the ravage-kali:latest image. This is the most repeatable
path when Docker is available.apt: installs tools into the current Linux/Kali/WSL/VM environment.brew: installs tools into the current macOS shell.manual: prints PATH and RAVAGE_<TOOL>_BIN guidance without running an
installer.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.
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-acme-box: Python support portal with login, invoice IDOR, catalog
SQL injection, weak JWT admin escalation, SSRF, and four seeded flags.ravage-forgeops-box: harder Go release-operations portal with project BOLA,
audit query injection, weak JWT admin escalation, artifact traversal,
diagnostics command injection, SSRF, mass assignment, reflected input, and six
seeded flags.ravage-node-market-box: Node/Express market operations portal with order
BOLA, catalog SQL injection, JWT admin confusion, prototype-pollution-style
beta config abuse, internal admin pivot, and five seeded flags.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
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.
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
Single-run default artifacts:
runs/<engagement_id>.db or the --db-path valuereport.json next to workspace/ for ravage attack, or
inside the workspace for direct ai-web runs.ravage/workspaces/<engagement_id>/events.jsonltranscript.jsonlterminal/<session>.jsonlartifacts/*.txtBenchmark-run artifacts:
<output-dir>/report.json<output-dir>/<case-id>.db<output-dir>/<case-id>.workspace/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.
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 .
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
connection refusedThe model endpoint is not running or *_BASE_URL points to the wrong port.
invalid_model_actionThe model returned prose or invalid JSON. The agent sends a correction observation and continues.
tool target escaped target originThe 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 localhostThe 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_evidenceThe model tried to report a vulnerability without confirmed proof. This is expected behavior and should not be relaxed.
Do not remove these without an explicit design change and tests: