Jump into setup guides, feature references, reporting conventions, API documentation, methodology pages, and workflow-specific playbooks.
Code security
Findings register
Finding lifecycle, severity, verification, and comments.
Findings, reports, dashboards, exports, integrations, and retests all read from the same normalized record.
Pencheff favors repeatable checks, then uses AI for triage, enrichment, orchestration, and remediation where it adds signal.
From the Pencheff docs
Findings API
/reference/findingsGET /scans/{scan_id}/findings
Return every finding for a scan. Filter with query params:
?severity=critical?category=injection?owasp_category=A03?verified_only=true?include_suppressed=true?sort=risk_score(default; usecvss_scoreorcreated_at)
GET /findings/{id}
Fetch a single finding with full evidence, comments, assignments, tags.
PATCH /findings/{id}
Update status. Valid fields:
{
"verification_status": "true_positive" | "false_positive" | "true_negative" | "false_negative",
"suppressed": true,
"suppress_reason": "accepted_risk" | "wont_fix" | "false_positive" | "duplicate" | "out_of_scope",
"suppress_notes": "string",
"resolved_at": "2026-04-21T…Z",
"sla_days": 7
}
Collaboration
POST /findings/{id}/comments— add a commentGET /findings/{id}/comments— list commentsPOST /findings/{id}/assign—{"assignee_user_id": "..."}POST /findings/{id}/tags—{"tag": "p0-fix"}DELETE /findings/{id}/tags/{tag}— remove a tag
Prioritisation fields
Every Finding includes the unified prioritisation surface:
| Field | Type | Source |
|---|---|---|
risk_score | float (0–100) | computed at insert from CVSS × EPSS × KEV × SSVC × reachability |
ssvc_decision | string | one of act, attend, track_star, track |
reachability | string | one of exploited, reachable, present, unknown — see Reachability classifier |
epss | float (0–1) | null | EPSS feed; populated for SCA findings |
kev | bool | CISA KEV catalog membership |
Sort the list endpoint by risk_score:
GET /scans/{scan_id}/findings?sort=risk_score (default)
The unified, cross-table queue lives at
/unified-findings.
POST /findings/{id}/propose_fix
Generates a draft FixProposal for the finding. SCA findings get a
deterministic version-bump diff; SAST/DAST findings synthesise a unified
diff via the operator-configured patch-synthesis backend. See
Auto-fix PRs.
The route accepts kind ∈ {sast, dast}; SCA findings ride the
dast kind and Pencheff detects the SCA payload from evidence and
routes internally.
POST /findings/{id}/triage
Pro tier. Triage 2.0 — exploitability walkthrough returning
{ walkthrough, blast_radius, exploit_scenario, fix_outline, confidence }. Cached on finding.ai_triage; pass ?force=true to
regenerate. See Triage 2.0.
From the Pencheff docs
Unified findings stream
/features/findings-streamA single sortable, filterable queue across SAST, DAST, SCA, IaC, and secrets — no more "click into a scan, then its findings" friction when you just want to know "what should I fix first?"
Lives at /findings in the
dashboard.
What it does
- Pulls from both the
findingstable (DAST + SCA from live scans) andrepo_findingstable (SAST + SCA + secrets + IaC from repo scans). - Projects them to a common shape and merges.
- Sorts by Pencheff's unified
risk_score(CVSS × EPSS × KEV × SSVC × reachability) —NULLlast, then severity, then created_at. - Paginates with stable order across pages.
Filters
The dashboard's filter chips translate one-to-one to query params:
GET /unified-findings
?source=sast # also: dast, sca, iac, secret (multi-select)
&severity=critical # critical | high | medium | low | info
&reachability=exploited
&include_suppressed=false
&target_id=<uuid> # restrict to one target
&limit=50&offset=0
Server-side filters fire before the merge, so paginated results stay consistent.
Source mapping
Pencheff projects each underlying row to a single source label so
filters work uniformly:
source | From findings rows where… | From repo_findings rows where… |
|---|---|---|
dast | category != "components" | — |
sca | category == "components" | scanner ∈ osv, ghsa, pip-audit, npm-audit |
sast | — | scanner ∈ semgrep, bandit, gosec, brakeman, eslint, treesitter:*, ruff. (Legacy codeql rows from pre-v0.7 scans still classify as SAST.) |
secret | — | scanner ∈ gitleaks, detect-secrets |
iac | — | scanner ∈ trivy_iac, checkov |
Drill-through
Each row in the list is a hyperlink. The unified item carries enough metadata for the dashboard to deep-link:
findings-table rows →/findings/<id>→ redirector that resolves thescan_idand forwards to the scan-scoped detail page.repo_findings-table rows →/repos/...(the existing repo finding detail page).
Risk-first by default
The default sort is descending risk_score. A KEV-listed CVSS 6.0
beats an unexploited CVSS 8.5 — that's what Snyk gets wrong by sorting
on CVSS alone. See EPSS, KEV, SSVC for the full
priority formula.
What's tested
cd apps/api && uv run pytest tests/test_unified_findings.py
12 unit tests cover the projection logic + sort key (risk_score desc, severity tiebreaker, recency tiebreaker) + ecosystem-aware source mapping.
Related