Säkerhet och leveranskedja
fremforge levererar en säkerhetsstack för leveranskedjan i flera lager, som ingår i varje plan per användare. Kontrollerna körs vid fyra flaskhalsar: pushen (skanning efter hemligheter), pull requesten (beroendeskanning, SAST), artefakten (skanning av containrar, SBOM, SLSA-proveniens) och commiten (signering med SSH-nyckel eller GPG).
För den avtalsmässiga hållningen (BSI C5, ISO 27001/27017/27018, TISAX, GDPR, analysen av Schrems II / CLOUD Act), se frem.sh/trust.
Var fynden listas
Säkerhet › Fynd öppnas på Alla: alla öppna fynd från alla skannrar i en lista, de viktigaste först (kända utnyttjade, sedan allvarlighetsgrad, sedan de äldsta). Filtrera efter typ och allvarlighetsgrad; varje filterknapp visar hur många fynd den skulle lista. En rad länkar till fyndet på dess egen flik (SAST, Secrets, Beroenden, Container-images, Licenser, Skadlig kod), där dess detaljer och åtgärder finns. Samma lista finns i API:t som GET /orgs/{org}/security/findings; se Findings API.
Skanning efter hemligheter (Push-skydd)
Gitleaks körs som en pre-receive-hook vid varje push, innan commiten tas emot i repot, från ett nytt repos allra första push. Push-skydd är alltid på: ingen organisation och inget repo kan stänga av skanningen. Den enda inställningen är Skannerns felläge under Säkerhet › Policyer › Push-skydd: när själva skannern inte är tillgänglig avvisas en push antingen (standard) eller släpps igenom och registreras som en händelse om genomsläpp vid fel i granskningsspåret; hemligheter som kommer igenom under det fönstret fångas senare av den veckovisa genomsökningen av historiken. Se Push-skydd.
Vad som blockeras
Gitleaks matchar fler än 120 mönster med hög tillförlitlighet, bland annat:
| Kategori | Exempel på mönster |
|---|---|
| Inloggningsuppgifter för molnleverantörer | AWS-åtkomstnycklar och hemliga nycklar, JSON för GCP-tjänstkonton, anslutningssträngar för Azure Storage |
| Tokens för versionshantering | GitHub-PAT:er, GitLab-PAT:er, applösenord för Bitbucket |
| Betalningsnycklar | Hemliga nycklar för Stripe, signeringshemligheter för Stripe-webhooks |
| Kommunikationstokens | Slack-bottokens, Slack-webhook-URL:er, autentiseringstokens för Twilio |
| Privata nycklar | Privata RSA-/EC-/Ed25519-nycklar, PEM-kodade certifikat |
| Generiska hemligheter | Strängar med hög entropi som matchar mönstret generic-api-key, signeringshemligheter för JWT |
| Databas-URL:er | Anslutningssträngar för PostgreSQL, MySQL, MongoDB och Redis med inbäddade inloggningsuppgifter |
När en push blockeras
Pushen avvisas av fjärrservern med ett felmeddelande som innehåller den matchade mönstertypen och fil + radnummer. Commiten tas inte emot.
Ta inte bara bort filen och checka in igen. Hemligheten finns i git-historiken och kommer att blockeras igen. Rätt åtgärd:
- Rotera hemligheten direkt hos den tjänst som utfärdade den (AWS IAM, Stripes kontrollpanel osv.). Utgå från att den är komprometterad.
- Skriv om historiken för att ta bort hemligheten från varje berörd commit:
# 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>- Meddela alla som kan ha klonat repot före omskrivningen.
Flödet för undantag
Om en blockerad hemlighet är avsiktlig (en token för interna verktyg, en publicerad testinloggning) kan en ägare av organisationen lägga till ett avgränsat undantag:
- Gå till Säkerhet › Policyer › Push-skydd › Aktiva undantag → Nytt undantag.
- Ange: Repository (eller välj “Alla repon”), hemlighetsmönster (från Gitleaks regel-id) och motivering (fritext, loggas i granskningsspåret).
- Spara. Undantaget gäller från nästa push.
Alla undantag syns för organisationens övriga ägare och visas i granskningsloggen med aktör, tidsstämpel och motivering. Undantag kan återkallas när som helst från samma sida.
Åsidosätta ett avslag vid push
När en push avvisas av spärren för hemlighetsskanning ser utvecklaren orsaken till avslaget i terminalen: den matchade mönstertypen, sökvägen och radnumret.
En ägare av organisationen kan godkänna ett undantag under Säkerhet › Policyer › Push-skydd › Senaste avvisningar. Varje avslag visar: repot, den som gjorde commiten, tidsstämpeln, den typ av hemlighet som upptäcktes och den berörda sökvägen.
Omfång för undantag (väljs vid godkännande):
- Bara den här commiten, godkänner den specifika commit-SHA:n. Samma hemlighet i en framtida commit avvisas igen.
- Den här sökvägen, godkänner hemligheten på den sökvägen i alla framtida commits. Använd för testfixturer eller avsiktligt ofarliga platshållarvärden.
- Det här repot, godkänner alla förekomster av den här typen av hemlighet i repot. Använd sparsamt.
- Hela organisationen, godkänner typen av hemlighet i alla repon. Bara för fastställda falska positiva (t.ex. ett eget tokenformat som matchar ett regelmönster men inte är en riktig hemlighet).
En orsak till undantaget krävs (fritext). Den skrivs till granskningsloggen.
Utvecklaren måste pusha igen efter att ett undantag har beviljats. Undantaget godtar inte den avvisade pushen i efterhand.
Egen tillåtelselista
För att tysta kända falska positiva i ett visst repo (testfixturer, exempelnycklar i dokumentation, avsiktligt publika inloggningsuppgifter), lägg till en .gitleaks.toml i repots rot:
[extend]
useDefault = true
[[allowlists]]
description = "Test fixture AWS keys"
paths = ["tests/fixtures/.*", "docs/examples/.*"]
[[allowlists]]
description = "Example key in README"
regexes = ["AKIAIOSFODNN7EXAMPLE"]Tillåtelselistan utvärderas per repo i pre-receive-hooken. Den åsidosätter inte Push-skydd på organisationsnivå för mönster som inte finns i tillåtelselistan.
Kontroller av pull requests: bara nya fynd blockerar
Kontrollerna för beroenden (fremforge/dep-scan), SAST (fremforge/sast), licenser (fremforge/license-scan), hemligheter (fremforge/secret-scan) och images (fremforge/image-scan) skannar hela repot, men var och en underkänner en pull request bara för fynd som pull requesten för in. Ett fynd som den branch den mergas in i redan har blockerar inte, så en pull request som inte ändrar något sårbart kan mergas i ett repo som har öppna fynd. Statusen lyder då till exempel No new findings at or above critical; 8 already on main.
fremforge jämför pull requestens fynd med den senaste skanningen av den branchen av samma skanner. Ett fynd är detsamma när:
| Kontroll | Samma fynd när |
|---|---|
| Beroenden | ekosystem, paket och säkerhetsmeddelande stämmer. Att höja ett paket till en version som fortfarande har samma säkerhetsmeddelande är inte nytt; ett nytt säkerhetsmeddelande är det |
| SAST | regeln, filen och den matchade koden stämmer. Ett fynd som bara flyttats till en annan rad är inte nytt |
| Licenser | ekosystem, paket och licens stämmer |
| Hemligheter | regeln och de matchade raderna stämmer, jämförda med en nyckelad hash som skannern beräknar. Själva hemligheten sparas eller skickas aldrig. En hemlighet som bara flyttats till en annan rad är inte ny |
| Images | säkerhetsmeddelande, paket och fil stämmer |
- Kopior räknas: en andra förekomst av ett fynd som branchen har en gång är nytt.
- Allt som redan avgör om ett fynd blockerar gäller fortfarande först: blockeringsgraden, avfärdanden, undantag och licenspolicyn. Ett nytt skadligt paket eller en ny känd utnyttjad sårbarhet blockerar alltid, oavsett om din organisation har valt att ej nåbara fynd inte blockerar.
- Befintliga fynd döljs inte. De listas i pull requestens kommentar och under Säkerhet › Fynd, räknas mot din SLA och ger varningar som tidigare.
Branchen skannas efter en push till din standard-branch, högst en gång i timmen, och den senaste pushen vinner. Ett fynd som nått branchen sedan dess senaste skanning räknas därför fortfarande som pull requestens fram till nästa skanning; ett nytt fynd misstas aldrig för ett befintligt. När det ännu inte finns någon skanning av branchen blockerar varje fynd, som tidigare, och pull requestens skanning begär en. När branchens första skanning har registrerats skannar fremforge själv om de öppna pull requests mot den vars kontroll har misslyckats under tiden (högst 20 per repo, de senast uppdaterade, per kontroll), så att de blir gröna, eller förblir röda bara för det de lägger till, utan omkörning eller ny push. En pull request mot en annan branch än standard-branchen räknar alltid varje fynd.
Beroendeskanning
osv-scanner kontrollerar varje pull request, och standard-branchen efter varje push, mot OSV:s databas över säkerhetsmeddelanden. Den läser låsfiler och manifest offline, utan att lösa upp beroenden, och publicerar commit-statusen fremforge/dep-scan och en kommentar i pull requesten. Fynden listas under Säkerhet › Fynd › Beroenden.
Inställningar och vad som blockerar en merge
Under Säkerhet › Fynd › Beroenden kan en ägare stänga av beroendeskanningen för organisationen och ange blockeringsgraden: den lägsta allvarlighetsgrad som underkänner fremforge/dep-scan. Standard är critical. I en pull request blockerar bara fynd som pull requesten för in. Om en underkänd kontroll faktiskt blockerar mergen beror på kontrollens läge (enforce, audit eller off) under Säkerhet › Prioritera › Pull request-kontroller, och på att kontrollen krävs av branch-skyddet.
För att ett fynd inte ska blockera, åtgärda det eller registrera ett undantag. En organisation kan också välja att fynd som inte är nåbara inte ska blockera. Det valet omfattar aldrig skadliga paket eller kända utnyttjade sårbarheter.
Pull requests med fixar
Två saker öppnar pull requests med fixar för sårbara beroenden:
- Säkerhetsfixar: Öppna fix-PR på ett fynd (eller den automatiska policyn) öppnar en pull request per paket till den lägsta version som åtgärdar alla öppna säkerhetsmeddelanden. Se Säkerhetsfixar.
- Uppdateringar av beroenden: Renovates egna sårbarhetsvarningar, för organisationer som är anslutna till Uppdateringar av beroenden. Varje ansluten organisations repon körs varje natt, och Kör nu startar en körning utanför schemat.
Manifest som stöds
De låsfiler och manifest som läses, och vilka ekosystem som får pull requests med fixar och nåbarhet, listas på ett ställe: Ekosystem som stöds.
Skanning av container-images
Trivy skannar varje OCI-image som pushas till fremforges paketregister. Resultaten visas under Säkerhet › Fynd › Container-images.
Trivy körs centralt, utan något att installera: trivy fs i varje pull request (statusen fremforge/image-scan, som bara blockerar på fynd som pull requesten för in), trivy config som en egen kontroll (fremforge/iac-scan), och trivy image på varje image i ditt register och varje image som hämtas via beroendeproxyn, med regelbunden omskanning. Fynd om felkonfiguration från trivy config har en egen flik, Säkerhet › Fynd › IaC; se IaC-skanning.
Varje fynd innehåller:
| Fält | Beskrivning |
|---|---|
| CVE-id | t.ex. CVE-2024-12345 med länk till NVD |
| Allvarlighetsgrad | CRITICAL / HIGH / MEDIUM / LOW / UNKNOWN |
| Berört paket | Paketnamn och installerad version |
| Fixversion | Första version som åtgärdar CVE:n, om en sådan finns |
| EPSS-poäng | Sannolikhet enligt Exploit Prediction Scoring System |
Skanning i ditt eget arbetsflöde (valfritt)
Det finns inget att aktivera: skanningarna ovan körs utan någon arbetsflödesfil. Om du också vill att ditt eget bygge ska misslyckas innan det pushar en image kan du själv köra Trivy i ett arbetsflöde, till exempel:
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 }}Sätt --severity till den tröskel där du vill att bygget ska misslyckas.
Varför det här exemplet låser med SHA och digest
En tidigare version av den här sidan använde aquasecurity/trivy-action@master och actions/checkout@v4. Båda är föränderliga referenser, och den 19 mars 2026 slutade den skillnaden vara teoretisk: en angripare skrev om taggen v0.69.4 för trivy-action så att den pekade på en skadlig release. Pipelines som refererade till actionen via tagg eller branch hämtade angriparens kod vid nästa körning, och de inloggningsuppgifter den samlade in användes för att publicera två bakdörrade releaser av ett annat projekt till PyPI.
En tagg är en pekare som någon annan kan flytta. En commit-SHA och en image-digest är det inte.
- Actions: referera till den fullständiga commit-SHA:n på 40 tecken, inte
@v4och aldrig@master. Renovate föreslår fortfarande uppgraderingar; den skriver om SHA:n och dess kommentar tillsammans. - Skannrar: föredra verktygets container-image låst via digest framför en wrapper-action. Färre rörliga delar, och det du låste är det som körs.
- Spegla det du är beroende av: fremforge når inget uppströmsregister från en produktionsväg; varje image kommer från vårt eget register, låst via digest.
Incidenten krävde ingen åtgärd på fremforges sida: vi har aldrig använt trivy-action, och vår egen Trivy är en image låst via digest. Projektet som komprometterades hade installerat Trivy från apt utan låst version. Låsning är inte glamoröst, och det är den kontroll som skulle ha stoppat det.
Generering av SBOM
Syft genererar CycloneDX 1.6 SBOM:er för varje container-image som pushas till registret och för varje release-tagg, med en SPDX 2.3-tvilling från samma körning (efter bästa förmåga; se SBOM:er). Uppladdningar tas emot som CycloneDX 1.5–1.7 eller SPDX 2.x/3.x. Generering av SBOM är på som standard för varje organisation, det finns inget steg för att aktivera den.
Ladda ned
Från paketregistrets gränssnitt: öppna imagen → fliken Attestations → Download SBOM.
Via API:t:
curl -H "Authorization: token <your-pat>" \
https://frem.sh/_app/api/v1/orgs/<org>/findings/attestations \
-o sbom.jsonSBOM:en är CycloneDX-JSON och innehåller alla transitiva beroenden med deras PURL-identifierare, licensuttryck och versionsintervall.
SAST
OpenGrep kör statisk analys centralt i varje pull request och på standard-branchen. Den är på som standard för varje organisation. Fynd publicerar statusen fremforge/sast och en kommentar i pull requesten, och listas under Säkerhet › Fynd › SAST. Språk, regler och inställningar finns på sidan SAST. Egna regler stöds inte.
Signerade commits
fremforge stöder två sätt att signera commits, och båda verifieras inbyggt av Forgejos märke “Verified” i webbgränssnittet:
- Signering med SSH-nyckel (rekommenderad standard), återanvänder den SSH-nyckel du redan har registrerat för
git push. Inget nytt nyckelmaterial att hantera, inget underbiträde från tredje part, inget beroende av en transparenslogg, ingen transit genom amerikansk jurisdiktion. Det här är fremforges standardväg. (För nya organisationer som skapats efter 2026-05-22 är SSH-transport avstängd som standard; signering med SSH-nyckel fungerar ändå oavsett om du använder SSH eller HTTPS förgit push.) - GPG-signering, klassisk OpenPGP-nyckel. Använd det om din organisation redan har en policy för hantering av GPG-nycklar (smartkort, HSM, central nyckelserver).
Nyckellös commit-signering FINNS hos fremforge via en egenhostad Sigstore-stack: Fulcio-CA på sign.frem.sh och en TSA på tsa.frem.sh, båda på T Cloud Public i eu-de med rot-CA:n i DEW KMS. Linux Foundations publika Sigstore-instans används INTE: fremforge skickar ingen signerings- eller verifieringstrafik via sigstore.dev / rekor.sigstore.dev, så hållningen att inte använda underbiträden i USA håller. Det finns avsiktligt ingen Rekor / transparenslogg för commit-signering; tidsstämpling förankrad i TSA:n är i stället integritetsmekanismen. (fremforge kör en transparenslogg, på log.frem.sh, men den täcker bara attesteringar av byggproveniens och är inte Rekor; se transparenslogg. De två är oberoende: en signerad commit förs inte in i den.) Se nyckellös commit-signering för konfigurationen av gitsign på kundsidan. (Obs: bygg- och release-proveniens är en separat väg: fremforges SLSA-proveniens signeras med en Ed25519-nyckel som plattformen har, INTE via Fulcio. Se avsnittet om SLSA nedan.)
Signering med SSH-nyckel, konfiguration
# 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_signersLägg till motsvarande SSH-nyckel i din användarprofil på fremforge under Inställningar → SSH/GPG-nycklar → Lägg till nyckel (Forgejo godtar samma nyckel för både push och signering, eller en separat nyckel bara för signering).
Vid nästa git commit signerar Git med din SSH-nyckel. Forgejo verifierar signaturen mot organisationens allowedSignersFile (som härleds automatiskt från medlemmarnas registrerade signeringsnycklar) och visar märket “Verified” i webbgränssnittet.
Verifiera
git log --show-signature HEAD~5..HEADVarje commit visar en rad Good "git" signature for <user>@<org>. Forgejos webbgränssnitt visar ett grönt märke “Verified” bredvid commit-hashen.
GPG-signering, konfiguration
Om du föredrar GPG fungerar det vanliga flödet med git config --global commit.gpgsign true + user.signingkey <gpg-key-id> oförändrat; ladda upp din publika nyckel under Inställningar → SSH/GPG-nycklar → Lägg till nyckel. Forgejo verifierar och visar märket “Verified”.
Kräv signerade commits (organisationspolicy)
Tvinga fram signerade commits på skyddade branches med Commit-signering under Säkerhet › Policyer › Repo-regler (se Branch-skydd), eller per repo under Repot → Inställningar → Grenar → <branch> → Kräv signerade incheckningar. Osignerade commits avvisas vid push. Både SSH-nyckel- och GPG-signaturer uppfyller kravet.
SLSA-proveniens
Bygg- och release-proveniens genereras på serversidan av fremforge: det finns ingen arbetsflödesfil i dina repon och inga runner-minuter förbrukas. Vid varje händelse release.published och package i Forgejo hashar fremforges api varje publicerad tillgång, bygger ett in-toto Statement inslaget i ett DSSE-kuvert, signerar det med plattformens byggnyckel, sparar attesteringen i lagring i EU som är avgränsad till din tenant, och laddar upp kuvertet bredvid tillgången som <asset>.intoto.jsonl. Den listas också i konsolen under Säkerhet › Komponenter › Byggattesteringar.
Den fristående arbetsflödesmallen
slsa-provenance.yamlsom kunderna tidigare förväntades anropa från sina byggpipelines avvecklades 2026-06-16: en granskning av hela flottan hittade noll anropare, och proveniens genereras nu automatiskt på serversidan utan någon koppling från kunden.
EU-suverän från grunden. Signeringsnycklarna, förtroenderoten, certifikatutfärdaren och lagringen finns alla inom fremforges konto på T Cloud Public (eu-de). Det finns ingen publik Sigstore, ingen Rekor och inget uppslag mot tuf-repo-cdn.sigstore.dev någonstans i verifieringsvägen.
Två signaturer, båda på varje attestering. Den primära är en Ed25519-nyckel som plattformen har, och det är den som receptet Verifiera nedan kontrollerar: vanliga curl / jq / openssl / python3, inget annat behövs. Dessutom signeras varje attestering nyckellöst mot fremforges egenhostade Sigstore: ett kortlivat Fulcio-certifikat vars SAN är byggets källidentitet, tidsstämplat av fremforges RFC3161-TSA. Båda beskrivs i förtroenderoten, som innehåller fulcio_root_ca_pem, tsa_cert_chain_pem, certificate_oidc_issuer och certificate_identity_pattern, så att även den nyckellösa signaturen kan verifieras offline.
Det finns fortfarande ingen transparenslogg: förtroenderoten säger "transparency_log": "none", så den nyckellösa signaturen valideras mot RFC3161-tidsstämpeln i stället för mot Rekor (cosign --use-signed-timestamps --insecure-ignore-tlog). Vad det ger och inte ger dig beskrivs i §Oberoende verifierbarhet.
(fremforges commit-signering är en separat väg med samma egenhostade CA; se nyckellös commit-signering.)
Förtroenderot: https://www.frem.sh/.well-known/slsa-trust-root.json, en liten JSON-fil som listar byggarens publika nycklar. Dess struktur är specifik för fremforge:
{
"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 är den aktiva signeringsnyckeln sedan nyckelbytet 2026-09-12. fremforge-slsa-builder-v1 signerade allt före det och behålls bara för verifiering: dess valid_to är det ögonblick då den slutade signera, inte ett utgångsdatum för de signaturer den gjorde. Verifiera alltid mot den nyckel som anges av keyid i det kuvert du har, aldrig mot ett fast namn.
Lås filen (eller en kopia av den) i din CI för att låsa förtroendet under verifieringsfönstret. Nyckelbyten aviseras 30 dagar i förväg enligt trust-sidans §regel om avisering av underbiträden; en signatur slutar verifieras bara om dess nyckel tas bort från trusted_keys.
Kuvertets och statementets struktur (DSSE-kuvert som omsluter ett in-toto Statement):
| Fält | Värde |
|---|---|
kuvertets payloadType | application/vnd.in-toto+json |
kuvertets signatures[0].keyid | fremforge-slsa-builder-v2 för allt som signerats sedan 2026-09-12, fremforge-slsa-builder-v1 före (matchar en trusted_keys[].kid i förtroenderoten) |
Statement _type | https://in-toto.io/Statement/v1 |
Statement predicateType | https://slsa.dev/provenance/v1 |
predicate.buildDefinition.buildType | Vilken väg som skapade statementet; se §Vad varje buildType betyder. En av …/buildtypes/forgejo-action-run/v1, …/container-image/v1, …/forgejo-release/v1, …/forgejo-package/v1 |
predicate.buildDefinition.internalParameters.run_id | Den Actions-körning som skapade artefakten. Finns i byggproveniens; saknas i ett publiceringsstatement |
predicate.buildDefinition.internalParameters.attestation_kind | publication när statementet observerar publicerade byte i stället för ett bygge. Saknas i byggproveniens |
predicate.buildDefinition.externalParameters.repository | <org>/<repo> |
predicate.buildDefinition.externalParameters.ref | Git-ref:en (t.ex. refs/tags/v1.2.3) |
predicate.runDetails.builder.id | https://frem.sh/runner-controller/v1 (styrs av servern och skrivs över vid signering: en komprometterad runner kan inte förfalska den) |
subject[].name / subject[].digest.sha256 | Tillgångens filnamn och den SHA-256 som fremforge beräknade för den |
Vad det här är, exakt. Fram till 2026-09-11 påstod det här avsnittet SLSA Build L2. Vi härledde det på nytt mot specifikationen v1.0, påståendet höll inte, och vi drog tillbaka det. Den lucka som gjorde det osant, proveniens som inte beskrev något bygge, stängdes 2026-09-12. Vi anger fortfarande ingen nivå, av ett skäl som ges i slutet av det här avsnittet.
Två olika statements signeras nu med samma nyckel, och du kan skilja dem åt enbart utifrån kuvertet.
Vad varje buildType betyder
buildType slutar med | Vad den intygar | Anger en byggkörning? |
|---|---|---|
forgejo-action-run/v1 | fremforges kontrollplan läste den här körningens uppladdade artefakter från byggplattformens egna poster, hämtade byten och hashade dem | Ja: run_id |
container-image/v1 | En image som pushats av en körning, attesterad med körningens identitet hämtad från en kortlivad OIDC-token som fremforge utfärdade för jobbet | Ja: run_id, kryptografiskt bunden |
forgejo-release/v1 | En publicerad release-tillgång. Ärver ett byggets commit när exakt samma digest redan har attesterats som byggd; se nedan | Bara när den kopplats |
forgejo-package/v1 | En paketversion som dök upp i registret | Nej |
Byggproveniens (de två första) beskriver hur artefakten skapades: den namnger körningen, arbetsflödet och commiten, och körningens identitet kommer från plattformen snarare än från något som bygget påstod om sig självt.
Publiceringsattestering (de två sista, okopplade) säger något snävare och säger det uttryckligen: attestation_kind: publication. Den betyder fremforge observerade att exakt detta byteinnehåll publicerades här, och signerar den observationen med en nyckel som bygget inte kan nå. Det är en verklig, kontrollerbar egenskap. Det är inget påstående om hur artefakten byggdes, och den låtsas inte längre vara det: där ingen källcommit är känd är commit_sha den bokstavliga strängen no-source-commit i stället för ett värde som ser ut som en git-commit. Om du ser 40 hextecken där är det en verklig commit.
Kopplingen via digest. En release-tillgång vars sha256 redan har en byggattestering ärver det byggets commit, och statementet pekar på det (source_commit_from: digest-join, plus byggattesteringens id och objektnyckel). Länken är artefaktens digest, så “samma byte” är ett kryptografiskt faktum snarare än en namnkonvention: att publicera en artefakt senare ändrar inte vad som skapade den. Ett uttryckligt target_commitish på releasen vinner alltid över slutledningen.
Plattformsegenskaperna bakom allt detta:
- Körningar kan inte påverka varandra. En tillfällig ECS-VM per jobb, som förstörs när jobbet avslutas, med ett ENI per VM och en inloggningsuppgift per runner som upphör med jobbet; se §Modell för kärnisolering.
- Signeringsnyckeln är utom räckhåll för bygget. Den finns i api:et, aldrig på en runner, och byggidentiteten skrivs över på serversidan vid signering, så ett komprometterat bygge kan inte förfalska den.
- Ingen koppling från kunden för de webhook-drivna vägarna. Inget att anropa från ett arbetsflöde, och inga runner-minuter förbrukas.
Varför vi fortfarande inte anger någon SLSA-nivå. De två isoleringsegenskaperna är vad SLSA v1.0 Build L3 kräver, och byggproveniensen beskriver nu ett verkligt bygge. Vi vill hellre att du läser tabellen ovan och avgör vad den är värd än att du godtar en siffra från den part som granskas. (Hermetiska byggen, reproducerbara artefakter och transparensloggar anges ibland som krav för L3. Det är de inte: det var begrepp i SLSA v0.1 L4 och förekommer inte på någon nivå i v1.0, som listar dem under framtida riktningar.)
Det som fortfarande saknas är en transparenslogg, vilket är en annan axel än någon nivå; se §Oberoende verifierbarhet.
Verifiera
Använd inte uppströms
slsa-verifier verify-artifact --trusted-root. Det verktyget har ingen flagga--trusted-rootförverify-artifactoch kan inte läsa fremforges egna JSON för förtroenderoten eller ett DSSE-kuvert med ren Ed25519; det förväntar sig ett bundle i Sigstore-stil. fremforges proveniens är ett vanligt DSSE-kuvert signerat med Ed25519, så du verifierar det direkt medcurl/jq/openssl/python3, som alla finns som standard i distributioner, utan något verktyg från fremforge.
Verifieringen har tre oberoende kontroller: DSSE-signaturen är giltig för den publicerade byggnyckeln, tillgångens SHA-256 stämmer med det som signerades, och statementet anger rätt repo och byggare.
1. Hämta förtroenderoten och extrahera byggarens publika nyckel
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 en attestering som signerats före 2026-09-12, välj fremforge-slsa-builder-v1 i stället. jq -r '.signatures[0].keyid' <asset>.intoto.jsonl talar om vilken.
2. Verifiera DSSE-signaturen, tillgångens digest och statementets fält
Kör från en katalog som innehåller den nedladdade tillgången och dess kuvert <asset>.intoto.jsonl. Sätt ASSET till tillgångens filnamn:
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")
PYOm alla tre kontrollerna skriver ut OK är tillgången exakt de byte som fremforge attesterade, kuvertet signerades med den publicerade byggnyckeln, och statementet anger fremforges byggare. Verifieringen misslyckas (slutkod som inte är noll) om signaturen inte valideras mot builder.pub (manipulerat kuvert eller fel/roterad nyckel), om tillgångens SHA-256 inte stämmer med det signerade subjectet (manipulerad tillgång), eller om builder.id / predicateType inte stämmer.
keyidi kuvertet (fremforge-slsa-builder-v2för allt som signerats sedan 2026-09-12) måste stämma med denkiddu valde ur förtroenderoten; ett kuvert som kontrolleras mot den andra nyckeln misslyckas medDSSE signature FAILED. Om fremforge roterar signeringsnyckeln, hämta förtroenderoten igen och välj denkidsom kuvertet anger.
Varför inte Sigstore vid lanseringen
Sigstores publika stack med Fulcio + Rekor + Trillian drivs av OpenSSF (Linux Foundation), en organisation registrerad i USA. Att använda den skulle skicka varje byggs metadata till infrastruktur i USA, vilket är oförenligt med fremforges åtagande om enbart underbiträden i EU i DPA bilaga B. fremforge kör sin egen egenhostade Fulcio + TSA på sign.frem.sh / tsa.frem.sh på T Cloud Public eu-de, som används för både commit-signering (se nyckellös commit-signering) och, sedan 2026-06-21, som den andra signaturen på byggproveniens. Det som avsiktligt saknas är den publika Sigstore-driftsättningen och Rekor.
Varje attesterings SHA-256 hashkedjas också in i plattformens granskningslogg, vars förankringar ligger i en OBS Object Lock i läget COMPLIANCE i 3 år. Det är en användbar intern kontroll och den är inte en ersättning för en transparenslogg, så vi beskriver den inte längre som likvärdigt manipulationsskydd. Två skäl, båda konkreta: förankringen kan inte läsas av dig, så den kan inte ingå i din verifiering; och OBS Object Lock skyddar varje objektversion: uppmätt 2026-08-14 kan en nyare version skugga en låst nyckel och en borttagningsmarkör dölja den för vanliga läsningar, och båda är åtgärder som operatören kan utföra. Se §Oberoende verifierbarhet för vad förtroendemodellen ger och inte ger dig.
Oföränderliga releaser
Organisationens ägare kan göra varje release frusen vid publicering: dess tagg, målcommit och tillgångar kan aldrig ändras igen (titel och anteckningar går fortfarande att redigera), en borttagen releases taggnamn kan aldrig återanvändas, och container-taggar kan också frysas (taggar i stil med v1.2.3, eller alla utom en lista). Varje oföränderlig release får ett signerat in-toto-releasestatement som namnger varje tillgång via SHA-256 och taggen via commit, och som kan kontrolleras med fremforge release verify. Se Oföränderliga releaser och releaseattestering.
Oberoende verifierbarhet
Att flytta ett repo, en runner och ett artefaktlager till Tyskland besvarar frågan var datan finns. Det besvarar inte i sig om granskningsspåret kan litas på utan att man litar på den operatör som skapade det. Det är olika egenskaper, och vi vill hellre vara tydliga med vilken av dem vi levererar i dag.
Vad du får utan att lita på oss vid verifieringen. Attesteringen följer med artefakten, <asset>.intoto.jsonl och bredvid den <asset>.sigstore.json, så du har båda halvorna. Byggarens publika nyckel, Fulcio-rot-CA:n och TSA-kedjan finns alla i en statisk förtroenderot som du uppmuntras att låsa en kopia av i din egen CI. Verifieringen räknar om artefaktens digest och kontrollerar signaturer lokalt, med curl / jq / openssl / python3. Inget API hos fremforge kontaktas, och inga verktyg från fremforge krävs. När du har låst roten kan en senare ändring på vår sida inte i efterhand ändra vad din pipeline godtar.
Vad du inte får, sagt rakt ut. Det finns nu en append-only transparenslogg för attesteringar av proveniens på log.frem.sh, men den ger ännu inte den egenskap man oftast menar med ordet. Konkret:
- Loggen gör att vi inte kan skriva om historiken i tysthet: om du har ett inkluderingsbevis och vi senare ändrar eller tar bort din attestering slutar ditt bevis att verifieras och du kan påvisa motsägelsen. Det är verkligt och det är nytt.
- Den är delvis motståndskraftig mot delade vyer, från och med den 16 september 2026. Ett oberoende vittne,
witness.stagemole.eu, som drivs av Mullvad VPN AB, medsignerar varje checkpoint, så det finns nu en tredje part som skulle märka om vi signerade något ovanligt. Det är den egenskap som den här punkten alltid har handlat om, och den saknas inte längre. Ett vittne är inget kvorum: det höjer kostnaden för att visa dig ett annat träd än alla andra, men kan inte upptäcka samverkan mellan loggen och dess enda vittne, och operatören kallar instansen staging. Observera också att de enda EU-vittnena av produktionskvalitet, Glasklar Teknik, Mullvad VPN och Tillitis, har samma moderbolag, Amagicom AB; fler av dem skulle ge tillgänglighet, inte oberoende. - RFC3161-tidsstämpeln som delvis ersätter en logg utfärdas av vår TSA, och förtroenderoten distribueras från vår domän. Låsning hanterar det senare för den som låser vid introduktionen; det hanterar inte förtroendet vid första kontakten.
- Vår interna förankring av granskningskedjan kan inte läsas av dig, så den kan inte ingå i din verifiering.
Om operatören är den part du behöver kunna ställa till svars, vilket är rätt fråga för försvar och kritisk infrastruktur, så är det som stänger luckan en transparenslogg med externa vittnen, en oberoende tidsstämplingstjänst och reproducerbara byggen som du kan bygga om och jämföra utan vår inblandning. Den första av de tre finns nu och beskrivs ovan, med sina begränsningar; tidsstämplingstjänsten och de reproducerbara byggena finns inte. Observera att en egenhostad logg inte skulle stänga den: en logg som vi driver är en logg som vi kontrollerar. Oberoende verifierbarhet kräver en utomstående part, och den ärliga avvägningen är att om det görs via den publika Sigstore-loggen skulle artefakters digest, repoidentiteter och tidpunkter för releaser publiceras utanför EU.
Vi vill hellre avgränsa det mot ett verkligt krav än gissa oss till ett. Om det här är ett krav i en upphandling för dig, berätta vilka av de egenskaperna du behöver och med vilken tidsplan, så återkommer vi med en daterad plan snarare än ett påstående.
Modell för kärnisolering
Varje jobb får en egen tillfällig virtuell ECS-maskin på T Cloud Public i eu-de, och den förstörs när jobbet är klart. VM:arna är enkeluppgift: runnern tas bort efter sin enda uppgift och dess inloggningsuppgift per runner upphör med jobbet, så en VM kan inte betjäna ett andra jobb ens i samma körning av arbetsflödet. (Före 2026-07-30 kunde efterföljande jobb för samma commit dela en VM; det kan de inte längre.) Isoleringsegenskaperna:
| Egenskap | Detalj |
|---|---|
| Delning av kärna | Ingen kärna delas mellan commits eller mellan organisationer: varje commit får en egen VM. Jobb för samma commit kan dela den VM:en, så behandla en pipeline som en förtroendedomän |
| Beständighet | VM:en förstörs när commitens jobb är klara; inget filsystemstillstånd överlever till en annan commit eller en annan organisation. Tillstånd kan följa med mellan jobb för samma commit: ett byggsteg som kör beroendekod från tredje part delar maskinen med ett senare driftsättningssteg, så avgränsa varje inloggningsuppgift som en driftsättning behöver till den pipelinen |
| Åtkomst till plattforms-API | VM:en har ingen IAM-agency kopplad; den identitet som härleds från metadata har inga roller, så OTC-API:t kan inte nås inifrån |
| Nätverk | Security Group per VM med tillåtelselista bara för utgående trafik; runners kan inte ta emot inkommande anslutningar |
| T Cloud Public-metadata | Blockeras av SSRF-proxyn för utgående trafik; runnerkod kan inte nå instansens metadataslutpunkt med privilegierat scope |
Se CI-runners för den fullständiga isoleringsspecifikationen och registrering av egna runners.
Isolering mellan tenants
Varje tenant på fremforge är ett separat nycklat namnområde i plattformen. Isoleringskedjan går från URL:en genom middleware, databasens frågelager, lagringslagret och granskningskedjan.
| Lager | Mekanism | Vad som stoppar åtkomst mellan tenants |
|---|---|---|
| URL-routning | Middleware för /<slug>/_admin/* läser in tenanten från slugen vid varje förfrågan | En användare med en session för organisation A som klistrar in organisation B:s URL avvisas vid medlemskontrollen, inte på grund av en inaktuell cookie |
| Medlemskontroll | Uppslag i Forgejos Owners-team mot slugen, verifierat vid varje förfrågan | Api:et litar aldrig på ett inkommande påstående om vilka organisationer användaren tillhör |
| Databas | Varje tabell med tenant-nyckel har en främmande nyckel tenant_id; varje läsning OCH varje skrivning filtrerar på den | SELECT / UPDATE / DELETE utan tenant_id är strukturellt omöjligt i de vägar som granskningen flaggat |
| Signerade tokens | Varje kortlivad signerad URL (magisk länk för fakturering, ångra uppsägning, OIDC-state) har tenant-id:t i sitt innehåll; verifieringssidan kontrollerar mot URL:ens slug | En token som utfärdats för tenant A avvisas om den spelas upp mot tenant B:s URL |
| Lagring (OBS) | SBOM:er, attesteringar och objekt i granskningsloggen har alla nyckeln <artifact>/<tenant_id>/...; utfärdandet av signerade URL:er kontrollerar tenant.id === row.tenant_id igen före utfärdandet | En direkt försignerad OBS-URL som är bunden till tenant A kan inte skrivas om för att läsa tenant B |
| Granskningskedja | Manipulationssäker hashkedja nycklad per tenant; en tenants tillägg ändrar aldrig en annan tenants kedjehuvud | Ett brott i tenant A:s kedja (t.ex. ett försök till borttagning i efterhand) påverkar inte integriteten i tenant B:s kedja |
| Operatörsåtkomst | Personal (fremverk) som tittar på kunddata loggas i kundens granskningskedja som en operatörsåtgärd; tenantens administratörer kan se operatörsbesök i sin egen granskningslogg | Ingen tyst åtkomst till kunddata för personalen |
Fördröjning vid återkallande från Owners-teamet
Api:et cachar en kunds medlemskap i Forgejos Owners-team i upp till 5 minuter för att hålla sidladdningar under 50 ms (alternativet är ett anrop till Forgejos API vid varje förfrågan, vilket skulle göra varje sida 100–200 ms långsammare). Avvägningen:
- När du lägger till någon i Owners på Forgejo-sidan får personen administratörsåtkomst inom 5 minuter.
- När du tar bort någon från Owners på Forgejo-sidan förlorar personen administratörsåtkomsten inom 5 minuter, inte omedelbart.
För de flesta åtgärder är fördröjningen acceptabel: en tidigare ägare kan inte skapa nya resurser i Forgejo (Forgejos egen kontroll sker omedelbart), och varje privilegierad åtgärd via fremforges administrationsgränssnitt registreras i tenantens granskningslogg med aktörens användarnamn, så aktivitet efter återkallandet förblir spårbar.
Om du behöver omedelbart återkallande (t.ex. som en del av incidenthanteringen efter att en inloggningsuppgift komprometterats) kan jourhavande operatör på begäran sätta cachens TTL till noll för din tenant: öppna ett ärende på support@frem.sh med ämnesprefixet [urgent-revocation] och användarnamnet + tenantens slug. Standard-SLA: under 15 minuter under kontorstid, under 60 minuter utanför. Samma effekt uppnås genom att återkalla användarens Forgejo-session (Forgejo Owner → User profile → Sign out), vilket ogiltigförklarar den sessionscookie som api:et läser, utan att någon åtgärd behövs på fremforges sida.
Plattformens säkerhetsåtaganden
| CVE-allvarlighetsgrad | SLA för patchning (från uppströms åtgärdad release) |
|---|---|
| Kritisk (CVSS ≥ 9,0) | 48 timmar |
| Hög (7,0–8,9) | 72 timmar |
| Medel (4,0–6,9) | 7 dagar |
| Låg | Nästa schemalagda underhållsfönster |
SLA:n för patchning är avtalsmässigt publicerad och citeras ordagrant i DPA:ns säkerhetsbilaga.
Granskningsloggens integritet: manipulationssäker hashkedja förankrad i WORM-lagring i T Cloud Public OBS varannan minut; integritetskontroll i FunctionGraph varje timme; brott i kedjan larmar jourhavande.
Felsökning
Pushen blockerades men jag hittar inte hemligheten i diffen.
Hemligheten kan finnas i en fil som ändrats men vars fullständiga innehåll inte syns i den vanliga diffen. Kör Gitleaks lokalt för att hitta den exakta platsen:
pip install gitleaks # or brew install gitleaks
gitleaks detect --source . --verboseIngen pull request med fix för en känd CVE.
Kontrollera att paketets ekosystem och manifest får pull requests med fixar (Ekosystem som stöds) och att säkerhetsmeddelandet har en åtgärdad version. Renovate körs varje natt för en ansluten organisation; Säkerhet › Åtgärda › Uppdateringar av beroenden → Kör nu startar en körning utanför schemat, och Öppna fix-PR på fyndet begär en säkerhetsfix direkt. Se När ingen pull request öppnas.
Nedladdning av SBOM returnerar 404.
SBOM:er genereras bara för images som pushats efter att generering av SBOM aktiverats. Images som pushats innan har ingen SBOM. Pusha en ny tagg för att generera en.
SSH-signering misslyckas med ssh-keygen: gpg failed to sign the data.
Se till att git config gpg.format är satt till ssh (inte standardvärdet openpgp) och att user.signingkey pekar på en fil med en publik nyckel. På macOS, kontrollera också att ssh-keygen finns i $PATH (Apples medföljande ssh-keygen fungerar; om du har installerat OpenSSH via Homebrew, se till att den skuggar korrekt).
Korsreferenser
- Push-skydd, listan över undantag och felläget
- Uppdateringar av beroenden, versionsuppdateringar och referensen för Renovates konfiguration
- CI-runners, isolering av runners, tillfälliga ECS-VM:ar per jobb, registrering av egna runners
- OIDC-tokenfederation, nyckellös molnautentisering från CI-jobb
- Webhooks för säkerhetsvarningar, säkerhetshändelser till dina verktyg för incidenthantering, inklusive ServiceNow