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:
| Kategorie | Beispielmuster |
|---|---|
| Zugangsdaten von Cloud-Anbietern | AWS-Access- und Secret-Keys, JSON von GCP-Servicekonten, Verbindungsstrings für Azure Storage |
| Tokens von Versionsverwaltungen | GitHub-PATs, GitLab-PATs, App-Passwörter von Bitbucket |
| Zahlungsschlüssel | Secret Keys von Stripe, Signatur-Secrets für Stripe-Webhooks |
| Kommunikations-Tokens | Slack-Bot-Tokens, Slack-Webhook-URLs, Auth-Tokens von Twilio |
| Private Schlüssel | Private RSA-, EC- und Ed25519-Schlüssel, PEM-kodierte Zertifikate |
| Generische Secrets | Strings mit hoher Entropie, die auf das Muster generic-api-key passen, JWT-Signatur-Secrets |
| Datenbank-URLs | Verbindungsstrings 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:
- Rotiere das Secret sofort beim ausstellenden Dienst (AWS IAM, Stripe-Dashboard usw.). Geh davon aus, dass es kompromittiert ist.
- 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>- 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:
- Öffne Sicherheit › Richtlinien › Push-Schutz › Aktive Ausnahmen → Neue Ausnahme.
- 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).
- 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:
| Check | Derselbe 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 |
| SAST | Regel, Datei und erkannter Code übereinstimmen. Ein Befund, der nur in eine andere Zeile gewandert ist, ist nicht neu |
| Lizenzen | Ökosystem, Paket und Lizenz übereinstimmen |
| Secrets | Regel 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 |
| Images | Advisory, 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:
| Feld | Beschreibung |
|---|---|
| CVE-ID | z. B. CVE-2024-12345 mit Link zur NVD |
| Schweregrad | CRITICAL / HIGH / MEDIUM / LOW / UNKNOWN |
| Betroffenes Paket | Paketname und installierte Version |
| Korrigierte Version | Erste Version, die die CVE behebt, sofern verfügbar |
| EPSS-Wert | Wahrscheinlichkeit 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
@v4und 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.jsonDie 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 pushregistriert 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ürgit pushverwendest.) - 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_signersFü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..HEADJeder 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):
| Feld | Wert |
|---|---|
Umschlag payloadType | application/vnd.in-toto+json |
Umschlag signatures[0].keyid | fremforge-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 _type | https://in-toto.io/Statement/v1 |
Statement predicateType | https://slsa.dev/provenance/v1 |
predicate.buildDefinition.buildType | Welcher 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_id | Der Actions-Lauf, der das Artefakt erzeugt hat. Vorhanden bei Build-Provenienz; fehlt bei einem Veröffentlichungs-Statement |
predicate.buildDefinition.internalParameters.attestation_kind | publication, wenn das Statement veröffentlichte Bytes beobachtet statt eines Builds. Fehlt bei Build-Provenienz |
predicate.buildDefinition.externalParameters.repository | <org>/<repo> |
predicate.buildDefinition.externalParameters.ref | Die Git-Ref (z. B. refs/tags/v1.2.3) |
predicate.runDetails.builder.id | https://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.sha256 | Der 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 auf | Was er aussagt | Nennt einen Build-Lauf? |
|---|---|---|
forgejo-action-run/v1 | Die Steuerungsebene von fremforge hat die hochgeladenen Artefakte dieses Laufs aus den eigenen Aufzeichnungen der Build-Plattform gelesen, die Bytes abgerufen und gehasht | Ja: run_id |
container-image/v1 | Ein von einem Lauf gepushtes Image, attestiert mit der Laufidentität aus einem kurzlebigen OIDC-Token, das fremforge für diesen Job ausgestellt hat | Ja: run_id, kryptografisch gebunden |
forgejo-release/v1 | Ein veröffentlichtes Release-Asset. Übernimmt den Commit eines Builds, wenn derselbe Digest bereits als gebaut attestiert wurde; siehe unten | Nur bei Verknüpfung |
forgejo-package/v1 | Eine Paketversion, die in der Registry erschienen ist | Nein |
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 beiverify-artifactkein Flag--trusted-rootund 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 mitcurl/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.pubFü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")
PYGeben 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
keyidim Umschlag (fremforge-slsa-builder-v2für alles, was seit dem 2026-09-12 signiert wurde) muss zu derkidpassen, die du aus der Vertrauensbasis ausgewählt hast; ein Umschlag, der gegen den anderen Schlüssel geprüft wird, scheitert mitDSSE signature FAILED. Wechselt fremforge den Signaturschlüssel, rufe die Vertrauensbasis erneut ab und wähle diekid, 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:
| Eigenschaft | Detail |
|---|---|
| Gemeinsamer Kernel | Zwischen 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 |
| Persistenz | Die 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-API | An 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 |
| Netzwerk | Security Group pro VM mit Allowlist nur für ausgehenden Verkehr; Runner können keine eingehenden Verbindungen annehmen |
| Metadaten von T Cloud Public | Durch 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.
| Schicht | Mechanismus | Was mandantenübergreifenden Zugriff verhindert |
|---|---|---|
| URL-Routing | Middleware für /<slug>/_admin/* lädt den Mandanten bei jeder Anfrage neu aus dem Slug | Ein 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üfung | Abfrage des Forgejo-Teams Owners gegen den Slug, bei jeder Anfrage geprüft | Die API vertraut nie einer eingehenden Behauptung darüber, welchen Organisationen der Benutzer angehört |
| Datenbank | Jede mandantenbezogene Tabelle hat einen Fremdschlüssel tenant_id; jeder Lese- UND jeder Schreibzugriff filtert danach | SELECT / UPDATE / DELETE ohne tenant_id ist in den vom Audit markierten Pfaden strukturell unmöglich |
| Signierte Tokens | Jede 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 ab | Ein 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üft | Eine direkt vorsignierte OBS-URL für Mandant A lässt sich nicht so umschreiben, dass sie Mandant B liest |
| Audit-Kette | Manipulationssichere Hash-Kette pro Mandant; die Einträge eines Mandanten verändern nie den Kettenkopf eines anderen | Ein 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 |
| Betreiberzugriff | Wenn Mitarbeitende (fremverk) Kundendaten ansehen, wird das als Betreiberaktion in der Audit-Kette des Kunden protokolliert; Mandanten-Admins sehen Betreiberbesuche in ihrem eigenen Audit-Log | Kein 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 CVE | Patch-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 |
| Low | Nä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 . --verboseKein 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
- Push-Schutz: die Liste der Ausnahmen und das Fehlerverhalten
- Abhängigkeits-Updates: Versions-Updates und die Referenz zur Renovate-Konfiguration
- CI-Runner: Runner-Isolation, kurzlebige ECS-VMs pro Job, Registrierung eigener Runner (BYO)
- OIDC-Token-Föderation: schlüssellose Cloud-Authentifizierung aus CI-Jobs
- Webhooks für Sicherheitswarnungen: Sicherheitsereignisse an deine Incident-Werkzeuge, einschließlich ServiceNow