Triage through the REST API¶
The triage page describes the decisions in the web interface. This page gives the limits, the settings and the REST API calls.
Who may decide¶
Only a session (local or OIDC account, see user accounts)
with the findings.triage permission may change a status, raise a severity or comment: a
bearer token, even admin, gets 403. An agent only proposes a status with the MCP tool
propose_finding_status (see status of a finding).
Limits and settings¶
| Element | Limit | Setting |
|---|---|---|
| Reason of an acceptance | 10 to 1000 characters; optional for a false positive (10 to 1000 when given) | — |
| Expiry date | 90 days by default, at most SECREVIEW_MAX_DISMISSAL_DAYS (365) |
configuration |
| No expiry | Allowed for an acceptance unless SECREVIEW_PERMANENT_ACCEPTANCE_ENABLED=false; a false positive never expires |
configuration |
| Plain validation | Below SECREVIEW_RISK_ACCEPTANCE_MIN_SEVERITY (medium by default), an acceptance needs no reason and no date; empty variable: every acceptance needs them |
configuration |
| Justification of a severity | 10 to 1000 characters | — |
| Comment | At most 2000 characters, secrets masked | — |
Status of a finding¶
curl -X PATCH -H "Authorization: Bearer $SESSION" -H "Content-Type: application/json" \
https://secreview.example.com/api/v1/findings/42 \
-d '{"status": "accepted", "reason": "Internal tool, not exposed to the Internet", "expires_on": "2027-01-31"}'
# {"id": 42, "status": "accepted", "status_approver": "alice", "status_expires_at": "2027-01-31T00:00:00Z", …}
curl -X DELETE -H "Authorization: Bearer $SESSION" \
https://secreview.example.com/api/v1/findings/42/proposal # reject the agent proposal
Open and Fixed take an optional reason. A reason that is missing or too short, an
expiry date in the past or too far away, or an expiry date for open / fixed gets 422.
Without expires_on, a false positive never expires, nor does an acceptance, unless
SECREVIEW_PERMANENT_ACCEPTANCE_ENABLED=false (422); GET /api/v1/me returns
permanent_acceptance to tell whether the caller may do it. Below
SECREVIEW_RISK_ACCEPTANCE_MIN_SEVERITY, reason and expires_on are optional for
accepted ({"status": "accepted"} is enough); GET /api/v1/me returns this threshold as
risk_acceptance_min_severity (null: every acceptance needs them).
Finding reported again¶
The server recognizes an issue by its fingerprint: project, rule or CWE, file and title,
without the line number, as in SARIF. A finding of category dependency with a
rule_id ignores its title: an advisory (GHSA-, CVE-, PYSEC-...) is recognized by its id
alone, whatever the file (a scan reports it on the lockfile, a review on the manifest), and a
hygiene rule (DEP-*) by its id and the file name. When a rule matches several lines of a file,
the occurrences are paired in line order if their number is unchanged (the code moved), else
only on the same line: a new occurrence never inherits the decision taken on another one, it
is open. A pending agent proposal of an acceptance or a false positive is carried over too.
The previous occurrence can no longer be triaged (422) nor sent through a connector (409).
The trend and the time to remediate start at the first occurrence (first_seen_at).
save_security_report and the pipeline scans return recurring_findings (findings already
raised by a previous report) and carried_decisions (decisions carried to them).
Severity of a finding¶
curl -X PATCH -H "Authorization: Bearer $SESSION" -H "Content-Type: application/json" \
https://secreview.example.com/api/v1/findings/42/severity \
-d '{"severity": "critical", "reason": "Endpoint reachable without authentication"}'
# {"id": 42, "severity": "critical", "original_severity": "medium", "severity_changed_by": "alice", …}
A lower or unchanged severity, or a missing justification, gets 422. The Markdown export and
get_security_report show the severity of the report, the analyst, the date and the
justification; the change is written to the audit log (finding.severity_changed).
Risk acceptances and comments¶
curl -H "Authorization: Bearer $SESSION" \
"https://secreview.example.com/api/v1/risk-acceptances?project=shop-api"
# {"project": "shop-api", "total": 1, "acceptances": [{"id": 42, "status": "accepted", "expired": false, "comments_total": 1, …}]}
curl -H "Authorization: Bearer $SESSION" https://secreview.example.com/api/v1/findings/42/comments
# [{"id": 7, "finding_id": 42, "author": "alice", "body": "Renew it after the audit", "created_at": "…"}]
curl -X POST -H "Authorization: Bearer $SESSION" -H "Content-Type: application/json" \
https://secreview.example.com/api/v1/findings/42/comments -d '{"body": "Renew it after the audit"}'
curl -X PATCH -H "Authorization: Bearer $SESSION" -H "Content-Type: application/json" \
https://secreview.example.com/api/v1/findings/42 -d '{"status": "open"}' # remove the acceptance
GET /api/v1/risk-acceptances takes limit (capped by SECREVIEW_MAX_LIST_LIMIT) and
offset; without project, it lists every project the caller can read. The page is also
reached at /ui/risk-acceptances.
No route of the API edits or deletes a comment. Anyone who reads the project sees the comments;
each one is written to the audit log (finding.commented). Accepting a risk, or accepting it
again with another reason, adds the reason to the comments; changing the expiry date adds none.