Findings-API
Elke scanner die een merge kan blokkeren, stelt zijn bevindingen beschikbaar via de REST API. Meldt een commitstatus “merge blocked”, dan lees je hier de concrete bevindingen die daarachter zitten, en negeer je hier de bevindingen die je accepteert.
Het zijn dezelfde gegevens als /<org>/_admin/code-security in de beheerinterface: die interface is zelf een client van deze endpoints.
De padsegmenten komen niet overeen met de statusnamen
Dit is het enige wat je echt moet onthouden. De context van de commitstatus en het padsegment in de API verschillen:
| Scanner | Commitstatus die je op de PR ziet | Padsegment in de API |
|---|---|---|
| Statische analyse | fremforge/sast | sast |
| Afhankelijkheden | fremforge/dep-scan | deps |
| Containerimages | fremforge/image-scan | images |
| Licenties | fremforge/license-scan | licenses |
Leid je het segment af van de statusnaam, dan krijg je 501 Not Implemented terug van de catch-all van de API, wat leest als “gedocumenteerd maar nog niet uitgerold”. Dat is het niet: je gebruikt de verkeerde URL.
Basis-URL
De product-API draait onder /_app, niet op het apexdomein (het apexdomein is de eigen API van Forgejo):
https://frem.sh/_app/api/v1/orgs/<org>/code-security/<scanner>/findingsAuthenticeer met een API-token van de organisatie of een PAT; zie de API-referentie voor de soorten tokens.
Alle bevindingen in één lijst
Wil je alle open bevindingen tegelijk lezen, ongeacht de scanner, gebruik dan de ene gezamenlijke lijst. Dat is de lijst onder Beveiliging › Bevindingen › Alle:
curl -sS \
-H "Authorization: Bearer ${FREMFORGE_TOKEN}" \
"https://frem.sh/_app/api/v1/orgs/acme/security/findings?severity=critical,high"{
"counts": { "code": 3, "secrets": 0, "dependencies": 41, "images": 12, "iac": 5, "licences": 2, "malware": 0 },
"items": [
{
"type": "dependencies",
"id": "0f6c…",
"severity": "critical",
"title": "GHSA-xxxx-xxxx-xxxx",
"subject": "acme/api",
"detail": "lodash@4.17.20",
"pr_number": null,
"known_exploited": true,
"first_seen_at": "2026-10-02T08:14:03.120Z"
}
],
"next_cursor": "eyJr…"
}type: een kommagescheiden lijst vancode,secrets,dependencies,images,iac,licencesenmalware. Standaard zijn dat ze allemaal. Bevindingen in infrastructure-as-code zijn hier een eigen type, ook al is het de imagescan die ze vindt.severity: een kommagescheiden lijst vancritical,high,mediumenlow.repo: één repository (owner/name) of image.limit: 1–200, standaard 50. Voor de volgende pagina geef je denext_cursordie je terugkreeg mee alscursor. Op de laatste pagina is dienull.
De volgorde is die van de console: eerst bekend misbruikte bevindingen, dan op ernst, dan de oudste, zodat de bevinding die het dichtst bij haar fix-by-datum zit bovenaan staat. Ernstniveaus zijn tussen scanners vergelijkbaar gemaakt: een SAST-error is high, een warning is medium en al het andere low; een verboden licentie is critical, een beperkte high en elke andere low; secrets en malware zijn altijd critical. counts bevat het aantal open bevindingen per type voor de hele organisatie, zonder de filters. Vereist findings:read. Een onbekend type of ernstniveau geeft 400.
branch: een beschermde releasebranch, bijvoorbeeldrelease/1.x, om de bevindingen van die branch te tonen in plaats van die van de standaardbranch. Elke rij bevat dan ookbranchenfix_by. Een branch die niet is gescand, geeft404 unknown-branch.GET /orgs/{org}/security/release-branchestoont de gescande branches.
Een rij bevat wat de lijst toont. Voor de volledige bevinding (bereikbaarheid, triage, afhankelijkheidspad) en om haar te negeren, gebruik je het eigen endpoint van de scanner hieronder met de id van de rij.
Bevindingen opvragen
Standaard alleen open bevindingen:
curl -sS \
-H "Authorization: Bearer ${FREMFORGE_TOKEN}" \
"https://frem.sh/_app/api/v1/orgs/acme/code-security/images/findings?limit=50"{
"findings": [
{
"id": "0f6c…",
"registry_image": "acme/api@9f2c…",
"cve_id": "CVE-2026-59950",
"package": "mcp",
"severity": "high",
"fixed_version": "1.28.1",
"dismissed_at": null,
"created_at": "2026-07-25T06:33:41.940Z"
}
],
"has_more": false
}Queryparameters, voor alle vier de scanners:
limit: 1–200, standaard 50.offset: blader metoffset += limittothas_morefalseis.include_dismissed: zet optrueom ook genegeerde bevindingen te zien.
De velden in het antwoord verschillen per scanner, omdat de bevindingen dat ook doen: images geeft cve_id / package / fixed_version, deps voegt ecosystem / manifest_path / current_version toe en, voor bevindingen op de standaardbranch, via / dependency_path (welke directe afhankelijkheid het pakket binnenhaalt, zie afhankelijkheidspaden), licenses geeft license_id / category, en sast geeft rule_id / path / line. De OpenAPI-specificatie bevat het exacte schema voor elk.
Twee extra velden komen uit bereikbaarheid en triage: bevindingen van deps en images bevatten een reachability-object en een sla-object (de geldende deadline, met de optionele vermenigvuldiger toegepast), en bevindingen van sast bevatten een triage-object (locatie, taint-pad, negeergeschiedenis en of de bevinding in de groep met lagere betrouwbaarheid valt). deps en images accepteren ook ?reachability= om op oordeel te filteren. Alle drie accepteren ?sort=risk om in triagevolgorde te tonen: voor deps en images het bereikbaarheidsoordeel, dan ernst, dan bekend misbruik, dan EPSS; voor sast de triagesignalen (de groep met lagere betrouwbaarheid als laatste, dan ernst, dan een taint-trace vóór een onbekende match of een match op alleen een patroon, dan de oudste eerst). Bladeren met offset is in beide volgordes stabiel. De standaardvolgorde is nieuwste eerst, bij gelijke stand op id.
Bevindingen van deps en images bevatten ook known_exploited: null, of { "cve_ids": [...], "sources": [...] } als de kwetsbaarheid als actief misbruikt staat vermeld in de EU Vulnerability Database van ENISA (euvd) of de KEV-catalogus van CISA (cisa_kev). Een afhankelijkheidsbevinding wordt gekoppeld via de CVE-aliassen van haar advisory, een imagebevinding via haar CVE-id; beide lijsten worden dagelijks ververst. Voeg ?known_exploited=true toe om alleen die bevindingen te tonen, of ?known_exploited=false voor de rest; elke andere waarde geeft 400. De lijsten Afhankelijkheden en Containerimages in de console hebben hetzelfde filter, Alleen bekend misbruikt, en Beveiligingsfixes opent hun fix-pull-requests als eerste.
Migratie nodig
Een deps-bevinding met een lege fixed_version bevat needs_migration wanneer haar advisory alleen onder een ander pakket is opgelost, zoals een nieuw Go-modulepad of nieuwe major (github.com/docker/docker naar github.com/moby/moby/v2) of een hernoemd pakket (npm request naar @cypress/request). Anders is het null:
"fixed_version": null,
"needs_migration": { "package": "github.com/moby/moby/v2", "fixed_version": "2.0.0-beta.8" }Voor zo’n bevinding wordt geen fix-pull-request aangeboden; zie Migratie nodig.
Wanneer een bevinding sluit
Bevindingentabellen zijn alleen-toevoegen: elke scan schrijft een nieuwe set rijen onder een nieuwe scanrun, en er wordt niets verwijderd als je het onderliggende probleem oplost. Twee dingen sluiten een bevinding.
Automatisch vervangen. Een taak die elk uur draait, lost bevindingen op waarvan de scanrun niet meer de laatste is voor hun doel, en markeert ze met dismissed_by: auto-resolve. Het “doel” is per scanner de natuurlijke identiteit:
| scanner | identiteit van het doel |
|---|---|
sast | repo + bestandspad |
deps, licenses | repo + manifestpad |
images | imagereferentie |
De regel komt dus neer op “als de meest recente scan van dat doel de bevinding niet opnieuw meldt, is ze weg”: dezelfde semantiek als die van OSV en Trivy. Los het op, laat een latere scan draaien, en de bevinding verdwijnt binnen het uur. Let op: de trigger is een nieuwere scan van hetzelfde doel, niet de merge zelf. Scant niets dat doel opnieuw, dan blijft de rij voor onbepaalde tijd open.
Hier zit een bewuste veiligheidseigenschap in: alleen de laatste scanrun vervangt. Een verkeerd geconfigureerde scan die voor een doel niets meldt, wist de bevindingen van dat doel niet, dus een kapotte scanner kan je dashboard niet stilletjes leegmaken.
Expliciet genegeerd. Je negeert de bevinding zelf, met een reden. Handmatig negeren blijft behouden bij de auto-resolve-ronde, dus je reden wordt nooit overschreven.
Negeren kan verlopen. Geef een expires_at op (in de API) of een datum bij Verloopt op (op de beheerpagina’s), en de acceptatie vervalt vanzelf: een dagelijkse taak heropent de bevinding zodra de datum voorbij is, legt in de auditlog vast wie haar had genegeerd en waarom, en stuurt bij een bevinding met ernst high of critical dezelfde beveiligingsmelding als bij een nieuwe bevinding. Een datum betekent tot en met die dag, eindigend om middernacht Europe/Copenhagen. Zonder datum verloopt negeren nooit; dat is de standaard en zo gedraagt elke eerdere negering zich. De vervaldatum gaat bij latere scans mee met de negering, zodat een nieuwe scan een tijdelijke acceptatie niet in een permanente kan veranderen.
Wat het negeren van een imagescanbevinding dekt. Een genegeerde bevinding in een containerimage blijft genegeerd in latere scans van dezelfde repository of hetzelfde image, maar alleen voor dezelfde CVE of regel in hetzelfde bestand (de target van de bevinding). Duikt dezelfde regel op in een ander bestand, ook een later toegevoegd bestand, dan is dat een aparte bevinding die zelfstandig blokkeert. Negeer je KSV-0014 in één manifest, dan accepteer je het dus niet voor de volgende Deployment die iemand toevoegt.
In de praktijk: los het op bij de bron en laat de volgende scan de bevinding vervangen. Negeer alleen als je de bevinding accepteert, of als er geen verdere scan van dat doel te verwachten is, bijvoorbeeld bij een bevinding die hoort bij een branch die inmiddels is samengevoegd en niet meer wordt gescand.
Een bevinding negeren
reason is verplicht, wordt bij de bevinding opgeslagen en in de auditlog geschreven. Schrijf hem voor wie hem over zes maanden leest.
curl -sS -X POST \
-H "Authorization: Bearer ${FREMFORGE_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"reason":"Fixed at source in acme/api#412; stale PR-scoped row."}' \
"https://frem.sh/_app/api/v1/orgs/acme/code-security/images/findings/0f6c…/dismiss"{ "dismissed": true, "expires_at": null, "id": "0f6c…" }Voeg expires_at toe om de acceptatie tijdelijk te maken: een datum ("2026-12-31", geaccepteerd tot en met die dag) of een ISO 8601-tijdstempel met tijdzone. Die moet in de toekomst liggen en binnen drie jaar; al het andere geeft een 400 invalid-expires-at. Het antwoord geeft het opgeslagen tijdstip terug:
{ "dismissed": true, "expires_at": "2026-12-31T23:00:00.000Z", "id": "0f6c…" }Een 404 betekent dat er geen open bevinding met dat id is; meestal is ze al genegeerd.
Om er veel tegelijk te sluiten, accepteren sast, deps en images tot 1000 id’s per aanroep:
curl -sS -X POST \
-H "Authorization: Bearer ${FREMFORGE_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"ids":["0f6c…","1a2b…"],"reason":"Accepted: vendored fixture, not shipped."}' \
"https://frem.sh/_app/api/v1/orgs/acme/code-security/deps/findings/bulk-dismiss"licenses kent alleen negeren per bevinding; er is geen bulk-endpoint voor.
Lezen is geen innemen
Twee paden die op elkaar lijken, doen verschillende dingen:
/orgs/<org>/code-security/images/findings: wat deze pagina beschrijft. Per organisatie, lezen en negeren./_app/api/v1/image-scans/findings: het ingest-endpoint waar de scanner-runner zijn resultaten naartoe POST. Niet bedoeld om te lezen.
Instellingen
De blokkeerdrempel van elke scanner is ook een API-aanroep: PUT /orgs/<org>/code-security/<scanner>/settings. Let op: images blokkeert op block_action, terwijl sast, deps en licenses blokkeren op block_severity; de drempel voor images is de image_scan-regel in het scanbeleid van de organisatie (PUT /orgs/<org>/policy, zie blokkeerdrempel); licenses accepteert daarnaast allowlisted_license_ids.
Voor deps is er nog één optioneel beleid: PUT /orgs/<org>/security/dep-scan-unreachable-policy ({"enabled": true, "repos": [...]}, scope policy:write) voorkomt dat een pull-request-bevinding met als bereikbaarheidsoordeel niet bereikbaar fremforge/dep-scan blokkeert. Het staat standaard uit; unknown, malware en bekend misbruikte bevindingen blokkeren altijd, en een onterecht “niet bereikbaar” laat een echte kwetsbaarheid door de merge. Zie Bereikbaarheid: niet blokkeren op niet-bereikbare bevindingen. Een bevinding die zo is doorgelaten, bevat block_waived: "unreachable" in de lijst hierboven.
Zie ook
- Bevindingen accepteren in de repository: de andere manier om een bevinding te accepteren, met een Trivy-ignorebestand dat naast de code wordt gecommit.
- Imagescanning: hoe images worden gescand en wat een merge blokkeert.
- Licentiescanning: beleidscategorieën en allowlists.
- API-referentie: authenticatie, tokens en de volledige OpenAPI-specificatie.