Zum Hauptinhalt springen

Befund-API

Jeder Scanner, der einen Merge blockieren kann, stellt seine Befunde über die REST-API bereit. Wenn ein Commit-Status „merge blocked" meldet, liest du hier die konkreten Befunde dahinter, und hier verwirfst du die, die du akzeptierst.

Das sind dieselben Daten wie unter /<org>/_admin/code-security in der Admin-Oberfläche; die Oberfläche ist ein Client dieser Endpunkte.

Die Pfadsegmente entsprechen nicht den Status-Namen

Das ist das Einzige, was du dir merken solltest. Der Kontext des Commit-Status und das Pfadsegment der API unterscheiden sich:

ScannerCommit-Status, den du am PR siehstAPI-Pfadsegment
Statische Analysefremforge/sastsast
Abhängigkeitenfremforge/dep-scandeps
Container-Imagesfremforge/image-scanimages
Lizenzenfremforge/license-scanlicenses

Wer das Segment aus dem Status-Namen errät, bekommt vom Catch-all der API 501 Not Implemented, was sich liest wie „dokumentiert, aber noch nicht ausgeliefert". Das stimmt nicht: Du hast die falsche URL.

Basis-URL

Die Produkt-API liegt unter /_app, nicht direkt unter der Domain (dort liegt die eigene API von Forgejo):

https://frem.sh/_app/api/v1/orgs/<org>/code-security/<scanner>/findings

Authentifiziere dich mit einem API-Token der Organisation oder einem PAT; die Token-Typen stehen in der API-Referenz.

Alle Befunde in einer Liste

Um alle offenen Befunde auf einmal zu lesen, egal von welchem Scanner, verwende die gemeinsame Liste. Sie ist die Liste unter Sicherheit › Befunde › 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: eine kommagetrennte Liste aus code, secrets, dependencies, images, iac, licences und malware. Standard sind alle. Befunde zu Infrastructure as Code sind hier ein eigener Typ, obwohl der Scan, der sie findet, der Image-Scan ist.
  • severity: eine kommagetrennte Liste aus critical, high, medium und low.
  • repo: ein Repository (owner/name) oder Image.
  • limit: 1–200, Standard 50. Für die nächste Seite übergib den zurückgegebenen next_cursor als cursor. Auf der letzten Seite ist er null.

Die Reihenfolge ist die der Konsole: zuerst bekanntermaßen ausgenutzte, dann nach Schweregrad, dann die ältesten, sodass der Befund, der seiner Behebungsfrist am nächsten ist, zuerst kommt. Die Schweregrade werden über die Scanner hinweg vergleichbar gemacht: Ein SAST-error ist high, eine warning medium und alles andere low; eine verbotene Lizenz ist critical, eine eingeschränkte high und jede andere low; Secrets und Malware sind immer critical. counts enthält die offenen Befunde pro Typ für die ganze Organisation, ohne die Filter. Erfordert findings:read. Ein unbekannter Typ oder Schweregrad wird mit 400 beantwortet.

  • branch: ein geschützter Release-Branch, zum Beispiel release/1.x, um die Befunde dieses Branches statt die des Standard-Branches aufzulisten. Jede Zeile enthält dann zusätzlich branch und fix_by. Ein Branch, der nicht gescannt wurde, antwortet mit 404 unknown-branch. GET /orgs/{org}/security/release-branches listet die gescannten auf.

Eine Zeile enthält, was die Liste zeigt. Für den vollständigen Befund (Erreichbarkeit, Triage, Abhängigkeitspfad) und zum Verwerfen verwende den Endpunkt des jeweiligen Scanners unten mit der id der Zeile.

Befunde auflisten

Standardmäßig nur offene Befunde:

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
}

Query-Parameter, für alle vier Scanner:

  • limit: 1–200, Standard 50.
  • offset: blättere mit offset += limit, bis has_more false ist.
  • include_dismissed: auf true setzen, um auch verworfene Befunde zu sehen.

Die Antwortfelder unterscheiden sich je nach Scanner, weil sich die Befunde unterscheiden: images liefert cve_id / package / fixed_version, deps ergänzt ecosystem / manifest_path / current_version und bei Befunden auf dem Standard-Branch via / dependency_path (welche direkte Abhängigkeit das Paket hereinbringt, siehe Abhängigkeitspfade), licenses liefert license_id / category, und sast liefert rule_id / path / line. Die OpenAPI-Spezifikation enthält das genaue Schema für jeden.

Zwei weitere Felder stammen aus Erreichbarkeit und Triage: Befunde von deps und images enthalten ein Objekt reachability und ein Objekt sla (die geltende Frist, mit angewendetem optionalem Multiplikator), und sast-Befunde enthalten ein Objekt triage (Fundstelle, Taint-Pfad, Verlauf der Verwerfungen und ob der Befund in der Gruppe mit geringerer Konfidenz liegt). deps und images akzeptieren außerdem ?reachability=, um nach Urteil zu filtern. Alle drei akzeptieren ?sort=risk, um in Triage-Reihenfolge aufzulisten: bei deps und images das Erreichbarkeitsurteil, dann Schweregrad, dann bekanntermaßen ausgenutzt, dann EPSS; bei sast die Triage-Signale (die Gruppe mit geringerer Konfidenz zuletzt, dann Schweregrad, dann ein Taint-Trace vor einem unbekannten oder rein musterbasierten Treffer, dann die ältesten zuerst). Das Blättern mit offset ist in beiden Reihenfolgen stabil. Die Standardreihenfolge ist neueste zuerst, bei Gleichstand nach ID.

Befunde von deps und images enthalten außerdem known_exploited: null oder { "cve_ids": [...], "sources": [...] }, wenn die Schwachstelle in der EU-Schwachstellendatenbank der ENISA (euvd) oder im KEV-Katalog der CISA (cisa_kev) als aktiv ausgenutzt gelistet ist. Ein Abhängigkeitsbefund wird über die CVE-Aliasse seiner Advisory zugeordnet, ein Image-Befund über seine CVE-ID; beide Listen werden täglich aktualisiert. Hänge ?known_exploited=true an, um nur diese Befunde aufzulisten, oder ?known_exploited=false für die übrigen; jeder andere Wert wird mit 400 beantwortet. Die Listen Abhängigkeiten und Container-Images in der Konsole haben denselben Filter, Nur bekannt ausgenutzte, und Sicherheitskorrekturen öffnet ihre Korrektur-Pull-Requests zuerst.

Migration nötig

Ein deps-Befund mit fixed_version gleich null enthält needs_migration, wenn seine Advisory nur in einem anderen Paket behoben ist, etwa unter einem neuen Go-Modulpfad oder einer neuen Major-Version (github.com/docker/docker zu github.com/moby/moby/v2) oder in einem umbenannten Paket (npm request zu @cypress/request). Andernfalls ist es null:

"fixed_version": null,
"needs_migration": { "package": "github.com/moby/moby/v2", "fixed_version": "2.0.0-beta.8" }

Für einen solchen Befund wird kein Korrektur-Pull-Request angeboten; siehe Migration nötig.

Wann ein Befund geschlossen wird

Befundtabellen werden nur ergänzt: Jeder Scan schreibt einen neuen Satz Zeilen unter einem neuen Scan-Lauf, und nichts wird gelöscht, wenn du das zugrunde liegende Problem behebst. Zwei Dinge schließen einen Befund.

Automatisch abgelöst. Ein stündlicher Job löst Befunde auf, deren Scan-Lauf nicht mehr der neueste für ihr Ziel ist, und markiert sie mit dismissed_by: auto-resolve. „Ziel" ist die natürliche Identität für jeden Scanner:

ScannerIdentität des Ziels
sastRepository + Dateipfad
deps, licensesRepository + Manifest-Pfad
imagesImage-Referenz

Die tatsächliche Regel lautet also „Hat der neueste Scan dieses Ziels ihn nicht erneut gemeldet, ist er weg", dieselbe Semantik, die OSV und Trivy verwenden. Behebe ihn, lass einen späteren Scan laufen, und er verschwindet innerhalb einer Stunde. Beachte, dass der Auslöser ein neuerer Scan desselben Ziels ist, nicht der Merge selbst: Scannt nichts dieses Ziel erneut, bleibt die Zeile unbegrenzt offen.

Dahinter steckt eine bewusste Sicherheitseigenschaft: Nur der neueste Scan-Lauf löst ab. Ein falsch konfigurierter Scan, der für ein Ziel nichts meldet, löscht die Befunde dieses Ziels nicht, sodass ein defekter Scanner dein Dashboard nicht stillschweigend leeren kann.

Ausdrücklich verworfen. Du verwirfst ihn, mit einer Begründung. Manuelle Verwerfungen bleiben beim automatischen Auflösen erhalten, sodass deine Begründung nie überschrieben wird.

Eine Verwerfung kann ablaufen. Gib ihr ein expires_at (in der API) oder ein Datum unter Läuft ab am (auf den Admin-Seiten), und die Akzeptanz erlischt von selbst: Ein täglicher Job öffnet den Befund wieder, sobald das Datum verstrichen ist, vermerkt im Audit-Log, wer ihn verworfen hatte und warum, und sendet bei einem hohen oder kritischen Befund dieselbe Sicherheitsbenachrichtigung wie bei einem neuen Befund. Ein Datum bedeutet bis einschließlich dieses Tages, endend um Mitternacht Europe/Copenhagen. Ohne Datum läuft eine Verwerfung nie ab; das ist der Standard und das Verhalten aller früheren Verwerfungen. Das Ablaufdatum wird mit der Verwerfung über spätere Scans hinweg mitgeführt, sodass ein erneuter Scan eine vorübergehende Akzeptanz nicht in eine dauerhafte verwandeln kann.

Was eine Verwerfung beim Image-Scan abdeckt. Ein verworfener Container-Image-Befund bleibt bei späteren Scans desselben Repositorys oder Images verworfen, aber nur für dieselbe CVE oder Regel in derselben Datei (das target des Befunds). Taucht dieselbe Regel in einer anderen Datei auf, auch in einer später hinzugefügten, ist das ein eigener Befund, der für sich blockiert. Wer also KSV-0014 auf einem Manifest verwirft, akzeptiert es nicht für das nächste Deployment, das jemand hinzufügt.

In der Praxis: Behebe an der Quelle und lass den nächsten Scan ablösen. Verwirf, wenn du den Befund akzeptierst oder wenn kein weiterer Scan dieses Ziels zu erwarten ist, zum Beispiel bei einem Befund, der an einem Branch hängt, der inzwischen gemergt ist und nicht mehr gescannt wird.

Einen Befund verwerfen

reason ist erforderlich, wird am Befund gespeichert und ins Audit-Log geschrieben. Schreib ihn für die Person, die ihn in sechs Monaten liest.

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…" }

Füge expires_at hinzu, um die Akzeptanz zu befristen: ein Datum ("2026-12-31", akzeptiert bis einschließlich dieses Tages) oder ein ISO-8601-Zeitstempel mit Zeitzone. Er muss in der Zukunft und innerhalb von drei Jahren liegen; alles andere ergibt 400 invalid-expires-at. Die Antwort gibt den gespeicherten Zeitpunkt zurück:

{ "dismissed": true, "expires_at": "2026-12-31T23:00:00.000Z", "id": "0f6c…" }

Ein 404 bedeutet, dass es keinen offenen Befund mit dieser ID gibt; meistens ist er bereits verworfen.

Um viele auf einmal zu schließen, akzeptieren sast, deps und images bis zu 1000 IDs pro Aufruf:

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 kennt nur das Verwerfen einzelner Befunde; dafür gibt es keinen Sammel-Endpunkt.

Lesen ist nicht Einspeisen

Zwei ähnlich aussehende Pfade erfüllen unterschiedliche Aufgaben:

  • /orgs/<org>/code-security/images/findings: das, was diese Seite beschreibt. Auf die Organisation bezogen, lesen und verwerfen.
  • /_app/api/v1/image-scans/findings: der Endpunkt zum Einspeisen, an den der Scanner-Runner seine Ergebnisse per POST schickt. Nicht zum Lesen gedacht.

Einstellungen

Der Blockierschwellenwert jedes Scanners ist ebenfalls ein API-Aufruf: PUT /orgs/<org>/code-security/<scanner>/settings. Beachte, dass images über block_action sperrt, während sast, deps und licenses über block_severity sperren; der Schwellenwert für Images ist der Eintrag image_scan der Scan-Richtlinie der Organisation (PUT /orgs/<org>/policy, siehe Blockierschwelle); licenses akzeptiert zusätzlich allowlisted_license_ids.

Für deps gibt es eine weitere, optionale Richtlinie: PUT /orgs/<org>/security/dep-scan-unreachable-policy ({"enabled": true, "repos": [...]}, Scope policy:write) verhindert, dass ein Pull-Request-Befund mit dem Erreichbarkeitsurteil nicht erreichbar fremforge/dep-scan blockiert. Sie ist standardmäßig aus; Befunde mit unknown, Malware und bekanntermaßen ausgenutzte Befunde blockieren immer, und ein falsches „nicht erreichbar" lässt eine echte Schwachstelle in den Merge durch. Siehe Erreichbarkeit: bei nicht erreichbaren Befunden nicht blockieren. Ein Befund, den sie durchgelassen hat, enthält in der obigen Liste block_waived: "unreachable".

Siehe auch