Tool reference
The tools are the deterministic half of locac. The model decides what to look at and whether a path is exploitable; the tools do the mechanical work of parsing, indexing, tracing and executing, and they are the only things that can produce evidence.
Every name below is valid in --tools and --exclude-tools. See
Running an audit for how filtering works.
Read-only
Always allowed, never approval-gated.
| Tool | What it does |
|---|---|
read | Read a UTF-8 text file as numbered lines. Pages large files with offset/limit |
ls | List a directory's entries: name, kind, size |
glob | Find files by glob pattern, as relative paths |
search | Regex search over file contents, ranked, returned as path:line:content. Searches everything by default, dependencies included |
outline | A source file's structure (functions, classes, methods) with line numbers, via tree-sitter, without dumping the file |
grok_sink | Given a symbol or API name, return its definitions and every call site in the repo, split production vs test |
scan_sinks | Sweep the repo for all known sinks of a vulnerability class: command injection, SQL injection, deserialization, path traversal, SSRF, code eval, memory safety, … |
trace_callers | Reverse-BFS up the call graph from a symbol toward entry points, grouping call sites by enclosing function |
trace_callees | Forward-BFS down the call graph from a symbol toward sinks, grouping the callees invoked inside each function - the downward mirror of trace_callers |
kb_search | Search the bundled security-knowledge base: what a weakness class is, how it shows up in code, how to verify it, what neutralises it |
code_search | Concept retrieval over the repo: hybrid BM25 plus TF-IDF vector retrieval, fused, with a security prior |
find | Router: classifies a raw query and dispatches it, a bare symbol to grok_sink and a concept phrase to code_search |
outline before read is the intended reflex: an outline of a 4,000-line file costs a fraction of
its text and usually answers the question.
Execution and mutation
These run code or write to disk in the target root, so every one of them is flagged dangerous and goes through the approval gate.
| Tool | What it does |
|---|---|
bash | Run a shell command in the sandboxed target root. Sequential |
execute | Think-in-code: run an analysis script over the target and get back only its stdout. One script replaces many read/search calls. Sandboxed, sequential |
edit | Replace exact text in an existing file, as one edit or a batch. Every old_string must appear exactly once |
write | Create a file, or replace one whole. Parent directories are created |
reproduce | Run a PoC against a structured vulnerability claim and get a deterministic verdict from the crash oracle: segfault, abort, ASan signal, or a sentinel match |
One more tool belongs to this story without belonging to this list:
| Tool | What it does |
|---|---|
coverage | Check whether a sink line executed, by parsing an lcov report your PoC already produced (coverage.py, c8, nyc, gcov, llvm-cov) |
coverage runs nothing. It reads a file your own instrumented run wrote, which is why it is not
dangerous and is allowed without an approval prompt.
Proof and diagnostics are not the same artifact
Every one of these tools records an auditable artifact. Only some of those artifacts are proof.
A proof artifact carries two extra things: a structured claim (which file and line, or which exact URL; which vulnerability class; what security effect the oracle must observe) and a machine-checked assertion that passed. Three tools can mint one:
| Tool | Mints proof when |
|---|---|
reproduce | Its claim is supplied and the oracle triggered - a native crash/sanitizer signal, or your sentinel appearing in the output |
run_module | A scanner module confirms a detection. One artifact per confirmed finding, carrying that module's payload, location and detector condition |
replay_request | You pass param + payload + claim + assertion, and the marker appears in the mutated response while being absent from the benign baseline |
Everything else is diagnostic, and says so in its own citation line:
executemints anexecartifact and nothing more. Exit zero proves the script ran; a nonzero exit may just be the script's own bug. It never evaluated an exploit assertion, so it cannot promote a severity - build the PoC underreproduceinstead.reproducethat did not trigger, andrun_modulethat confirmed nothing, are recorded so the attempt is on the record. A failed assertion is not a passed one.replay_requestwithoutclaim+assertionis a manual probe. So is one whose marker was already in the baseline: that is the response behaving normally, not the mutation causing it.coveragereaches a sink without crashing. That tells you the path is live, not that it is exploitable.
The claim also has to be about this finding. A perfectly real artifact minted for a different bug
does not transfer, which is why a High/Critical finding must carry a file (or an exact DAST URL)
and, for a source finding, a line. All four severity gates - record_finding, score_finding,
report_findings and cross_verify - route through the same predicate, so they cannot drift apart.
Findings and memory
| Tool | What it does |
|---|---|
record_finding | Record a finding, or an unconfirmed lead. CWE and CVSS are filled in later |
score_finding | Score a recorded finding from a CVSS 3.1 or 4.0 vector. Computes the base score and severity deterministically, and applies the severity gate |
report_findings | Render the session's findings, gated to the reporting bar (default: High and above) and sorted by severity |
scratchpad | Short-term per-session notes. Survives compaction. set / get / list / delete |
memory | Long-term scoped memory across sessions. set / get / list / search / delete |
recall | Search the full transcript, tool output compaction has elided out of the live context included, for ranked snippets, or fetch an exact seq |
Severity is computed, never asserted. score_finding takes a CVSS vector and derives the score and
severity from it, so a finding cannot be talked up a band.
recall is what makes elision safe: compaction removes old tool output from the live context, but
the transcript still holds it, and the agent can go back for it by search or by sequence number.
Along with report_findings it carries the same read-only flag the recon tools do, since both only read
stores locac itself wrote.
Orchestration
| Tool | What it does |
|---|---|
spawn_subagent | Launch a fresh-context child as a role: surface-mapper for read-only recon, exploit-verifier to independently reproduce a candidate. Returns an id. Findings it records land in this session |
wait_subagent | Block until a child finishes; returns its typed report plus status and turn count |
steer_subagent | Send guidance to a running child, picked up at its next decision point. A no-op once it has finished |
cross_verify | The 2-of-3 quorum: fan out three independent verifiers (reproduce / refute / boundary), tally, record the outcome |
switch_worktree | Relocate the session into an isolated git worktree at a chosen ref, to reproduce against a specific release without touching the main checkout. Ends the run; resume to continue there. Dangerous |
update_plan | Post or revise the working checklist, so you can see where the agent is. The whole plan is sent each time and replaces the previous one |
present_plan | Conclude the plan phase and submit a plan for approval. Ends the run |
ask_user | Put a structured question to the operator: up to four questions, each with 2-4 options and an optional multiSelect, plus an always-present free-text "Other" row. Orchestrator-only |
submit_report | A child's final typed report. Ends that child's run. Called exactly once |
A subagent cannot spawn subagents. spawn_subagent, wait_subagent and steer_subagent are never
in a child's tool set, and submit_report is always added to it. ask_user is orchestrator-only for
the same reason from the other side: a child runs with no operator to ask, so it is never in a role's
tool set either.
ask_user
ask_user is how the agent hands a decision back to the person running the audit rather than guessing:
which of several leads to chase first, whether a behaviour counts as in-scope, which credential set to
assume. In the TUI it renders as a modal that blocks the turn until answered, the same way the
dangerous-tool approval prompt does; the free-text "Other" row lets the operator type an answer none of
the options cover.
Run without an interactive terminal - piped, --print, a role child, CI - there is no operator to ask,
so it degrades deterministically: it auto-selects each question's first option, notes that it did, and
the run never stalls waiting on input that will not arrive.
The cross-verify quorum
cross_verify(finding_id) runs three verifiers with different lenses:
- reproduce. Build the PoC from scratch and run it.
- refute. Find the sanitiser that makes this a false positive.
- boundary. Confirm it fires in the default deployment.
A vote counts only if that verifier reproduced the bug itself during this round and cites proof
that clears four separate checks: minted during this round (a pre-existing artifact is a
receipt, not a verification), bound to the exact finding_id it was asked to vote on, matching
that finding's location, and distinct from what the other two lenses cited. Re-citing the
candidate's artifact, citing a fresh artifact about some other path, or three lenses sharing one
artifact all fail. Two of three must promote.
The finding_id requirement is why the verifiers are told the id, not just the location: a URL can
host several bug classes at once, and a claim naming only the URL would let one of them vouch for
another.
A High or Critical finding cannot be promoted or reported without a passing quorum, so this runs
before score_finding.
DAST
Present only when a run wires a DAST store. These send live requests.
| Tool | What it does |
|---|---|
crawl | Same-origin crawl of a target to map pages, query parameters and forms into the scan record. Run before run_module |
run_module | Run active scanner modules over the crawled record, or a single URL, to confirm reflected XSS, error-based SQLi, template injection (ssti, CWE-1336), time-based OS command injection (cmdi, CWE-78), open redirect and path traversal dynamically. Probes inject into query and form params, JSON body leaves, request headers and cookies |
replay_request | Re-send a recorded request, optionally mutating one parameter, and return the status plus a body snippet |
Naming a DAST tool in --tools on a run that has no DAST store is not an error. It is simply a
no-op there.
run_module is detection-scoped. It mints one artifact per confirmed detection rather than one
per scan, so "a scan ran here" can never stand in for "this parameter is injectable". Pass
finding_id to re-verify a DAST finding that already exists: the artifact is bound to that id only
when the URL and the class (CWE, or the title when there is no CWE) both match, and no duplicate
finding row is written. That is the call a cross-verify lens makes.
replay_request proves nothing on its own. With claim + assertion it becomes differential:
locac sends the benign request first, then the mutated one, and the assertion passes only if the
marker is absent from the baseline and present after the mutation. Both claim and assertion must
be supplied together, and both require param + payload - there is no baseline to compare
against otherwise.
The approval gate
| Category | Default decision |
|---|---|
| Read-only tools | Allow |
| Dangerous tools | Ask |
| Everything else | Allow |
The dangerous set is exactly: bash, execute, edit, write, reproduce, crawl,
run_module, replay_request, switch_worktree.
Resolution order for any one call: a per-tool rule (last match wins) beats the dangerous/read-only category, which beats the default.
What "ask" resolves to depends on the run:
| Situation | Result |
|---|---|
| TUI on a real terminal | An interactive y/n/a overlay |
--auto | Read-only shell commands auto-approved; everything else fail-closed |
--yes / -y / --allow-all | Approved |
Anything else (piped, --print, CI) | Denied, fail-closed |
In plan mode every dangerous tool is blocked with a reason the model can read, while read-only
tools, the findings tools and present_plan stay available.
Filtering
locac run "…" --tools "read,search,outline,code_search,trace_callers"
locac run "…" --exclude-tools "bash,execute"
--exclude-toolsis applied after--tools.- Both accept comma-separated names, which are trimmed, de-duplicated and sorted into a canonical form.
- The filter applies to every harness the run builds, role children included.
submit_reportcannot be removed by either flag, because a child has to be able to answer its parent.- An unknown name is a startup error, not a silent no-op:
error: unknown tool "raed" in --tools (known: bash, code_search, coverage, …)
Next
Providers and Environment variables cover what the model runs on; the Flag reference has the filtering flags in full.