Zum Hauptinhalt springen

Sicherheit und Lieferkette

fremforge liefert in jedem Nutzer-Tarif einen mehrschichtigen Sicherheits-Stack für die Lieferkette mit. Die Kontrollen greifen an vier Engstellen: beim Push (Secret-Scanning), im Pull Request (Abhängigkeits-Scanning, SAST), am Artefakt (Container-Scanning, SBOM, SLSA-Provenienz) und beim Commit (Signierung per SSH-Schlüssel oder GPG).

Zur vertraglichen Einordnung (BSI C5, ISO 27001/27017/27018, TISAX, DSGVO, die Analyse zu Schrems II und CLOUD Act) siehe frem.sh/trust.

Wo Befunde aufgeführt sind

Sicherheit › Befunde öffnet mit Alle: jeder offene Befund jedes Scanners in einer Liste, die wichtigsten zuerst (bekanntermaßen ausgenutzte, dann nach Schweregrad, dann die ältesten). Filtere nach Typ und Schweregrad; jeder Chip zeigt, wie viele Befunde er auflisten würde. Eine Zeile verlinkt auf den Befund in seinem eigenen Tab (SAST, Secrets, Abhängigkeiten, Container-Images, Lizenzen, Malware), wo seine Details und Aktionen stehen. Dieselbe Liste gibt es in der API als GET /orgs/{org}/security/findings; siehe Befund-API.

Secret-Scanning (Push-Schutz)

Gitleaks läuft als Pre-Receive-Hook bei jedem Push, bevor der Commit ins Repository übernommen wird, schon ab dem allerersten Push eines neuen Repositorys. Der Push-Schutz ist immer eingeschaltet: Keine Organisation und kein Repository kann den Scan abschalten. Die einzige Einstellung ist das Fehlerverhalten des Scanners unter Sicherheit › Richtlinien › Push-Schutz: Ist der Scanner selbst nicht verfügbar, wird ein Push entweder abgewiesen (der Standard) oder durchgelassen und als Allow-on-Error-Ereignis im Audit-Trail erfasst; Secrets, die in diesem Zeitfenster durchkommen, findet später der wöchentliche Durchlauf über die Historie. Siehe Push-Schutz.

Was blockiert wird

Gitleaks erkennt mehr als 120 Muster mit hoher Trefferzuverlässigkeit, darunter:

KategorieBeispielmuster
Zugangsdaten von Cloud-AnbieternAWS-Access- und Secret-Keys, JSON von GCP-Servicekonten, Verbindungsstrings für Azure Storage
Tokens von VersionsverwaltungenGitHub-PATs, GitLab-PATs, App-Passwörter von Bitbucket
ZahlungsschlüsselSecret Keys von Stripe, Signatur-Secrets für Stripe-Webhooks
Kommunikations-TokensSlack-Bot-Tokens, Slack-Webhook-URLs, Auth-Tokens von Twilio
Private SchlüsselPrivate RSA-, EC- und Ed25519-Schlüssel, PEM-kodierte Zertifikate
Generische SecretsStrings mit hoher Entropie, die auf das Muster generic-api-key passen, JWT-Signatur-Secrets
Datenbank-URLsVerbindungsstrings für PostgreSQL, MySQL, MongoDB und Redis mit eingebetteten Zugangsdaten

Wenn ein Push blockiert wird

Der Push wird auf dem Remote mit einer Fehlermeldung abgewiesen, die den Typ des erkannten Musters sowie Datei und Zeilennummer nennt. Der Commit wird nicht übernommen.

Lösche nicht einfach die Datei und committe erneut. Das Secret steckt in der Git-Historie und wird wieder blockiert. Die richtige Behebung:

  1. Rotiere das Secret sofort beim ausstellenden Dienst (AWS IAM, Stripe-Dashboard usw.). Geh davon aus, dass es kompromittiert ist.
  2. Schreibe die Historie um, um das Secret aus jedem betroffenen Commit zu entfernen:
# Install git-filter-repo (https://github.com/newren/git-filter-repo)
pip install git-filter-repo

# Create a replacements file
echo '<secret-value>==>REDACTED' > replacements.txt

# Rewrite history
git filter-repo --replace-text replacements.txt

# Force-push the cleaned branch
git push --force-with-lease origin <branch>
  1. Informiere alle, die das Repository vor dem Umschreiben geklont haben könnten.

Ablauf einer Ausnahme

Ist ein blockiertes Secret beabsichtigt (Token für internes Tooling, veröffentlichte Test-Zugangsdaten), kann ein Owner der Organisation eine eingegrenzte Ausnahme anlegen:

  1. Öffne Sicherheit › Richtlinien › Push-Schutz › Aktive Ausnahmen → Neue Ausnahme.
  2. Gib an: Repository (oder wähle „All repos“), Secret-Muster (aus der Regel-ID von Gitleaks) und Begründung (Freitext, wird im Audit-Trail protokolliert).
  3. Speichere. Die Ausnahme gilt ab dem nächsten Push.

Alle Ausnahmen sind für die anderen Owner der Organisation sichtbar und erscheinen im Audit-Log mit Akteur, Zeitstempel und Begründung. Ausnahmen lassen sich jederzeit auf derselben Seite widerrufen.

Eine Push-Ablehnung übersteuern

Wird ein Push vom Secret-Scanning abgewiesen, sieht der Entwickler den Grund in der Terminalausgabe: den Typ des erkannten Musters, den Dateipfad und die Zeilennummer.

Ein Owner der Organisation kann unter Sicherheit › Richtlinien › Push-Schutz › Aktuelle Ablehnungen eine Ausnahme freigeben. Jeder Eintrag zeigt: das Repository, den Committer, den Zeitstempel, den erkannten Secret-Typ und den betroffenen Dateipfad.

Geltungsbereiche einer Ausnahme (bei der Freigabe auszuwählen):

  • This commit only: gibt den konkreten Commit-SHA frei. Dasselbe Secret wird in einem künftigen Commit erneut abgewiesen.
  • This file path: gibt das Secret unter diesem Pfad in jedem künftigen Commit frei. Für Test-Fixtures oder bewusst unkritische Platzhalterwerte.
  • This repository: gibt jedes Vorkommen dieses Secret-Typs im Repository frei. Sparsam einsetzen.
  • Org-wide: gibt den Secret-Typ in allen Repositorys frei. Nur für erklärte Fehlalarme (etwa ein eigenes Token-Format, das auf ein Regelmuster passt, aber kein echtes Secret ist).

Eine Begründung ist Pflicht (Freitext). Sie wird ins Audit-Log geschrieben.

Nach erteilter Ausnahme muss der Entwickler erneut pushen. Die Ausnahme übernimmt den abgewiesenen Push nicht nachträglich.

Eigene Allowlist

Um bekannte Fehlalarme in einem bestimmten Repository stummzuschalten (Test-Fixtures, Beispielschlüssel in der Dokumentation, absichtlich öffentliche Zugangsdaten), lege eine .gitleaks.toml im Wurzelverzeichnis des Repositorys an:

[extend]
useDefault = true

[[allowlists]]
description = "Test fixture AWS keys"
paths = ["tests/fixtures/.*", "docs/examples/.*"]

[[allowlists]]
description = "Example key in README"
regexes = ["AKIAIOSFODNN7EXAMPLE"]

Die Allowlist wird pro Repository im Pre-Receive-Hook ausgewertet. Sie übersteuert den Push-Schutz auf Organisationsebene nicht für Muster, die nicht in der Allowlist stehen.

Pull-Request-Prüfungen: Nur neue Befunde blockieren

Die Checks für Abhängigkeiten (fremforge/dep-scan), SAST (fremforge/sast), Lizenzen (fremforge/license-scan), Secrets (fremforge/secret-scan) und Images (fremforge/image-scan) scannen das gesamte Repository, lassen einen Pull Request aber nur an Befunden scheitern, die der Pull Request selbst einführt. Ein Befund, den der Ziel-Branch bereits hat, blockiert nicht, sodass ein Pull Request, der nichts Verwundbares ändert, auch in einem Repository mit offenen Befunden gemergt werden kann. Der Status lautet dann zum Beispiel No new findings at or above critical; 8 already on main.

fremforge vergleicht die Befunde des Pull Requests mit dem neuesten Scan dieses Branches durch denselben Scanner. Ein Befund gilt als derselbe, wenn:

CheckDerselbe Befund, wenn
AbhängigkeitenÖkosystem, Paket und Advisory übereinstimmen. Ein Paket auf eine Version anzuheben, die dieselbe Advisory noch hat, ist nicht neu; eine neue Advisory schon
SASTRegel, Datei und erkannter Code übereinstimmen. Ein Befund, der nur in eine andere Zeile gewandert ist, ist nicht neu
LizenzenÖkosystem, Paket und Lizenz übereinstimmen
SecretsRegel und erkannte Zeilen übereinstimmen, verglichen über einen schlüsselbasierten Hash (HMAC), den der Scanner berechnet. Das Secret selbst wird nie gespeichert oder versendet. Ein Secret, das nur in eine andere Zeile gewandert ist, ist nicht neu
ImagesAdvisory, Paket und Datei übereinstimmen
  • Kopien zählen: Ein zweites Vorkommen eines Befunds, den der Branch einmal hat, ist neu.
  • Alles, was ohnehin entscheidet, ob ein Befund blockiert, gilt weiterhin zuerst: die Blockierschwelle, Verwerfungen, Ausnahmen und die Lizenzrichtlinie. Ein neues bösartiges Paket oder eine neue bekanntermaßen ausgenutzte Schwachstelle blockiert immer, unabhängig davon, ob deine Organisation festgelegt hat, dass nicht erreichbare Befunde nicht blockieren.
  • Bestehende Befunde werden nicht versteckt. Sie stehen im Kommentar zum Pull Request und unter Sicherheit › Befunde, zählen für deine SLA und lösen wie bisher Warnungen aus.

Der Branch wird nach einem Push auf deinen Standard-Branch gescannt, höchstens einmal pro Stunde, wobei der neueste Push gewinnt. Ein Befund, der seit dem letzten Scan in den Branch gelangt ist, zählt daher bis zum nächsten Scan weiterhin als Befund des Pull Requests; ein neuer Befund wird nie für einen bestehenden gehalten. Gibt es noch keinen Scan des Branches, blockiert wie bisher jeder Befund, und der Scan des Pull Requests fordert einen an. Sobald der erste Scan des Branches erfasst ist, scannt fremforge von selbst erneut die offenen Pull Requests in diesen Branch, deren Check inzwischen fehlgeschlagen ist (höchstens 20 pro Repository und Check, die zuletzt aktualisierten), sodass sie grün werden oder nur noch an dem rot bleiben, was sie hinzufügen, ohne erneuten Lauf oder neuen Push. Ein Pull Request in einen anderen Branch als deinen Standard-Branch zählt immer jeden Befund.

Abhängigkeits-Scanning

osv-scanner prüft jeden Pull Request und den Standard-Branch nach jedem Push gegen die OSV-Advisory-Datenbank. Er liest Lockfiles und Manifeste offline, ohne Abhängigkeiten aufzulösen, und setzt den Commit-Status fremforge/dep-scan sowie einen Kommentar im Pull Request. Befunde stehen unter Sicherheit › Befunde › Abhängigkeiten.

Einstellungen und was einen Merge blockiert

Unter Sicherheit › Befunde › Abhängigkeiten kann ein Owner das Abhängigkeits-Scanning für die Organisation abschalten und die Blockierschwelle festlegen: den niedrigsten Schweregrad, bei dem fremforge/dep-scan fehlschlägt. Standard ist critical. Bei einem Pull Request blockieren nur Befunde, die der Pull Request einführt. Ob ein fehlschlagender Check den Merge tatsächlich blockiert, hängt vom Modus des Checks (enforce, audit oder off) unter Sicherheit › Priorisieren › Pull-Request-Prüfungen ab, und davon, ob der Check im Branch-Schutz erforderlich ist.

Damit ein Befund nicht blockiert, behebe ihn oder erfasse eine Ausnahme. Eine Organisation kann außerdem festlegen, dass Befunde, die nicht erreichbar sind, nicht blockieren. Diese Festlegung gilt nie für bösartige Pakete oder bekanntermaßen ausgenutzte Schwachstellen.

Korrektur-Pull-Requests

Zwei Dinge öffnen Korrektur-Pull-Requests für verwundbare Abhängigkeiten:

  • Sicherheitskorrekturen: Korrektur-PR öffnen bei einem Befund (oder die automatische Richtlinie) öffnet einen Pull Request pro Paket auf die niedrigste Version, die jede offene Advisory behebt. Siehe Sicherheitskorrekturen.
  • Abhängigkeits-Updates: die eigenen Schwachstellenwarnungen von Renovate, für Organisationen, die sich für Abhängigkeits-Updates angemeldet haben. Die Repositorys jeder angemeldeten Organisation werden jede Nacht durchlaufen, und Jetzt ausführen startet einen Lauf außerhalb des Takts.

Unterstützte Manifeste

Welche Lockfiles und Manifeste gelesen werden und welche Ökosysteme Korrektur-Pull-Requests und Erreichbarkeit bekommen, steht an einer Stelle: unter Unterstützte Ökosysteme.

Scanning von Container-Images

Trivy scannt jedes OCI-Image, das in die Paket-Registry von fremforge gepusht wird. Die Ergebnisse erscheinen unter Sicherheit › Befunde › Container-Images.

Trivy läuft zentral, ohne dass du etwas installieren musst: trivy fs bei jedem Pull Request (der Status fremforge/image-scan, der nur bei Befunden blockiert, die der Pull Request einführt), trivy config als eigener Check (fremforge/iac-scan) und trivy image für jedes Image in deiner Registry und jedes Image, das über den Dependency-Proxy bezogen wird, mit regelmäßigen erneuten Scans. Befunde zu Fehlkonfigurationen aus trivy config haben einen eigenen Tab, Sicherheit › Befunde › IaC; siehe IaC-Scanning.

Jeder Befund enthält:

FeldBeschreibung
CVE-IDz. B. CVE-2024-12345 mit Link zur NVD
SchweregradCRITICAL / HIGH / MEDIUM / LOW / UNKNOWN
Betroffenes PaketPaketname und installierte Version
Korrigierte VersionErste Version, die die CVE behebt, sofern verfügbar
EPSS-WertWahrscheinlichkeit nach dem Exploit Prediction Scoring System

Scanning im eigenen Workflow (optional)

Es gibt nichts zu aktivieren: Die oben beschriebenen Scans laufen ohne Workflow-Datei. Soll zusätzlich dein eigener Build fehlschlagen, bevor er ein Image pusht, kannst du Trivy selbst in einem Workflow ausführen, zum Beispiel:

name: Image scan
on:
  push:
    branches: [main]
  pull_request:

jobs:
  trivy:
    runs-on: fremforge
    steps:
      - uses: actions/checkout@<full-40-char-sha>   # pin by SHA, never a tag or branch
      - name: Build image
        run: docker build -t ${{ github.repository }}:${{ github.sha }} .
      - name: Scan image
        run: |
          docker run --rm \
            -v /var/run/docker.sock:/var/run/docker.sock \
            aquasec/trivy:0.74.0@sha256:<digest> \
            image --exit-code 1 --severity HIGH,CRITICAL \
            ${{ github.repository }}:${{ github.sha }}

Setze --severity auf die Schwelle, ab der der Build fehlschlagen soll.

Warum dieses Beispiel per SHA und per Digest festlegt

Eine frühere Version dieser Seite verwendete aquasecurity/trivy-action@master und actions/checkout@v4. Beides sind veränderliche Referenzen, und am 19. März 2026 wurde dieser Unterschied real: Ein Angreifer hat den Tag v0.69.4 von trivy-action auf ein bösartiges Release umgebogen. Pipelines, die die Action per Tag oder Branch referenzierten, zogen beim nächsten Lauf den Code des Angreifers, und mit den dabei abgegriffenen Zugangsdaten wurden zwei Releases eines anderen Projekts mit Hintertür auf PyPI veröffentlicht.

Ein Tag ist ein Zeiger, den jemand anderes verschieben kann. Ein Commit-SHA und ein Image-Digest sind das nicht.

  • Actions: Referenziere den vollständigen 40-stelligen Commit-SHA, nicht @v4 und niemals @master. Renovate schlägt weiterhin Upgrades vor; es schreibt den SHA und seinen Kommentar gemeinsam um.
  • Scanner: Bevorzuge das per Digest festgelegte Container-Image des Werkzeugs gegenüber einer Wrapper-Action. Weniger bewegliche Teile, und was du festgelegt hast, ist das, was läuft.
  • Spiegle, wovon du abhängst: fremforge erreicht aus keinem Produktionspfad eine Upstream-Registry; jedes Image kommt aus unserer eigenen Registry, per Digest festgelegt.

Dieser Vorfall erforderte auf Seiten von fremforge keine Maßnahme: Wir haben trivy-action nie verwendet, und unser eigenes Trivy ist ein per Digest festgelegtes Image. Das kompromittierte Projekt hatte Trivy über apt ohne festgelegte Version installiert. Festlegen ist unspektakulär, und es ist genau die Kontrolle, die das verhindert hätte.

SBOM-Erzeugung

Syft erzeugt CycloneDX-1.6-SBOMs für jedes Container-Image, das in die Registry gepusht wird, und für jeden Release-Tag, mit einem SPDX-2.3-Gegenstück aus demselben Lauf (nach bestem Bemühen; siehe SBOMs). Uploads werden als CycloneDX 1.5–1.7 oder SPDX 2.x/3.x akzeptiert. Die SBOM-Erzeugung ist für jede Organisation standardmäßig eingeschaltet; es gibt keinen Aktivierungsschritt.

Herunterladen

In der Oberfläche der Paket-Registry: Image öffnen → Tab Attestations → Download SBOM.

Per API:

curl -H "Authorization: token <your-pat>" \
  https://frem.sh/_app/api/v1/orgs/<org>/findings/attestations \
  -o sbom.json

Die SBOM ist CycloneDX-JSON und enthält alle transitiven Abhängigkeiten mit ihren PURL-Kennungen, Lizenzausdrücken und Versionsbereichen.

SAST

OpenGrep führt bei jedem Pull Request und auf dem Standard-Branch zentral eine statische Analyse aus. Sie ist für jede Organisation standardmäßig eingeschaltet. Befunde setzen den Status fremforge/sast und einen Kommentar im Pull Request und stehen unter Sicherheit › Befunde › SAST. Sprachen, Regeln und Einstellungen stehen auf der Seite SAST. Eigene Regeln werden nicht unterstützt.

Signierte Commits

fremforge unterstützt zwei Wege zum Signieren von Commits, die beide nativ über das Abzeichen „Verified“ von Forgejo in der Weboberfläche verifiziert werden:

  • Signierung per SSH-Schlüssel (empfohlener Standard): verwendet den SSH-Schlüssel, den du bereits für git push registriert hast. Kein neues Schlüsselmaterial zu verwalten, kein Unterauftragsverarbeiter eines Dritten, keine Abhängigkeit von einem Transparenzlog, kein Transit über US-Rechtsräume. Das ist der kanonische Weg in fremforge. (Bei neuen Organisationen, die nach dem 2026-05-22 angelegt wurden, ist der SSH-Transport standardmäßig deaktiviert; die Signierung per SSH-Schlüssel funktioniert trotzdem, unabhängig davon, ob du SSH oder HTTPS für git push verwendest.)
  • Signierung per GPG: klassischer OpenPGP-Schlüssel. Verwende diesen Weg, wenn deine Organisation bereits eine Richtlinie für die GPG-Schlüsselverwaltung hat (Smartcard, HSM, zentraler Keyserver).

Schlüssellose Commit-Signierung IST verfügbar in fremforge, über einen selbst betriebenen Sigstore-Stack: Fulcio-CA unter sign.frem.sh und eine TSA unter tsa.frem.sh, beide auf T Cloud Public in eu-de, mit der Root-CA in DEW KMS. Die öffentliche Sigstore-Instanz der Linux Foundation wird NICHT verwendet: fremforge leitet keinen Signier- oder Verifizierungsverkehr über sigstore.dev / rekor.sigstore.dev, sodass die Haltung ohne US-Unterauftragsverarbeiter bestehen bleibt. Es gibt bewusst kein Rekor bzw. Transparenzlog für die Commit-Signierung; stattdessen ist der von der TSA verankerte Zeitstempel das Integritätsprimitiv. (fremforge betreibt durchaus ein Transparenzlog, unter log.frem.sh, aber es deckt nur Attestierungen der Build-Provenienz ab und ist kein Rekor; siehe Transparenzlog. Beide sind unabhängig: Ein signierter Commit wird dort nicht eingetragen.) Die Einrichtung von gitsign auf Kundenseite beschreibt Schlüssellose Commit-Signierung. (Hinweis: Die Provenienz von Builds und Releases ist ein separater Weg: Die SLSA-Provenienz von fremforge wird mit einem Ed25519-Schlüssel signiert, den die Plattform hält, NICHT über Fulcio. Siehe den Abschnitt zu SLSA unten.)

Signierung per SSH-Schlüssel einrichten

# Tell Git to use SSH for signing (Git ≥ 2.34)
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true

# (Optional) point Git at your local allowedSignersFile for verify
git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers

Füge den passenden SSH-Schlüssel deinem fremforge-Benutzerprofil unter Einstellungen → SSH- / GPG-Schlüssel → Schlüssel hinzufügen hinzu (Forgejo akzeptiert denselben Schlüssel für Push und Signierung, oder einen separaten Schlüssel nur zum Signieren).

Beim nächsten git commit signiert Git mit deinem SSH-Schlüssel. Forgejo verifiziert die Signatur gegen die allowedSignersFile der Organisation (automatisch aus den registrierten Signaturschlüsseln der Mitglieder abgeleitet) und zeigt in der Weboberfläche das Abzeichen „Verified“ an.

Verifizieren

git log --show-signature HEAD~5..HEAD

Jeder Commit zeigt eine Zeile Good "git" signature for <user>@<org>. Die Weboberfläche von Forgejo zeigt neben dem Commit-Hash ein grünes Abzeichen „Verified“.

Signierung per GPG einrichten

Wenn du GPG bevorzugst, funktioniert der übliche Ablauf mit git config --global commit.gpgsign true + user.signingkey <gpg-key-id> unverändert; lade deinen öffentlichen Schlüssel unter Einstellungen → SSH- / GPG-Schlüssel → Schlüssel hinzufügen hoch. Forgejo verifiziert ihn und zeigt das Abzeichen „Verified“ an.

Signierte Commits verlangen (Organisationsrichtlinie)

Erzwinge signierte Commits auf geschützten Branches mit Commit-Signierung unter Sicherheit › Richtlinien › Repository-Regeln (siehe Branch-Schutz), oder pro Repository unter Repository → Einstellungen → Branches → <branch> → Signierte Commits erforderlich. Unsignierte Commits werden beim Push abgewiesen. Sowohl Signaturen per SSH-Schlüssel als auch per GPG erfüllen die Anforderung.

SLSA-Provenienz

Die Provenienz von Builds und Releases wird serverseitig von fremforge erzeugt: Es gibt keine Workflow-Datei in deinen Repositorys, und es werden keine Runner-Minuten verbraucht. Bei jedem Forgejo-Ereignis release.published und package berechnet die fremforge-API den Hash jedes veröffentlichten Assets, baut ein in-toto-Statement in einem DSSE-Umschlag, signiert es mit dem Builder-Schlüssel der Plattform, speichert die Attestierung in einem auf deinen Mandanten beschränkten Speicher mit Standort in der EU und lädt den Umschlag neben dem Asset als <asset>.intoto.jsonl hoch. Er ist außerdem in der Konsole unter Sicherheit › Komponenten › Build-Attestierungen aufgeführt.

Die eigenständige Workflow-Vorlage slsa-provenance.yaml, die Kunden früher aus ihren Build-Pipelines aufrufen sollten, wurde am 2026-06-16 eingestellt: Ein Audit der gesamten Flotte fand keinen einzigen Aufrufer, und die Provenienz wird jetzt automatisch serverseitig erzeugt, ohne dass Kunden etwas verdrahten müssen.

EU-souverän von Grund auf. Die Signaturschlüssel, die Vertrauensbasis, die Zertifizierungsstelle und der Speicher liegen alle im T-Cloud-Public-Konto von fremforge (eu-de). Im gesamten Verifizierungspfad gibt es kein öffentliches Sigstore, kein Rekor und keine Abfrage von tuf-repo-cdn.sigstore.dev.

Zwei Signaturen, beide auf jeder Attestierung. Die primäre stammt von einem Ed25519-Schlüssel, den die Plattform hält, und sie ist diejenige, die das Rezept unter Verifizieren unten prüft: nur curl / jq / openssl / python3, sonst nichts. Zusätzlich wird jede Attestierung schlüssellos gegen das selbst betriebene Sigstore von fremforge signiert: ein kurzlebiges Fulcio-Zertifikat, dessen SAN die Identität der Build-Quelle ist, mit einem Zeitstempel der RFC3161-TSA von fremforge. Beide sind in der Vertrauensbasis beschrieben, die fulcio_root_ca_pem, tsa_cert_chain_pem, certificate_oidc_issuer und certificate_identity_pattern enthält, sodass auch die schlüssellose Signatur offline verifizierbar ist.

Es gibt weiterhin kein Transparenzlog (in der Vertrauensbasis steht "transparency_log": "none"), daher wird die schlüssellose Signatur gegen den RFC3161-Zeitstempel statt gegen Rekor validiert (cosign --use-signed-timestamps --insecure-ignore-tlog). Was dir das bringt und was nicht, steht unter §Unabhängige Verifizierbarkeit.

(Die Commit-Signierung von fremforge ist ein separater Weg über dieselbe selbst betriebene CA; siehe Schlüssellose Commit-Signierung.)

Vertrauensbasis: https://www.frem.sh/.well-known/slsa-trust-root.json, eine kleine JSON-Datei, die die öffentlichen Builder-Schlüssel auflistet. Ihr Format ist fremforge-spezifisch:

{
  "trusted_keys": [
    {
      "kid": "fremforge-slsa-builder-v1",
      "alg": "ed25519",
      "pem": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
      "valid_from": "2026-05-17T00:00:00Z",
      "valid_to": "2026-09-12T00:00:00Z",
      "builder_id": "https://frem.sh/runner-controller/v1"
    },
    {
      "kid": "fremforge-slsa-builder-v2",
      "alg": "ed25519",
      "pem": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
      "valid_from": "2026-09-12T00:00:00Z",
      "builder_id": "https://frem.sh/runner-controller/v1"
    }
  ]
}

fremforge-slsa-builder-v2 ist seit dem Schlüsselwechsel am 2026-09-12 der aktive Signaturschlüssel. fremforge-slsa-builder-v1 hat alles davor signiert und wird nur noch zur Verifizierung vorgehalten: Sein valid_to ist der Zeitpunkt, an dem er aufgehört hat zu signieren, kein Ablaufdatum für die Signaturen, die er erstellt hat. Verifiziere immer gegen den Schlüssel, den die keyid im vorliegenden Umschlag nennt, nie gegen einen festen Namen.

Lege die Datei (oder eine Kopie davon) in deiner CI fest, um das Vertrauen über das Verifizierungsfenster hinweg zu fixieren. Schlüsselwechsel werden gemäß der Benachrichtigungsregel für Unterauftragsverarbeiter auf der Trust-Seite 30 Tage im Voraus angekündigt; eine Signatur lässt sich erst dann nicht mehr verifizieren, wenn ihr Schlüssel aus trusted_keys entfernt wird.

Aufbau von Umschlag und Statement (DSSE-Umschlag um ein in-toto-Statement):

FeldWert
Umschlag payloadTypeapplication/vnd.in-toto+json
Umschlag signatures[0].keyidfremforge-slsa-builder-v2 für alles, was seit dem 2026-09-12 signiert wurde, davor fremforge-slsa-builder-v1 (entspricht einem trusted_keys[].kid in der Vertrauensbasis)
Statement _typehttps://in-toto.io/Statement/v1
Statement predicateTypehttps://slsa.dev/provenance/v1
predicate.buildDefinition.buildTypeWelcher Weg das Statement erzeugt hat; siehe §Was jeder buildType bedeutet. Einer von …/buildtypes/forgejo-action-run/v1, …/container-image/v1, …/forgejo-release/v1, …/forgejo-package/v1
predicate.buildDefinition.internalParameters.run_idDer Actions-Lauf, der das Artefakt erzeugt hat. Vorhanden bei Build-Provenienz; fehlt bei einem Veröffentlichungs-Statement
predicate.buildDefinition.internalParameters.attestation_kindpublication, wenn das Statement veröffentlichte Bytes beobachtet statt eines Builds. Fehlt bei Build-Provenienz
predicate.buildDefinition.externalParameters.repository<org>/<repo>
predicate.buildDefinition.externalParameters.refDie Git-Ref (z. B. refs/tags/v1.2.3)
predicate.runDetails.builder.idhttps://frem.sh/runner-controller/v1 (serverseitig kontrolliert und beim Signieren erzwungen überschrieben; ein kompromittierter Runner kann diesen Wert nicht fälschen)
subject[].name / subject[].digest.sha256Der Dateiname des Assets und der SHA-256, den fremforge dafür berechnet hat

Was das genau ist. Bis zum 2026-09-11 beanspruchte dieser Abschnitt SLSA Build L2. Wir haben das gegen die Spezifikation v1.0 neu hergeleitet, die Behauptung hielt nicht, und wir haben sie zurückgezogen. Die Lücke, die sie unwahr machte (eine Provenienz, die keinen Build beschrieb), wurde am 2026-09-12 geschlossen. Wir nennen trotzdem weiterhin keine Stufe, aus einem Grund, der am Ende dieses Abschnitts steht.

Zwei verschiedene Statements werden jetzt mit demselben Schlüssel signiert, und du kannst sie allein am Umschlag unterscheiden.

Was jeder buildType bedeutet

buildType endet aufWas er aussagtNennt einen Build-Lauf?
forgejo-action-run/v1Die Steuerungsebene von fremforge hat die hochgeladenen Artefakte dieses Laufs aus den eigenen Aufzeichnungen der Build-Plattform gelesen, die Bytes abgerufen und gehashtJa: run_id
container-image/v1Ein von einem Lauf gepushtes Image, attestiert mit der Laufidentität aus einem kurzlebigen OIDC-Token, das fremforge für diesen Job ausgestellt hatJa: run_id, kryptografisch gebunden
forgejo-release/v1Ein veröffentlichtes Release-Asset. Übernimmt den Commit eines Builds, wenn derselbe Digest bereits als gebaut attestiert wurde; siehe untenNur bei Verknüpfung
forgejo-package/v1Eine Paketversion, die in der Registry erschienen istNein

Build-Provenienz (die ersten beiden) beschreibt, wie das Artefakt erzeugt wurde: Sie nennt den Lauf, den Workflow und den Commit, und die Laufidentität stammt von der Plattform, nicht aus irgendetwas, das der Build über sich selbst behauptet hat.

Veröffentlichungsattestierung (die letzten beiden, unverknüpft) sagt etwas Engeres, und sagt es ausdrücklich: attestation_kind: publication. Sie bedeutet: fremforge hat beobachtet, dass genau dieser bytegenaue Inhalt hier veröffentlicht wurde, und signiert diese Beobachtung mit einem Schlüssel, den der Build nicht erreichen kann. Das ist eine echte, prüfbare Eigenschaft. Sie ist keine Aussage darüber, wie das Artefakt gebaut wurde, und gibt auch nicht mehr vor, eine zu sein: Ist kein Quell-Commit bekannt, steht in commit_sha der Literalwert no-source-commit statt eines Werts, der wie ein Git-Commit aussieht. Siehst du dort 40 Hex-Zeichen, ist es ein echter Commit.

Die Digest-Verknüpfung. Ein Release-Asset, dessen sha256 bereits eine Build-Attestierung hat, übernimmt den Commit dieses Builds, und das Statement verweist darauf (source_commit_from: digest-join, plus ID und Objektschlüssel der Build-Attestierung). Die Verbindung ist der Digest des Artefakts, sodass „dieselben Bytes“ eine kryptografische Tatsache ist und keine Namenskonvention: Ein Artefakt später zu veröffentlichen, ändert nicht, was es erzeugt hat. Ein ausdrückliches target_commitish am Release hat immer Vorrang vor der Ableitung.

Die Plattformeigenschaften hinter alldem:

  • Läufe können sich nicht gegenseitig beeinflussen. Eine kurzlebige ECS-VM pro Job, die nach Ende des Jobs zerstört wird, mit einer ENI pro VM und Zugangsdaten pro Runner, die mit dem Job verfallen; siehe §Modell der Kernel-Isolation.
  • Der Signaturschlüssel ist für den Build unerreichbar. Er liegt in der API, nie auf einem Runner, und die Builder-Identität wird beim Signieren serverseitig überschrieben, sodass ein kompromittierter Build sie nicht fälschen kann.
  • Keine Verdrahtung durch Kunden für die webhookgesteuerten Wege. Nichts, was aus einem Workflow aufzurufen wäre, und keine verbrauchten Runner-Minuten.

Warum wir trotzdem keine SLSA-Stufe nennen. Diese beiden Isolationseigenschaften sind das, was SLSA v1.0 Build L3 verlangt, und die Build-Provenienz beschreibt jetzt einen tatsächlichen Build. Uns ist lieber, du liest die Tabelle oben und entscheidest selbst, was sie wert ist, als dass du eine Zahl von der Partei übernimmst, die geprüft wird. (Hermetische Builds, reproduzierbare Artefakte und Transparenzlogs werden manchmal als Anforderungen von L3 angeführt. Das sind sie nicht: Es waren Konzepte von SLSA v0.1 L4, und sie kommen in v1.0 auf keiner Stufe vor, die sie unter künftigen Richtungen aufführt.)

Was noch fehlt, ist ein Transparenzlog, und das ist eine andere Achse als jede Stufe; siehe §Unabhängige Verifizierbarkeit.

Verifizieren

Verwende nicht das Upstream-Werkzeug slsa-verifier verify-artifact --trusted-root. Dieses Werkzeug hat bei verify-artifact kein Flag --trusted-root und kann weder die eigene Vertrauensbasis-JSON von fremforge noch einen reinen Ed25519-DSSE-Umschlag verarbeiten; es erwartet ein Bundle im Sigstore-Format. Die Provenienz von fremforge ist ein schlichter, mit Ed25519 signierter DSSE-Umschlag, daher verifizierst du ihn direkt mit curl / jq / openssl / python3, alles Standard in den Distributionen, ohne fremforge-Werkzeuge.

Die Verifizierung besteht aus drei unabhängigen Prüfungen: Die DSSE-Signatur ist für den veröffentlichten Builder-Schlüssel gültig, der SHA-256 des Assets entspricht dem Signierten, und das Statement nennt das richtige Repository und den richtigen Builder.

1. Vertrauensbasis abrufen und den öffentlichen Builder-Schlüssel extrahieren

curl -sSfL https://www.frem.sh/.well-known/slsa-trust-root.json -o trust-root.json
jq -r '.trusted_keys[] | select(.kid=="fremforge-slsa-builder-v2") | .pem' \
  trust-root.json > builder.pub

Für eine Attestierung, die vor dem 2026-09-12 signiert wurde, wähle stattdessen fremforge-slsa-builder-v1. jq -r '.signatures[0].keyid' <asset>.intoto.jsonl sagt dir, welcher es ist.

2. DSSE-Signatur, Asset-Digest und Statement-Felder verifizieren

Führe das in einem Verzeichnis aus, das das heruntergeladene Asset und seinen Umschlag <asset>.intoto.jsonl enthält. Setze ASSET auf den Dateinamen des Assets:

ASSET=myapp python3 - <<'PY'
import base64, json, hashlib, os, subprocess, sys, pathlib

asset = os.environ["ASSET"]
env = json.loads(pathlib.Path(f"{asset}.intoto.jsonl").read_text().strip())

payload_type = env["payloadType"]                 # application/vnd.in-toto+json
payload      = base64.b64decode(env["payload"])

# Reconstruct the DSSE pre-authentication encoding (DSSEv1):
#   "DSSEv1 " + LEN(type) + " " + type + " " + LEN(payload) + " " + payload
# where LEN is the utf-8 byte length and separators are single ASCII spaces.
pae = (
    b"DSSEv1 "
    + str(len(payload_type.encode())).encode() + b" "
    + payload_type.encode() + b" "
    + str(len(payload)).encode() + b" "
    + payload
)
pathlib.Path("pae.bin").write_bytes(pae)
pathlib.Path("sig.bin").write_bytes(base64.b64decode(env["signatures"][0]["sig"]))

# Ed25519 ("pure" EdDSA — no pre-hash) verify with openssl. Feed the raw PAE.
r = subprocess.run(
    ["openssl", "pkeyutl", "-verify", "-pubin", "-inkey", "builder.pub",
     "-rawin", "-in", "pae.bin", "-sigfile", "sig.bin"],
    capture_output=True, text=True,
)
if r.returncode != 0 or "Signature Verified Successfully" not in r.stdout:
    print("DSSE signature FAILED:", r.stdout, r.stderr, file=sys.stderr); sys.exit(1)
print("DSSE signature: OK")

stmt = json.loads(payload)

# The signed subject digest must match the asset bytes on disk.
want = next((s["digest"]["sha256"] for s in stmt["subject"] if s["name"] == asset), None)
got  = hashlib.sha256(pathlib.Path(asset).read_bytes()).hexdigest()
if want is None:
    print(f"asset {asset} not found among signed subjects", file=sys.stderr); sys.exit(1)
if want != got:
    print(f"digest mismatch: signed={want} actual={got}", file=sys.stderr); sys.exit(1)
print(f"asset digest: OK ({got})")

# Sanity-check the builder identity and predicate type.
builder = stmt["predicate"]["runDetails"]["builder"]["id"]
if builder != "https://frem.sh/runner-controller/v1":
    print(f"unexpected builder.id: {builder}", file=sys.stderr); sys.exit(1)
if stmt["predicateType"] != "https://slsa.dev/provenance/v1":
    print(f"unexpected predicateType: {stmt['predicateType']}", file=sys.stderr); sys.exit(1)
print("builder.id + predicateType: OK")
PY

Geben alle drei Prüfungen OK aus, ist das Asset genau die Bytes, die fremforge attestiert hat, der Umschlag wurde mit dem veröffentlichten Builder-Schlüssel signiert, und das Statement nennt den Builder von fremforge. Die Verifizierung schlägt fehl (Exit-Code ungleich null), wenn die Signatur gegen builder.pub nicht gültig ist (manipulierter Umschlag oder falscher bzw. gewechselter Schlüssel), wenn der SHA-256 des Assets nicht zum signierten Subject passt (manipuliertes Asset) oder wenn builder.id / predicateType nicht übereinstimmen.

Die keyid im Umschlag (fremforge-slsa-builder-v2 für alles, was seit dem 2026-09-12 signiert wurde) muss zu der kid passen, die du aus der Vertrauensbasis ausgewählt hast; ein Umschlag, der gegen den anderen Schlüssel geprüft wird, scheitert mit DSSE signature FAILED. Wechselt fremforge den Signaturschlüssel, rufe die Vertrauensbasis erneut ab und wähle die kid, die der Umschlag nennt.

Warum zum Start kein Sigstore

Der öffentliche Stack aus Fulcio, Rekor und Trillian von Sigstore wird von der OpenSSF (Linux Foundation) betrieben, einer in den USA eingetragenen Organisation. Ihn zu nutzen, würde die Metadaten jedes Builds an Infrastruktur in den USA senden, was mit der Zusage von fremforge, nur Unterauftragsverarbeiter in der EU einzusetzen, in AVV Anhang B unvereinbar ist. fremforge betreibt eigene Fulcio- und TSA-Instanzen unter sign.frem.sh / tsa.frem.sh auf T Cloud Public eu-de, die sowohl für die Commit-Signierung (siehe Schlüssellose Commit-Signierung) als auch, seit dem 2026-06-21, als zweite Signatur auf der Build-Provenienz verwendet werden. Bewusst nicht dabei sind die öffentliche Sigstore-Instanz und Rekor.

Der SHA-256 jeder Attestierung wird außerdem in das Audit-Log der Plattform hashverkettet, dessen Anker in einem OBS Object Lock im COMPLIANCE-Modus für 3 Jahre liegen. Das ist eine nützliche interne Kontrolle und kein Ersatz für ein Transparenzlog, daher beschreiben wir sie nicht mehr als gleichwertigen Manipulationsnachweis. Zwei Gründe, beide konkret: Der Anker ist für dich nicht lesbar, kann also nicht Teil deiner Verifizierung sein; und OBS Object Lock schützt jede Objektversion: Gemessen am 2026-08-14 kann eine neuere Version einen gesperrten Schlüssel überdecken, und eine Löschmarkierung verbirgt ihn vor gewöhnlichen Lesezugriffen, beides Aktionen, die dem Betreiber zur Verfügung stehen. Was dir das Vertrauensmodell gibt und was nicht, steht unter §Unabhängige Verifizierbarkeit.

Unveränderliche Releases

Owner einer Organisation können jedes Release bei der Veröffentlichung einfrieren: Sein Tag, sein Ziel-Commit und seine Assets können sich nie mehr ändern (Titel und Notizen bleiben bearbeitbar), der Tag-Name eines gelöschten Release kann nie wiederverwendet werden, und auch Container-Tags lassen sich einfrieren (Tags im Stil v1.2.3 oder alle bis auf eine Liste). Jedes unveränderliche Release erhält ein signiertes in-toto-Release-Statement, das jedes Asset per SHA-256 und den Tag per Commit benennt und mit fremforge release verify prüfbar ist. Siehe Unveränderliche Releases und Release-Attestierung.

Unabhängige Verifizierbarkeit

Ein Repository, einen Runner und einen Artefaktspeicher nach Deutschland zu verlagern, beantwortet, wo die Daten liegen. Es beantwortet für sich genommen nicht, ob man dem Audit-Trail vertrauen kann, ohne dem Betreiber zu vertrauen, der ihn erzeugt hat. Das sind unterschiedliche Eigenschaften, und wir sagen lieber ausdrücklich, welche davon wir heute liefern.

Was du bekommst, ohne uns zum Zeitpunkt der Verifizierung zu vertrauen. Die Attestierung reist mit dem Artefakt (<asset>.intoto.jsonl und daneben <asset>.sigstore.json), sodass du beide Hälften in der Hand hast. Der öffentliche Builder-Schlüssel, die Fulcio-Root-CA und die TSA-Kette stehen alle in einer statischen Vertrauensbasis, von der du eine Kopie in deiner eigenen CI festlegen solltest. Die Verifizierung berechnet den Digest des Artefakts neu und prüft die Signaturen lokal, mit curl / jq / openssl / python3. Es wird keine fremforge-API kontaktiert, und es sind keine fremforge-Werkzeuge nötig. Hast du die Vertrauensbasis einmal festgelegt, kann eine spätere Änderung auf unserer Seite nicht rückwirkend ändern, was deine Pipeline akzeptiert.

Was du nicht bekommst, offen gesagt. Es gibt jetzt ein nur anfügbares Transparenzlog für Provenienz-Attestierungen unter log.frem.sh, aber es bietet noch nicht die Eigenschaft, die man mit dem Wort üblicherweise meint. Konkret:

  • Das Log macht es uns unmöglich, die Historie unbemerkt umzuschreiben: Hältst du einen Inklusionsnachweis und wir ändern oder entfernen später deine Attestierung, lässt sich dein Nachweis nicht mehr verifizieren, und du kannst den Widerspruch belegen. Das ist echt und neu.
  • Es ist seit dem 16. September 2026 teilweise resistent gegen Split-View. Ein unabhängiger Witness, witness.stagemole.eu, betrieben von Mullvad VPN AB, gegenzeichnet jeden Checkpoint, sodass es jetzt eine dritte Partei gibt, der es auffallen würde, wenn wir etwas Ungewöhnliches signieren. Um genau diese Eigenschaft ging es in diesem Punkt schon immer, und sie fehlt nicht mehr. Ein Witness ist kein Quorum: Er erhöht den Aufwand, dir einen anderen Baum zu zeigen als allen anderen, kann aber eine Absprache zwischen dem Log und seinem einzigen Witness nicht erkennen, und sein Betreiber bezeichnet die Instanz als Staging. Beachte außerdem, dass die einzigen produktionsreifen Witnesses in der EU (Glasklar Teknik, Mullvad VPN und Tillitis) dieselbe Muttergesellschaft haben, Amagicom AB; mehr von ihnen brächten Verfügbarkeit, nicht Unabhängigkeit.
  • Der RFC3161-Zeitstempel, der ein Log teilweise ersetzt, wird von unserer TSA ausgestellt, und die Vertrauensbasis wird von unserer Domain verteilt. Das Festlegen (Pinning) löst das Zweite für alle, die beim Onboarding festlegen; das Problem des Vertrauens beim ersten Kontakt löst es nicht.
  • Unser interner Anker der Audit-Kette ist für dich nicht lesbar und kann daher nicht Teil deiner Verifizierung sein.

Wenn der Betreiber die Partei ist, die du zur Rechenschaft ziehen können musst (die richtige Frage für Verteidigung und kritische Infrastruktur), dann schließen die Lücke ein Transparenzlog mit externen Witnesses, eine unabhängige Zeitstempelstelle und reproduzierbare Builds, die du ohne unsere Beteiligung nachbauen und vergleichen kannst. Das erste dieser drei gibt es jetzt, oben beschrieben, mit seinen Grenzen; die Zeitstempelstelle und reproduzierbare Builds gibt es nicht. Beachte, dass ein selbst betriebenes Log die Lücke nicht schließen würde: Ein Log, das wir betreiben, ist ein Log, das wir kontrollieren. Unabhängige Verifizierbarkeit erfordert eine externe Partei, und der ehrliche Kompromiss ist, dass der Weg über das öffentliche Sigstore-Log Artefakt-Digests, Repository-Identitäten und den Zeitpunkt von Releases außerhalb der EU veröffentlichen würde.

Wir planen das lieber gegen eine echte Anforderung, als eine zu erraten. Ist das für dich eine Beschaffungsvorgabe, sag uns, welche dieser Eigenschaften du brauchst und bis wann, und wir melden uns mit einem datierten Plan statt mit einer Behauptung.

Modell der Kernel-Isolation

Jeder Job erhält eine eigene kurzlebige ECS-VM auf T Cloud Public in eu-de, die nach Abschluss dieses Jobs zerstört wird. VMs sind Single-Task: Der Runner wird nach seinem einen Task gelöscht, und seine Zugangsdaten pro Runner verfallen mit dem Job, sodass eine VM keinen zweiten Job bedienen kann, nicht einmal innerhalb desselben Workflow-Laufs. (Vor dem 2026-07-30 konnten sich aufeinanderfolgende Jobs zu einem Commit eine VM teilen; das ist nicht mehr möglich.) Die Isolationseigenschaften:

EigenschaftDetail
Gemeinsamer KernelZwischen Commits oder zwischen Organisationen wird kein Kernel geteilt; jeder Commit erhält eine eigene VM. Jobs desselben Commits können sich diese VM teilen, behandle eine Pipeline also als eine Vertrauensdomäne
PersistenzDie VM wird zerstört, sobald die Jobs des Commits abgeschlossen sind; kein Dateisystemzustand überlebt bis zu einem anderen Commit oder einer anderen Organisation. Zwischen Jobs desselben Commits kann Zustand übergehen: Ein Build-Schritt, der Code von Abhängigkeiten Dritter ausführt, teilt sich die Maschine mit einem späteren Deploy-Schritt, beschränke also alle Zugangsdaten, die ein Deploy braucht, auf diese Pipeline
Zugriff auf die Plattform-APIAn der VM hängt keine IAM-Agency; die aus den Metadaten abgeleitete Identität hat keine Rollen, sodass die OTC-API von innen nicht erreichbar ist
NetzwerkSecurity Group pro VM mit Allowlist nur für ausgehenden Verkehr; Runner können keine eingehenden Verbindungen annehmen
Metadaten von T Cloud PublicDurch den SSRF-Ausgangsproxy blockiert; Runner-Code kann den Metadaten-Endpunkt der Instanz nicht mit privilegiertem Scope erreichen

Die vollständige Isolationsspezifikation und die Registrierung eigener Runner (BYO) beschreibt CI-Runner.

Mandantenisolation

Jeder fremforge-Mandant ist ein eigener, über die Mandanten-ID abgegrenzter Namensraum in der Plattform. Die Isolationskette reicht von der URL über Middleware, die Datenbankabfrageschicht und die Speicherschicht bis zur Audit-Kette.

SchichtMechanismusWas mandantenübergreifenden Zugriff verhindert
URL-RoutingMiddleware für /<slug>/_admin/* lädt den Mandanten bei jeder Anfrage neu aus dem SlugEin Benutzer mit einer Sitzung für Organisation A, der die URL von Organisation B einfügt, wird bei der Mitgliedschaftsprüfung abgewiesen, nicht erst an einem veralteten Cookie
MitgliedschaftsprüfungAbfrage des Forgejo-Teams Owners gegen den Slug, bei jeder Anfrage geprüftDie API vertraut nie einer eingehenden Behauptung darüber, welchen Organisationen der Benutzer angehört
DatenbankJede mandantenbezogene Tabelle hat einen Fremdschlüssel tenant_id; jeder Lese- UND jeder Schreibzugriff filtert danachSELECT / UPDATE / DELETE ohne tenant_id ist in den vom Audit markierten Pfaden strukturell unmöglich
Signierte TokensJede kurzlebige signierte URL (Magic Link für die Abrechnung, Rücknahme einer Kündigung, OIDC-State) trägt die Mandanten-ID im Payload; die Verifizierung gleicht sie mit dem Slug der URL abEin für Mandant A ausgestelltes Token wird abgewiesen, wenn es gegen die URL von Mandant B wiederverwendet wird
Speicher (OBS)SBOMs, Attestierungen und Audit-Log-Objekte sind alle unter <artifact>/<tenant_id>/... abgelegt; vor der Ausstellung einer signierten URL wird tenant.id === row.tenant_id erneut geprüftEine direkt vorsignierte OBS-URL für Mandant A lässt sich nicht so umschreiben, dass sie Mandant B liest
Audit-KetteManipulationssichere Hash-Kette pro Mandant; die Einträge eines Mandanten verändern nie den Kettenkopf eines anderenEin Bruch in der Kette von Mandant A (z. B. ein versuchtes rückwirkendes Löschen) beeinträchtigt die Integrität der Kette von Mandant B nicht
BetreiberzugriffWenn Mitarbeitende (fremverk) Kundendaten ansehen, wird das als Betreiberaktion in der Audit-Kette des Kunden protokolliert; Mandanten-Admins sehen Betreiberbesuche in ihrem eigenen Audit-LogKein stiller Zugriff von Mitarbeitenden auf Kundendaten

Verzögerung beim Entzug aus dem Team Owners

Die API speichert die Mitgliedschaft eines Kunden im Forgejo-Team Owners bis zu 5 Minuten zwischen, damit Seiten in unter 50 ms laden (die Alternative wäre ein Roundtrip zur Forgejo-API bei jeder Anfrage, was jede Seite um 100–200 ms verlangsamen würde). Der Kompromiss:

  • Fügst du in Forgejo jemanden zu Owners hinzu, erhält die Person innerhalb von 5 Minuten Admin-Zugriff.
  • Entfernst du in Forgejo jemanden aus Owners, verliert die Person den Admin-Zugriff innerhalb von 5 Minuten, nicht sofort.

Für die meisten Vorgänge ist diese Verzögerung vertretbar: Ein ehemaliger Owner kann keine neuen Forgejo-Ressourcen anlegen (die eigene Prüfung von Forgejo greift sofort), und jede privilegierte Aktion über die Admin-Oberfläche von fremforge wird im Audit-Log des Mandanten mit dem Benutzernamen des Akteurs erfasst, sodass Aktivität nach dem Entzug zuordenbar bleibt.

Brauchst du einen sofortigen Entzug (z. B. im Rahmen der Reaktion auf einen Vorfall nach kompromittierten Zugangsdaten), kann die Bereitschaft des Betreibers auf Anfrage die Cache-TTL für deinen Mandanten auf null setzen: Öffne ein Ticket unter support@frem.sh mit dem Betreffpräfix [urgent-revocation] und dem Benutzernamen sowie dem Slug des Mandanten. Standard-SLA: unter 15 Minuten während der Geschäftszeiten, unter 60 Minuten außerhalb. Dieselbe Wirkung erzielst du, indem du die Forgejo-Sitzung des Benutzers widerrufst (Forgejo Owner → User profile → Sign out); das macht das Sitzungs-Cookie ungültig, das die API auswertet, ohne dass auf Seiten von fremforge etwas nötig ist.

Sicherheitszusagen der Plattform

Schweregrad der CVEPatch-SLA (ab dem korrigierten Upstream-Release)
Critical (CVSS ≥ 9.0)48 Stunden
High (7.0–8.9)72 Stunden
Medium (4.0–6.9)7 Tage
LowNächstes geplantes Wartungsfenster

Die Patch-SLA ist vertraglich veröffentlicht und wird im Sicherheitsanhang des AVV wörtlich zitiert.

Integrität des Audit-Logs: manipulationssichere Hash-Kette, alle 2 Minuten in WORM-Speicher von T Cloud Public OBS verankert; stündliche Integritätsprüfung per FunctionGraph; Brüche in der Kette alarmieren die Bereitschaft.

Fehlerbehebung

Push blockiert, aber ich finde das Secret nicht im Diff.

Das Secret steckt möglicherweise in einer Datei, die geändert wurde, deren vollständiger Inhalt im normalen Diff aber nicht sichtbar ist. Führe Gitleaks lokal aus, um die genaue Stelle zu finden:

pip install gitleaks   # or brew install gitleaks
gitleaks detect --source . --verbose

Kein Korrektur-Pull-Request für eine bekannte CVE.

Prüfe, ob das Ökosystem und das Manifest des Pakets Korrektur-Pull-Requests bekommen (Unterstützte Ökosysteme) und ob die Advisory eine korrigierte Version hat. Renovate läuft für eine angemeldete Organisation jede Nacht; Sicherheit › Beheben › Abhängigkeits-Updates → Jetzt ausführen startet einen Lauf außerhalb des Takts, und Korrektur-PR öffnen beim Befund fordert sofort eine Sicherheitskorrektur an. Siehe Wenn kein Pull Request geöffnet wird.

Der SBOM-Download liefert 404.

SBOMs werden nur für Images erzeugt, die nach dem Einschalten der SBOM-Erzeugung gepusht wurden. Images, die davor gepusht wurden, haben keine SBOM. Pushe einen neuen Tag, um eine zu erzeugen.

SSH-Signierung schlägt fehl mit ssh-keygen: gpg failed to sign the data.

Stelle sicher, dass git config gpg.format auf ssh gesetzt ist (nicht auf den Standard openpgp) und user.signingkey auf eine Datei mit einem öffentlichen Schlüssel zeigt. Prüfe unter macOS außerdem, ob ssh-keygen im $PATH liegt (das mitgelieferte ssh-keygen von Apple funktioniert; hast du OpenSSH über Homebrew installiert, achte darauf, dass es korrekt vorrangig gefunden wird).

Querverweise