Anatomy of a finding
The fields of a finding: what the agent writes, what is derived, and what is optional.
The agent records a finding with the add_finding tool call and can change it
later in the same run with update_finding. The application fills in some
fields itself. When the run completes, the findings are stored against the
engagement. After that, the only field that can change is status, which an
operator can set in the console; Triage describes it.
Fields¶
"Agent" means the model supplies the value in a tool call. "Application" means the code sets it and the model has no say.
| Field | Written by | Required | Notes |
|---|---|---|---|
id |
Application | — | A UUID assigned when the finding is built. |
engagement_id |
Application | — | The engagement the run belongs to. |
title |
Agent | Yes | Free text. |
description |
Agent | Yes | Free text. |
severity |
Agent | Yes | One of five values. update_finding can change it. See Severity and taxonomy. |
affected_asset |
Agent | Yes | Free text naming the host, URL or service. |
evidence |
Agent | Yes | A list of evidence items, described below. The list can be empty. |
remediation |
Agent | Yes | Free text advice. |
phase |
Application | — | The phase the run was in when the finding was added. |
status |
Agent, then operator | No | Defaults to unverified. update_finding can change it during the run, and an operator can change it in the console afterwards. |
cvss_score |
Agent | No | A number. update_finding can change it. |
cvss_vector |
Agent | No | A string. update_finding can change it. |
cves |
Agent | No | A list of strings. |
attack_technique_ids |
Agent | Asked for | Checked against the shipped ATT&CK manifest. A missing list is recorded as empty. |
attack_tactic_ids |
Application | — | Derived from the technique ids. |
wstg_ids |
Agent | No | A list of OWASP WSTG ids, or absent. |
nist_test_category |
Agent | Yes | One of four values. |
compliance_refs |
Report pipeline | — | Replaced every time a report is rendered. |
related_finding_ids |
Agent | No | Ids of other findings in the run. update_finding can add to it. |
created_at |
Application | — | The time the server stored the finding. |
An add_finding call that lacks a required field, or gives a severity, status
or test category outside its set of values, fails. The finding is not recorded,
and the failed call counts toward the run's limit of five consecutive failed
steps.
Severity and taxonomy covers severity, CVSS, CVE, the ATT&CK and WSTG ids and the test category. The rest are below.
Status¶
status takes one of four values: unverified, verified, false_positive,
not_exploitable. The agent sets it, in the add_finding call or in a later
update_finding call, and a finding whose status it never sets stays
unverified. After the run, an operator can change it (Triage).
The phase prompts ask the agent to use verified for a demonstrated exploit,
and false_positive or not_exploitable for findings that did not hold up;
Phases describes those prompts.
The code does not check the status against the evidence.
The status appears in the CSV status column, in the SARIF result's
properties.status, in the console's finding lists, and in the partner API's
findings. The PDF does not print it.
Reports do not filter by status
A finding marked false_positive or not_exploitable is rendered and
counted like any other. It appears in the PDF's findings, it counts toward
the executive summary's risk posture and severity counts, and it marks the
controls it maps to as tested. Filter on the status column of the CSV or
SARIF export if you need to separate them.
Triage¶
The console's Findings page lists every finding across the organization's engagements, most severe first. It filters by severity, status and engagement, and searches titles, affected assets and CVE ids. Expanding a finding on that page, or on an engagement's page, shows a Triage status control that sets the status to any of the four values. Any member of the organization can change it.
A change has these effects:
- The CSV and SARIF exports are built on every download, so the next download carries the new status. A file downloaded earlier keeps the old status.
- The partner API's findings return the new status.
- The PDF is unaffected, since it does not print the status.
- The evidence locker is unaffected. The sealed manifest records which findings an artifact belongs to, not their status, so changing a status after the seal neither breaks verification nor is recorded by it.
The change is not logged. The finding keeps no record of who changed its status, when, or what it was before.
Phase¶
The Phase enum has eight values: pre_engagement, intelligence,
threat_modeling, vuln_analysis, exploitation, post_exploitation,
reporting and complete. A finding carries one of the six in between. The
agent makes no tool calls in pre_engagement, which renders the
rules-of-engagement document and advances, or in complete, which ends the run.
update_finding does not change a finding's phase, so a finding added during
vuln_analysis and verified during exploitation still reads vuln_analysis.
Related findings¶
related_finding_ids lists the ids of other findings, usually the finding an
escalation step builds on. The agent supplies them in add_finding or appends
them with update_finding. An id that is not a well-formed UUID makes the call
fail. In update_finding it fails after the status, severity and CVSS changes
and the new evidence have been applied, the evidence captured as artifacts, and
the well-formed ids before it appended; those changes stay. The ids are not
checked against the run's findings when they are recorded.
The PDF's attack chain is built from these lists. It includes every finding whose list is not empty and follows only the ids that belong to a finding of the same run; an id that matches no finding in the run is dropped. The CSV and SARIF exports do not carry the field.
Control ids¶
compliance_refs holds the framework control ids a finding maps to. The agent
does not supply it, so it is stored empty. Every rendering of a report replaces
it with the ids the framework's tables give for the finding, in sorted order.
The value in any report is the value computed when that report was rendered.
A control id on a finding means the finding carried an ATT&CK or WSTG id, or
matched a keyword pattern, that the framework's tables map to that control. It
does not mean the control passed or failed.
What the result means explains what
each case tells you and what tested and not_tested mean.
Duplicates within a run¶
A finding's dedup key is the pair (title, affected_asset). When the agent adds
a finding whose title and affected asset both match, character for character, a
finding already recorded in the run, the new one is dropped. That check runs in
add_finding and nowhere else: not at storage, not at report time, and not
across runs.
The drop is silent. The agent is told the finding was recorded, and the dropped finding's evidence items are still captured into the evidence locker, linked to the id of a finding that is never stored.
Evidence items and locker artifacts¶
A finding's evidence list holds items the agent writes into the tool call.
An item has a kind, a text content, a timestamp and an optional command.
The kind is one of six values: command_output, screenshot, payload,
request, response, file. The items are stored with the finding, and the
PDF prints their content under the finding.
These are not the evidence locker's artifacts. When a finding is added, or when
update_finding supplies additional_evidence, the content of every item is
also captured as a locker artifact, and the six kinds collapse into four artifact
kinds. Artifacts describes that translation. The
artifact is hashed, chained and sealed; the inline item stored with the finding
is not. The PDF's Sealed evidence table under a finding, and the
artifact_sha256 values in the exports, list the artifacts linked to the finding
as the sealed manifest records them.
The command field has one further use: the first word of every command across
a run's evidence items makes up the report's Tools used list.
Last updated 2026-10-11