Documentation menu

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.

ToolWhat it does
readRead a UTF-8 text file as numbered lines. Pages large files with offset/limit
lsList a directory's entries: name, kind, size
globFind files by glob pattern, as relative paths
searchRegex search over file contents, ranked, returned as path:line:content. Searches everything by default, dependencies included
outlineA source file's structure (functions, classes, methods) with line numbers, via tree-sitter, without dumping the file
grok_sinkGiven a symbol or API name, return its definitions and every call site in the repo, split production vs test
scan_sinksSweep the repo for all known sinks of a vulnerability class: command injection, SQL injection, deserialization, path traversal, SSRF, code eval, memory safety, …
trace_callersReverse-BFS up the call graph from a symbol toward entry points, grouping call sites by enclosing function
trace_calleesForward-BFS down the call graph from a symbol toward sinks, grouping the callees invoked inside each function - the downward mirror of trace_callers
kb_searchSearch the bundled security-knowledge base: what a weakness class is, how it shows up in code, how to verify it, what neutralises it
code_searchConcept retrieval over the repo: hybrid BM25 plus TF-IDF vector retrieval, fused, with a security prior
findRouter: 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.

ToolWhat it does
bashRun a shell command in the sandboxed target root. Sequential
executeThink-in-code: run an analysis script over the target and get back only its stdout. One script replaces many read/search calls. Sandboxed, sequential
editReplace exact text in an existing file, as one edit or a batch. Every old_string must appear exactly once
writeCreate a file, or replace one whole. Parent directories are created
reproduceRun 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:

ToolWhat it does
coverageCheck 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:

ToolMints proof when
reproduceIts claim is supplied and the oracle triggered - a native crash/sanitizer signal, or your sentinel appearing in the output
run_moduleA scanner module confirms a detection. One artifact per confirmed finding, carrying that module's payload, location and detector condition
replay_requestYou 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:

  • execute mints an exec artifact 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 under reproduce instead.
  • reproduce that did not trigger, and run_module that confirmed nothing, are recorded so the attempt is on the record. A failed assertion is not a passed one.
  • replay_request without claim + assertion is a manual probe. So is one whose marker was already in the baseline: that is the response behaving normally, not the mutation causing it.
  • coverage reaches 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

ToolWhat it does
record_findingRecord a finding, or an unconfirmed lead. CWE and CVSS are filled in later
score_findingScore 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_findingsRender the session's findings, gated to the reporting bar (default: High and above) and sorted by severity
scratchpadShort-term per-session notes. Survives compaction. set / get / list / delete
memoryLong-term scoped memory across sessions. set / get / list / search / delete
recallSearch 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

ToolWhat it does
spawn_subagentLaunch 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_subagentBlock until a child finishes; returns its typed report plus status and turn count
steer_subagentSend guidance to a running child, picked up at its next decision point. A no-op once it has finished
cross_verifyThe 2-of-3 quorum: fan out three independent verifiers (reproduce / refute / boundary), tally, record the outcome
switch_worktreeRelocate 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_planPost 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_planConclude the plan phase and submit a plan for approval. Ends the run
ask_userPut 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_reportA 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.

ToolWhat it does
crawlSame-origin crawl of a target to map pages, query parameters and forms into the scan record. Run before run_module
run_moduleRun 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_requestRe-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

CategoryDefault decision
Read-only toolsAllow
Dangerous toolsAsk
Everything elseAllow

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:

SituationResult
TUI on a real terminalAn interactive y/n/a overlay
--autoRead-only shell commands auto-approved; everything else fail-closed
--yes / -y / --allow-allApproved
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-tools is 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_report cannot 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.