API til fund
Hver scanner, der kan blokere en merge, stiller sine fund til rådighed via REST API’et. Når en commit-status siger “merge blocked”, er det her, du læser de konkrete fund bag den, og her, du afviser dem, du accepterer.
Det er de samme data som /<org>/_admin/code-security i administrationsbrugerfladen: brugerfladen er selv en klient af disse endpoints.
Stisegmenterne svarer ikke til statusnavnene
Det er den ene ting, der er værd at huske. Konteksten i commit-statussen og segmentet i API-stien er forskellige:
| Scanner | Commit-status, du ser på pull requesten | Segment i API-stien |
|---|---|---|
| Statisk analyse | fremforge/sast | sast |
| Afhængigheder | fremforge/dep-scan | deps |
| Container-images | fremforge/image-scan | images |
| Licenser | fremforge/license-scan | licenses |
Gætter du segmentet ud fra statusnavnet, svarer API’ets catch-all med 501 Not Implemented, som læses som “dokumenteret, men endnu ikke leveret”. Det er det ikke: du har bare den forkerte URL.
Basis-URL
Produktets API ligger under /_app, ikke på apex-domænet (apex er Forgejos eget API):
https://frem.sh/_app/api/v1/orgs/<org>/code-security/<scanner>/findingsGodkend dig med et API-token for organisationen eller et PAT. Se API-referencen for tokentyperne.
Alle fund i én liste
Vil du læse alle åbne fund på én gang, uanset scanner, så brug den samlede liste. Det er listen under Sikkerhed › Fund › 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: en kommasepareret liste afcode,secrets,dependencies,images,iac,licencesogmalware. Standard er dem alle. Fund i infrastructure-as-code er deres egen type her, selv om det er image-scanningen, der finder dem.severity: en kommasepareret liste afcritical,high,mediumoglow.repo: ét repository (owner/name) eller image.limit: 1–200, standard 50. Til næste side sender du dennext_cursor, du fik tilbage, somcursor. Den ernullpå sidste side.
Rækkefølgen er konsollens: kendt udnyttede først, derefter alvorlighed, derefter de ældste, så det fund, der er tættest på sin frist for rettelse, kommer først. Alvorlighederne er gjort sammenlignelige på tværs af scannere: en SAST-error er high, en warning er medium, og alt andet er low; en forbudt licens er critical, en begrænset er high, og alle andre er low; secrets og malware er altid critical. counts indeholder de åbne fund pr. type for hele organisationen, uden filtrene. Kræver findings:read. En ukendt type eller alvorlighed giver 400.
branch: en beskyttet release-branch, for eksempelrelease/1.x, for at liste den branchs fund i stedet for standardbranchens. Hver række har så ogsåbranchogfix_by. En branch, der ikke er blevet scannet, giver404 unknown-branch.GET /orgs/{org}/security/release-brancheslister de scannede.
En række indeholder det, listen viser. For det fulde fund (reachability, triage, afhængighedssti) og for at afvise det skal du bruge scannerens eget endpoint nedenfor med rækkens id.
List fund
Som standard kun åbne fund:
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
}Forespørgselsparametre for alle fire scannere:
limit: 1–200, standard 50.offset: bladr medoffset += limit, indtilhas_moreerfalse.include_dismissed: sæt tiltruefor også at se afviste fund.
Felterne i svaret er forskellige fra scanner til scanner, fordi fundene er det: images returnerer cve_id / package / fixed_version, deps tilføjer ecosystem / manifest_path / current_version og, for fund på standardbranchen, via / dependency_path (hvilken direkte afhængighed der trækker pakken ind, se afhængighedsstier), licenses returnerer license_id / category, og sast returnerer rule_id / path / line. OpenAPI-specifikationen har det præcise skema for hver.
To felter mere kommer fra reachability og triage: fund fra deps og images har et reachability-objekt og et sla-objekt (den gældende frist, med opt-in-multiplikatoren anvendt), og fund fra sast har et triage-objekt (placering, taint-sti, historik over afvisninger og om fundet hører til gruppen med lavere sikkerhed). deps og images tager også ?reachability= til at filtrere efter vurdering. Alle tre tager ?sort=risk for at liste i triage-rækkefølge: for deps og images reachability-vurderingen, derefter alvorlighed, derefter kendt udnyttet, derefter EPSS; for sast triage-signalerne (gruppen med lavere sikkerhed sidst, derefter alvorlighed, derefter et taint-spor før et ukendt eller rent mønsterbaseret match, derefter de ældste først). Bladring med offset er stabil i begge rækkefølger. Standardrækkefølgen er nyeste først, med id som tiebreaker.
Fund fra deps og images har også known_exploited: null eller { "cve_ids": [...], "sources": [...] }, når sårbarheden er opført som udnyttet i praksis i ENISA’s EU Vulnerability Database (euvd) eller CISA’s KEV-katalog (cisa_kev). Et afhængighedsfund matches via CVE-aliasserne i dets advisory, et image-fund via dets CVE-id; begge lister opdateres dagligt. Tilføj ?known_exploited=true for kun at liste de fund, eller ?known_exploited=false for resten; enhver anden værdi giver 400. Konsollens lister over afhængigheder og container-images har samme filter, Kun kendt udnyttede, og Sikkerhedsrettelser åbner deres pull requests med rettelser først.
Kræver migrering
Et deps-fund med fixed_version sat til null har needs_migration, når dets advisory kun er rettet under en anden pakke, for eksempel en ny Go-modulsti eller major-version (github.com/docker/docker til github.com/moby/moby/v2) eller en omdøbt pakke (npm request til @cypress/request). Ellers er det null:
"fixed_version": null,
"needs_migration": { "package": "github.com/moby/moby/v2", "fixed_version": "2.0.0-beta.8" }Der tilbydes ingen pull request med en rettelse for sådan et fund; se Kræver migrering.
Hvornår et fund lukkes
Tabellerne med fund er kun til indsættelse: hver scanning skriver et nyt sæt rækker under en ny scanningskørsel, og intet slettes, når du retter det underliggende problem. To ting lukker et fund.
Automatisk afløst. Et job, der kører hver time, lukker fund, hvis scanningskørsel ikke længere er den seneste for deres mål, og markerer dem dismissed_by: auto-resolve. “Mål” er den naturlige identitet for hver scanner:
| Scanner | Målets identitet |
|---|---|
sast | repo + filsti |
deps, licenses | repo + manifeststi |
images | image-reference |
Den reelle regel er altså “hvis den seneste scanning af det mål ikke rapporterede fundet igen, er det væk”: samme semantik, som OSV og Trivy bruger. Ret det, lad en senere scanning køre, og det forsvinder inden for en time. Bemærk, at udløseren er en nyere scanning af samme mål, ikke selve mergen: hvis intet scanner målet igen, forbliver rækken åben på ubestemt tid.
Der er en bevidst sikkerhedsegenskab her: kun den seneste scanningskørsel afløser. En fejlkonfigureret scanning, der ikke rapporterer noget for et mål, sletter ikke målets fund, så en defekt scanner kan ikke i stilhed tømme dit dashboard.
Afvist eksplicit. Du afviser det med en begrundelse. Manuelle afvisninger bevares af auto-resolve-kørslen, så din begrundelse bliver aldrig overskrevet.
En afvisning kan udløbe. Giv den en expires_at (API’et) eller en dato i Udløber den (administrationssiderne), så bortfalder accepten af sig selv: et dagligt job genåbner fundet, når datoen er passeret, registrerer i revisionsloggen, hvem der afviste det og hvorfor, og sender, for et fund med høj eller kritisk alvorlighed, den samme sikkerhedsadvarsel, som et nyt fund får. En dato betyder til og med den dag og slutter ved midnat Europe/Copenhagen. Uden dato udløber en afvisning aldrig; det er standarden og den måde, alle tidligere afvisninger opfører sig på. Udløbet følger afvisningen videre gennem senere scanninger, så en ny scanning ikke kan gøre en midlertidig accept permanent.
Hvad en afvisning i image-scanningen dækker. Et afvist fund i et container-image forbliver afvist i senere scanninger af samme repository eller image, men kun for samme CVE eller regel i samme fil (fundets target). Dukker samme regel op i en anden fil, også en fil tilføjet senere, er det et separat fund, og det blokerer for sig selv. At afvise KSV-0014 på ét manifest accepterer det altså ikke for den næste Deployment, nogen tilføjer.
I praksis: ret ved kilden, og lad den næste scanning afløse fundet. Afvis, når du accepterer fundet, eller når der ikke forventes flere scanninger af målet, for eksempel et fund knyttet til en branch, der siden er merget og ikke længere scannes.
Afvis et fund
reason er påkrævet, gemmes på fundet og skrives i revisionsloggen. Skriv den til den, der læser den om seks måneder.
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…" }Tilføj expires_at for at gøre accepten midlertidig: en dato ("2026-12-31", accepteret til og med den dag) eller et ISO 8601-tidsstempel med tidszone. Den skal ligge i fremtiden og inden for tre år; alt andet giver 400 invalid-expires-at. Svaret gentager det gemte tidspunkt:
{ "dismissed": true, "expires_at": "2026-12-31T23:00:00.000Z", "id": "0f6c…" }En 404 betyder, at der ikke findes et åbent fund med det id; oftest er det allerede afvist.
For at lukke mange på én gang accepterer sast, deps og images op til 1000 id’er pr. kald:
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 understøtter kun afvisning af ét fund ad gangen: der er intet endpoint til masseafvisning.
At læse er ikke at indlæse
To stier, der ligner hinanden, har forskellige opgaver:
/orgs/<org>/code-security/images/findings: det, denne side beskriver. Afgrænset til organisationen, til læsning og afvisning./_app/api/v1/image-scans/findings: det indlæsnings-endpoint, som scannerens runner POST’er resultater til. Ikke til læsning.
Indstillinger
Hver scanners blokeringstærskel er også et API-kald: PUT /orgs/<org>/code-security/<scanner>/settings. Bemærk, at images blokerer på block_action, mens sast, deps og licenses blokerer på block_severity; tærsklen for images er image_scan-posten i organisationens scanningspolitik (PUT /orgs/<org>/policy, se blokeringstærskel); licenses tager desuden allowlisted_license_ids.
For deps er der endnu en politik, som du selv skal slå til: PUT /orgs/<org>/security/dep-scan-unreachable-policy ({"enabled": true, "repos": [...]}, scope policy:write) forhindrer, at et fund i en pull request, hvis reachability-vurdering er ikke nåbar, blokerer fremforge/dep-scan. Den er slået fra som standard; unknown, malware og kendt udnyttede fund blokerer altid, og en forkert “ikke nåbar”-vurdering lader en reel sårbarhed blive merget. Se Reachability: bloker ikke på ikke-nåbare fund. Et fund, den har sluppet igennem, har block_waived: "unreachable" i listen ovenfor.
Se også
- Accept af fund i repoet: den anden måde at acceptere et fund på, nemlig en Trivy-ignore-fil, der committes sammen med koden.
- Image-scanning: hvordan images scannes, og hvad der blokerer en merge.
- Licensscanning: politikkategorier og tilladelseslister.
- API-reference: godkendelse, tokens og den fulde OpenAPI-specifikation.