Gå til hovedinnhold

Sikkerhet og forsyningskjede

fremforge leverer en lagdelt sikkerhetsstabel for forsyningskjeden som er inkludert i alle planer med betaling per bruker. Kontrollene kjører på fire knutepunkter: pushen (skanning etter secrets), pull requesten (avhengighetsskanning, SAST), artefaktet (containerskanning, SBOM, SLSA-proveniens) og commiten (signering med SSH-nøkkel eller GPG).

For den kontraktsmessige posisjonen (BSI C5, ISO 27001/27017/27018, TISAX, GDPR, analysen av Schrems II og CLOUD Act), se frem.sh/trust.

Hvor funn er oppført

Sikkerhet › Funn åpner på Alle: alle åpne funn fra alle skannere i én liste, de viktigste først (kjent utnyttet, deretter alvorlighetsgrad, deretter de eldste). Filtrer på type og alvorlighetsgrad; hver brikke viser hvor mange funn den ville listet. En rad lenker til funnet i sin egen fane (SAST, Secrets, Dependencies, Container images, Licences, Malware), der detaljene og handlingene står. Den samme listen finnes i API-et som GET /orgs/{org}/security/findings; se Findings-API-et.

Skanning etter secrets (push-beskyttelse)

Gitleaks kjører som en pre-receive-hook ved hver push, før commiten tas inn i repositoryet, helt fra et nytt repositorys første push. Push-beskyttelse er alltid på: ingen organisasjon eller repository kan slå av skanningen. Den eneste innstillingen er skannerens feilmodus under Sikkerhet › Policyer › Push-beskyttelse: når selve skanneren ikke er tilgjengelig, blir en push enten avvist (standard) eller sluppet gjennom og registrert som en allow-on-error-hendelse i revisjonssporet; secrets som slipper gjennom i det tidsvinduet, fanges senere av den ukentlige gjennomgangen av historikken. Se Push-beskyttelse.

Hva som blokkeres

Gitleaks matcher over 120 mønstre med høy treffsikkerhet, blant annet:

KategoriEksempler på mønstre
Legitimasjon for skyleverandørerAWS access/secret keys, JSON for GCP-tjenestekontoer, tilkoblingsstrenger for Azure Storage
Tokener for kildekodesystemerGitHub-PAT-er, GitLab-PAT-er, app-passord for Bitbucket
BetalingsnøklerStripe secret keys, signeringssecrets for Stripe-webhooks
KommunikasjonstokenerSlack-bottokener, URL-er for Slack-webhooks, Twilio-autentiseringstokener
Private nøklerPrivate RSA/EC/Ed25519-nøkler, PEM-kodede sertifikater
Generiske secretsStrenger med høy entropi som matcher mønsteret generic-api-key, signeringssecrets for JWT
Database-URL-erTilkoblingsstrenger for PostgreSQL, MySQL, MongoDB og Redis med innebygd legitimasjon

Når en push blokkeres

Pushen avvises på remoten med en feilmelding som inneholder mønstertypen som ble matchet, og fil og linjenummer. Commiten blir ikke tatt inn.

Ikke bare slett filen og committ på nytt. Secreten ligger i Git-historikken og vil bli blokkert igjen. Riktig utbedring:

  1. Roter secreten umiddelbart hos tjenesten som utstedte den (AWS IAM, Stripe-dashbordet osv.). Gå ut fra at den er kompromittert.
  2. Skriv om historikken for å fjerne secreten fra alle berørte commits:
# Install git-filter-repo (https://github.com/newren/git-filter-repo)
pip install git-filter-repo

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

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

# Force-push the cleaned branch
git push --force-with-lease origin <branch>
  1. Gi beskjed til alle som kan ha klonet repositoryet før omskrivingen.

Overstyring

Hvis en blokkert secret er tilsiktet (token for internt verktøy, publisert testlegitimasjon), kan en eier av organisasjonen legge til en avgrenset overstyring:

  1. Gå til Sikkerhet › Policyer › Push-beskyttelse › Aktive overstyringer → Ny overstyring.
  2. Angi: Repository (eller velg «All repos»), secret-mønster (fra regel-ID-en i Gitleaks) og begrunnelse (fritekst, logges i revisjonssporet).
  3. Lagre. Overstyringen gjelder fra neste push.

Alle overstyringer er synlige for andre eiere av organisasjonen og vises i revisjonsloggen med aktør, tidsstempel og begrunnelse. Overstyringer kan tilbakekalles når som helst fra den samme siden.

Overstyre en avvist push

Når en push avvises av sperren for skanning etter secrets, ser utvikleren årsaken i terminalen, mønstertypen som ble matchet, filstien og linjenummeret.

En eier av organisasjonen kan godkjenne en overstyring under Sikkerhet › Policyer › Push-beskyttelse › Siste avvisninger. Hver avvisning viser: repositoryet, den som committet, tidsstempelet, typen secret som ble oppdaget, og den berørte filstien.

Omfang for overstyringen (velges ved godkjenning):

  • This commit only godkjenner den bestemte commit-SHA-en. Den samme secreten i en senere commit blir avvist igjen.
  • This file path godkjenner secreten på den stien i alle senere commits. Bruk det for testfiksturer eller bevisst ikke-sensitive plassholderverdier.
  • This repository godkjenner alle forekomster av denne typen secret i repositoryet. Bruk det med varsomhet.
  • Org-wide godkjenner typen secret i alle repositoryer. Bare for erklærte falske positiver (f.eks. et eget tokenformat som matcher et regelmønster, men ikke er en ekte secret).

En begrunnelse for overstyringen er påkrevd (fritekst). Den skrives til revisjonsloggen.

Utvikleren må pushe på nytt etter at en overstyring er gitt. Overstyringen godtar ikke den avviste pushen i ettertid.

Egen tillatelsesliste

For å dempe kjente falske positiver i et bestemt repository (testfiksturer, eksempelnøkler i dokumentasjon, bevisst offentlig legitimasjon) legger du en .gitleaks.toml i roten av repositoryet:

[extend]
useDefault = true

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

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

Tillatelseslisten evalueres per repository under pre-receive-hooken. Den overstyrer ikke push-beskyttelsen på organisasjonsnivå for mønstre som ikke står i listen.

Sjekker på pull requests: bare nye funn blokkerer

Sjekkene for avhengigheter (fremforge/dep-scan), SAST (fremforge/sast), lisenser (fremforge/license-scan), secrets (fremforge/secret-scan) og imager (fremforge/image-scan) skanner hele repositoryet, men hver av dem feiler en pull request bare på funn som pull requesten selv introduserer. Et funn som branchen den merges inn i allerede har, blokkerer ikke, så en pull request som ikke endrer noe sårbart, kan merges i et repository som har åpne funn. Statusen lyder da for eksempel No new findings at or above critical; 8 already on main.

fremforge sammenligner funnene i pull requesten med den siste skanningen av den branchen med den samme skanneren. Et funn er det samme når:

SjekkSamme funn når
Avhengigheterøkosystem, pakke og sårbarhetsvarsel stemmer. Å oppgradere en pakke til en versjon som fortsatt har det samme sårbarhetsvarselet, er ikke nytt; et nytt sårbarhetsvarsel er det
SASTregelen, filen og den matchede koden stemmer. Et funn som bare har flyttet seg til en annen linje, er ikke nytt
Lisenserøkosystem, pakke og lisens stemmer
Secretsregelen og de matchede linjene stemmer, sammenlignet med en nøkkelbasert hash som skanneren beregner. Selve secreten lagres eller sendes aldri. En secret som bare har flyttet seg til en annen linje, er ikke ny
Imagersårbarhetsvarsel, pakke og fil stemmer
  • Kopier teller: en ny forekomst av et funn som branchen har én gang, er nytt.
  • Alt som allerede avgjør om et funn blokkerer, gjelder først: blokkeringsgrensen for alvorlighetsgrad, avvisninger, unntak og lisenspolicyen. En ny ondsinnet pakke eller kjent utnyttet sårbarhet blokkerer alltid, uansett om organisasjonen har valgt at funn som ikke er nåbare, ikke blokkerer.
  • Eksisterende funn skjules ikke. De er oppført i kommentaren på pull requesten og under Sikkerhet › Funn, teller med i SLA-en din og varsler som før.

Branchen skannes etter en push til standardbranchen, høyst én gang i timen, og den nyeste pushen vinner. Et funn som har nådd branchen siden den siste skanningen, teller derfor fortsatt som pull requestens frem til neste skanning; et nytt funn blir aldri forvekslet med et eksisterende. Når det ennå ikke finnes noen skanning av branchen, blokkerer alle funn, som før, og skanningen av pull requesten ber om en. Så snart branchens første skanning er registrert, skanner fremforge på egen hånd de åpne pull requestene inn i den på nytt der sjekken har feilet i mellomtiden (høyst 20 per repository, de sist oppdaterte, per sjekk), slik at de blir grønne, eller bare forblir røde på det de selv legger til, uten ny kjøring eller ny push. En pull request inn i en annen branch enn standardbranchen teller alltid alle funn.

Avhengighetsskanning

osv-scanner sjekker hver pull request, og standardbranchen etter hver push, mot OSV-databasen over sårbarhetsvarsler. Den leser låsefiler og manifester offline, uten å løse opp avhengigheter, og legger ut commit-statusen fremforge/dep-scan og en kommentar på pull requesten. Funnene er oppført under Sikkerhet › Funn › Avhengigheter.

Innstillinger og hva som blokkerer en merge

Under Sikkerhet › Funn › Avhengigheter kan en eier slå av avhengighetsskanning for organisasjonen og sette blokkeringsgrensen: den laveste alvorlighetsgraden som feiler fremforge/dep-scan. Standard er critical. På en pull request blokkerer bare funn som pull requesten introduserer. Om en sjekk som feiler, faktisk blokkerer mergen, avhenger av sjekkens modus (enforce, audit eller off) under Sikkerhet › Prioriter › Sjekker for pull requests, og av at sjekken er påkrevd av branchbeskyttelsen.

For å hindre at et funn blokkerer, retter du det eller registrerer et unntak. En organisasjon kan også velge at funn som ikke er nåbare, ikke blokkerer. Det valget dekker aldri ondsinnede pakker eller kjent utnyttede sårbarheter.

Pull requests med rettelser

To ting åpner pull requests med rettelser for sårbare avhengigheter:

  • Sikkerhetsrettelser: Åpne PR med rettelse på et funn (eller den automatiske policyen) åpner én pull request per pakke til den laveste versjonen som retter alle åpne sårbarhetsvarsler. Se Sikkerhetsrettelser.
  • Avhengighetsoppdateringer: Renovates egne sårbarhetsvarsler, for organisasjoner som er meldt inn i Avhengighetsoppdateringer. Repositoryene i hver innmeldt organisasjon kjøres hver natt, og Kjør nå starter en kjøring utenom tur.

Støttede manifester

Låsefilene og manifestene som leses, og hvilke økosystemer som får pull requests med rettelser og nåbarhet, er listet på ett sted: Støttede økosystemer.

Skanning av containerimager

Trivy skanner hvert OCI-image som pushes til fremforges pakkeregister. Resultatene vises under Sikkerhet › Funn › Containerimager.

Trivy kjører sentralt, uten at noe må installeres: trivy fs på hver pull request (statusen fremforge/image-scan, som bare blokkerer på funn som pull requesten introduserer), trivy config som sin egen sjekk (fremforge/iac-scan), og trivy image på hvert image i registeret ditt og hvert image som hentes gjennom avhengighetsproxyen, med jevnlig ny skanning. Funn om feilkonfigurasjon fra trivy config har sin egen fane, Sikkerhet › Funn › IaC; se IaC-skanning.

Hvert funn inneholder:

FeltBeskrivelse
CVE-IDf.eks. CVE-2024-12345 med lenke til NVD
AlvorlighetsgradCRITICAL / HIGH / MEDIUM / LOW / UNKNOWN
Berørt pakkePakkenavn og installert versjon
Rettet versjonFørste versjon som løser CVE-en, hvis den finnes
EPSS-poengsumSannsynlighet etter Exploit Prediction Scoring System

Skanning i din egen workflow (valgfritt)

Det er ingenting å slå på: skanningene ovenfor kjører uten en workflow-fil. Hvis du i tillegg vil at ditt eget bygg skal feile før det pusher et image, kan du kjøre Trivy i en workflow selv, for eksempel:

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

Sett --severity til grensen du vil at bygget skal feile på.

Hvorfor dette eksemplet låser på SHA og digest

En tidligere versjon av denne siden brukte aquasecurity/trivy-action@master og actions/checkout@v4. Begge er foranderlige referanser, og 19. mars 2026 sluttet den forskjellen å være teoretisk: en angriper skrev om trivy-action-taggen v0.69.4 til å peke på en ondsinnet utgivelse. Pipelines som refererte til actionen med tagg eller branch, hentet angriperens kode ved neste kjøring, og legitimasjonen den samlet inn, ble brukt til å publisere to utgivelser med bakdør av et annet prosjekt til PyPI.

En tagg er en peker som noen andre kan flytte. En commit-SHA og en image-digest er ikke det.

  • Handlinger: referer til hele commit-SHA-en på 40 tegn, ikke @v4 og aldri @master. Renovate foreslår fortsatt oppgraderinger; den skriver om SHA-en og kommentaren dens samtidig.
  • Skannere: foretrekk verktøyets containerimage låst på digest fremfor en innpakningsaction. Færre bevegelige deler, og det du låste, er det som kjører.
  • Speil det du er avhengig av: fremforge kontakter ikke noe upstream-register fra en produksjonssti; hvert image kommer fra vårt eget register, låst på digest.

Den hendelsen krevde ingen handling hos fremforge: vi har aldri brukt trivy-action, og vår egen Trivy er et image låst på digest. Prosjektet som ble kompromittert, hadde installert Trivy fra apt uten en låst versjon. Låsing er lite glamorøst, og det er kontrollen som ville ha stoppet det.

Generering av SBOM

Syft genererer CycloneDX 1.6-SBOM-er for hvert containerimage som pushes til registeret og for hver utgivelsestagg, med en SPDX 2.3-tvilling fra den samme kjøringen (så langt det lar seg gjøre; se SBOM-er). Opplastinger godtas som CycloneDX 1.5–1.7 eller SPDX 2.x/3.x. Generering av SBOM er slått på som standard for alle organisasjoner; det er ingenting å slå på.

Nedlasting

Fra pakkeregisterets brukergrensesnitt: åpne imaget → fanen Attestations → Download SBOM.

Via API-et:

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

SBOM-en er CycloneDX-JSON og inneholder alle transitive avhengigheter med PURL-identifikatorer, lisensuttrykk og versjonsintervaller.

SAST

OpenGrep kjører statisk analyse sentralt på hver pull request og på standardbranchen. Den er slått på som standard for alle organisasjoner. Funn legger ut statusen fremforge/sast og en kommentar på pull requesten, og er oppført under Sikkerhet › Funn › SAST. Språk, regler og innstillinger står på siden SAST. Egne regler støttes ikke.

Signerte commits

fremforge støtter to måter å signere commits på, og begge verifiseres direkte av Forgejos «Verified»-merke i nettgrensesnittet:

  • Signering med SSH-nøkkel (anbefalt standard) gjenbruker SSH-nøkkelen du allerede har registrert for git push. Ikke noe nytt nøkkelmateriale å administrere, ingen tredjeparts underdatabehandler, ingen avhengighet av en transparenslogg, ingen transitt gjennom amerikansk jurisdiksjon. Dette er fremforges kanoniske vei. (For nye organisasjoner opprettet etter 2026-05-22 er SSH-transport slått av som standard; signering med SSH-nøkkel fungerer likevel, uansett om du bruker SSH eller HTTPS for git push.)
  • Signering med GPG: klassisk OpenPGP-nøkkel. Bruk dette hvis organisasjonen din allerede har en policy for GPG-nøkkeladministrasjon (smartkort, HSM, sentral nøkkelserver).

Nøkkelfri commit-signering ER tilgjengelig hos fremforge via en selvhostet Sigstore-stabel: Fulcio-CA på sign.frem.sh og en TSA på tsa.frem.sh, begge på T Cloud Public i eu-de med rot-CA-en i DEW KMS. Den offentlige Sigstore-instansen til Linux Foundation brukes IKKE: fremforge sender ingen signerings- eller verifiseringstrafikk gjennom sigstore.dev / rekor.sigstore.dev, så linjen om ingen amerikanske underdatabehandlere holder. Det finnes bevisst ingen Rekor eller transparenslogg for commit-signering; tidsstempling forankret i TSA-en er integritetsmekanismen i stedet. (fremforge driver en transparenslogg på log.frem.sh, men den dekker bare attesteringer av byggeproveniens og er ikke Rekor; se transparenslogg. De to er uavhengige: en signert commit føres ikke inn i den.) Se nøkkelfri commit-signering for oppsettet av gitsign på kundesiden. (Merk: proveniens for bygg og utgivelser er en separat vei: fremforges SLSA-proveniens signeres med en Ed25519-nøkkel som plattformen har, IKKE gjennom Fulcio. Se avsnittet om SLSA nedenfor.)

Signering med SSH-nøkkel, oppsett

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

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

Legg til den tilhørende SSH-nøkkelen i brukerprofilen din i fremforge under Innstillinger → SSH- / GPG-nøkler → Legg til nøkkel (Forgejo godtar den samme nøkkelen for både push og signering, eller en separat nøkkel bare for signering).

Ved neste git commit signerer Git med SSH-nøkkelen din. Forgejo verifiserer signaturen mot organisasjonens allowedSignersFile (avledet automatisk fra medlemmenes registrerte signeringsnøkler) og viser «Verified»-merket i nettgrensesnittet.

Verifiser

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

Hver commit viser en linje Good "git" signature for <user>@<org>. Forgejos nettgrensesnitt viser et grønt «Verified»-merke ved siden av commit-hashen.

Signering med GPG, oppsett

Foretrekker du GPG, fungerer den vanlige fremgangsmåten med git config --global commit.gpgsign true + user.signingkey <gpg-key-id> uendret; last opp den offentlige nøkkelen din under Innstillinger → SSH- / GPG-nøkler → Legg til nøkkel. Forgejo verifiserer og viser «Verified»-merket.

Krev signerte commits (organisasjonspolicy)

Håndhev signerte commits på beskyttede brancher med Commit-signering under Sikkerhet › Policyer › Regler for repositoryer (se Branchbeskyttelse), eller per repository under Repository → Innstillinger → Brancher → <branch> → Krev signerte commits. Usignerte commits avvises ved push. Både SSH-nøkkel- og GPG-signaturer oppfyller kravet.

SLSA-proveniens

Proveniens for bygg og utgivelser genereres på serversiden av fremforge: det ligger ingen workflow-fil i repositoryene dine, og ingen runner-minutter brukes. Ved hver Forgejo-hendelse release.published og package hasher fremforges API hver publiserte ressurs, bygger et in-toto Statement pakket inn i en DSSE-konvolutt, signerer det med plattformens byggenøkkel, lagrer attesteringen i EU-basert lagring avgrenset til din kunde og laster opp konvolutten igjen ved siden av ressursen som <asset>.intoto.jsonl. Den er også oppført i konsollen under Sikkerhet › Komponenter › Byggeattesteringer.

Den frittstående workflow-malen slsa-provenance.yaml, som kundene tidligere var ventet å kalle fra byggepipelinene sine, ble faset ut 2026-06-16: en gjennomgang av hele flåten fant null kallere, og proveniens utløses nå automatisk på serversiden uten noe oppsett hos kunden.

EU-suveren av design. Signeringsnøklene, tillitsroten, sertifikatutstederen og lagringen ligger alle i fremforges konto i T Cloud Public (eu-de). Det finnes ingen offentlig Sigstore, ingen Rekor og ingen oppslag mot tuf-repo-cdn.sigstore.dev noe sted i verifiseringsveien.

To signaturer, begge på hver attestering. Den primære er en Ed25519-nøkkel som plattformen har, og det er den som oppskriften Verifiser nedenfor sjekker: vanlig curl / jq / openssl / python3, ingenting annet trengs. I tillegg signeres hver attestering nøkkelfritt mot fremforges selvhostede Sigstore: et kortlevd Fulcio-sertifikat der SAN er byggekildens identitet, tidsstemplet av fremforges RFC3161-TSA. Begge er beskrevet i tillitsroten, som inneholder fulcio_root_ca_pem, tsa_cert_chain_pem, certificate_oidc_issuer og certificate_identity_pattern, slik at også den nøkkelfrie signaturen kan verifiseres offline.

Det finnes fortsatt ingen transparenslogg: tillitsroten sier "transparency_log": "none", så den nøkkelfrie signaturen valideres mot RFC3161-tidsstemplet i stedet for Rekor (cosign --use-signed-timestamps --insecure-ignore-tlog). Hva det gir deg og ikke gir deg, står i §Uavhengig verifiserbarhet.

(fremforges commit-signering er en separat vei på den samme selvhostede CA-en; se nøkkelfri commit-signering.)

Tillitsrot: https://www.frem.sh/.well-known/slsa-trust-root.json, en liten JSON-fil som lister byggenøklenes offentlige nøkler. Formen er spesifikk for 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 er den aktive signeringsnøkkelen siden nøkkelbyttet 2026-09-12. fremforge-slsa-builder-v1 signerte alt før det og beholdes bare for verifisering: dens valid_to er tidspunktet den sluttet å signere, ikke en utløpstid for signaturene den laget. Verifiser alltid mot nøkkelen som keyid i konvolutten du har, peker på, aldri mot et fast navn.

Lås filen (eller en kopi av den) i CI-en din for å feste tilliten gjennom verifiseringsvinduet. Nøkkelbytter varsles 30 dager i forveien etter varslingsregelen for underdatabehandlere på trust-siden; en signatur slutter bare å verifisere hvis nøkkelen dens fjernes fra trusted_keys.

Formen på konvolutt og statement (DSSE-konvolutt som pakker inn et in-toto Statement):

FeltVerdi
konvoluttens payloadTypeapplication/vnd.in-toto+json
konvoluttens signatures[0].keyidfremforge-slsa-builder-v2 for alt som er signert siden 2026-09-12, fremforge-slsa-builder-v1 før det (tilsvarer en trusted_keys[].kid i tillitsroten)
Statement _typehttps://in-toto.io/Statement/v1
Statement predicateTypehttps://slsa.dev/provenance/v1
predicate.buildDefinition.buildTypeHvilken vei som produserte statementet; se §Hva hver buildType betyr. En av …/buildtypes/forgejo-action-run/v1, …/container-image/v1, …/forgejo-release/v1, …/forgejo-package/v1
predicate.buildDefinition.internalParameters.run_idActions-kjøringen som produserte artefaktet. Finnes på byggeproveniens; mangler på et publiseringsstatement
predicate.buildDefinition.internalParameters.attestation_kindpublication når statementet observerer publiserte byte i stedet for et bygg. Mangler på byggeproveniens
predicate.buildDefinition.externalParameters.repository<org>/<repo>
predicate.buildDefinition.externalParameters.refGit-ref-en (f.eks. refs/tags/v1.2.3)
predicate.runDetails.builder.idhttps://frem.sh/runner-controller/v1 (styrt av serveren og tvangsoverstyrt ved signering: en kompromittert runner kan ikke forfalske denne)
subject[].name / subject[].digest.sha256Ressursens filnavn og SHA-256-en som fremforge beregnet for den

Hva dette er, helt presist. Frem til 2026-09-11 hevdet dette avsnittet SLSA Build L2. Vi utledet det på nytt mot v1.0-spesifikasjonen, påstanden holdt ikke, og vi trakk den tilbake. Mangelen som gjorde den usann, proveniens som ikke beskrev noe bygg, ble lukket 2026-09-12. Vi oppgir fortsatt ikke noe nivå, av en grunn som står på slutten av dette avsnittet.

To forskjellige statements signeres nå med den samme nøkkelen, og du kan skille dem fra hverandre ut fra konvolutten alene.

Hva hver buildType betyr

buildType slutter påHva den hevderNavngir en byggekjøring?
forgejo-action-run/v1fremforges kontrollplan leste kjøringens opplastede artefakter fra byggeplattformens egne registreringer, hentet bytene og hashet demJa: run_id
container-image/v1Et image pushet av en kjøring, attestert med kjøringens identitet hentet fra et kortlevd OIDC-token som fremforge utstedte for den jobbenJa: run_id, kryptografisk bundet
forgejo-release/v1En publisert utgivelsesressurs. Arver commiten til et bygg når den identiske digesten allerede var attestert som bygget; se nedenforBare når koblet
forgejo-package/v1En pakkeversjon som dukket opp i registeretNei

Byggeproveniens (de to første) beskriver hvordan artefaktet ble produsert: den navngir kjøringen, workflowen og commiten, og kjøringens identitet kommer fra plattformen og ikke fra noe bygget har hevdet om seg selv.

Publiseringsattestering (de to siste, ukoblet) sier noe smalere og sier det eksplisitt: attestation_kind: publication. Den betyr fremforge observerte at nøyaktig dette innholdet, byte for byte, ble publisert her, og signerer den observasjonen med en nøkkel som bygget ikke kan nå. Det er en reell egenskap som kan sjekkes. Det er ikke et utsagn om hvordan artefaktet ble bygget, og det later ikke lenger som det er det: der ingen kildecommit er kjent, er commit_sha den bokstavelige verdien no-source-commit og ikke en verdi formet som en Git-commit. Ser du 40 heksadesimale tegn der, er det en ekte commit.

Digest-koblingen. En utgivelsesressurs hvis sha256 allerede har en byggeattestering, arver det byggets commit, og statementet peker på den (source_commit_from: digest-join, pluss byggeattesteringens ID og objektnøkkel). Koblingen er artefaktets digest, så «de samme bytene» er et kryptografisk faktum og ikke en navnekonvensjon: å publisere et artefakt senere endrer ikke hva som produserte det. En eksplisitt target_commitish på utgivelsen vinner alltid over slutningen.

Plattformegenskapene bak alt dette:

  • Kjøringer kan ikke påvirke hverandre. Én flyktig ECS-VM per jobb, som ødelegges når jobben er ferdig, med et ENI per VM og en legitimasjon per runner som dør med jobben; se §Modell for kjerneisolasjon.
  • Signeringsnøkkelen er utenfor byggets rekkevidde. Den ligger i API-et, aldri på en runner, og byggeidentiteten overstyres på serversiden ved signering, så et kompromittert bygg ikke kan forfalske den.
  • Ikke noe oppsett hos kunden for de webhook-drevne veiene. Ingenting å kalle fra en workflow, og ingen runner-minutter brukes.

Hvorfor vi fortsatt ikke oppgir et SLSA-nivå. De to isolasjonsegenskapene er det SLSA v1.0 Build L3 krever, og byggeproveniensen beskriver nå et faktisk bygg. Vi vil heller at du leser tabellen ovenfor og avgjør hva den er verdt, enn at du godtar et tall fra parten som revideres. (Hermetiske bygg, reproduserbare artefakter og transparenslogger blir noen ganger oppgitt som krav for L3. Det er de ikke: det var begreper fra SLSA v0.1 L4 og finnes ikke på noe nivå i v1.0, som lister dem under fremtidige retninger.)

Det som fortsatt mangler, er en transparenslogg, som er en annen akse enn noe nivå; se §Uavhengig verifiserbarhet.

Verifiser

Ikke bruk upstreams slsa-verifier verify-artifact --trusted-root. Det verktøyet har ikke noe --trusted-root-flagg på verify-artifact og kan ikke lese fremforges egen JSON-tillitsrot eller en DSSE-konvolutt med ren Ed25519; det forventer en bundle i Sigstore-stil. fremforges proveniens er en vanlig DSSE-konvolutt signert med Ed25519, så du verifiserer den direkte med curl / jq / openssl / python3, som alle følger med distribusjonen som standard, uten verktøy fra fremforge.

Verifiseringen består av tre uavhengige sjekker: DSSE-signaturen er gyldig for den publiserte byggenøkkelen, ressursens SHA-256 stemmer med det som ble signert, og statementet navngir riktig repository og bygger.

1. Hent tillitsroten og trekk ut byggenøkkelens offentlige nøkkel

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

For en attestering signert før 2026-09-12 velger du fremforge-slsa-builder-v1 i stedet. jq -r '.signatures[0].keyid' <asset>.intoto.jsonl forteller deg hvilken.

2. Verifiser DSSE-signaturen, ressursens digest og feltene i statementet

Kjør fra en katalog som inneholder den nedlastede ressursen og konvolutten <asset>.intoto.jsonl. Sett ASSET til ressursens filnavn:

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

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

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

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

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

stmt = json.loads(payload)

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

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

Skriver alle tre sjekkene OK, er ressursen nøyaktig de bytene fremforge attesterte, konvolutten ble signert med den publiserte byggenøkkelen, og statementet navngir fremforges bygger. Verifiseringen feiler (avslutningskode ulik null) hvis signaturen ikke valideres mot builder.pub (manipulert konvolutt eller feil eller byttet nøkkel), ressursens SHA-256 ikke stemmer med det signerte subjektet (manipulert ressurs), eller builder.id / predicateType ikke stemmer.

keyid i konvolutten (fremforge-slsa-builder-v2 for alt som er signert siden 2026-09-12) må stemme med kid du valgte fra tillitsroten; en konvolutt som sjekkes mot den andre nøkkelen, feiler med DSSE signature FAILED. Hvis fremforge bytter signeringsnøkkel, henter du tillitsroten på nytt og velger den kid som konvolutten navngir.

Hvorfor ikke Sigstore ved lansering

Sigstores offentlige Fulcio + Rekor + Trillian-stabel drives av OpenSSF (Linux Foundation), et organ registrert i USA. Å bruke den ville sende hvert byggs metadata til infrastruktur i USA, noe som er uforenlig med fremforges forpliktelse om bare EU-baserte underdatabehandlere i DPA-ens vedlegg B. fremforge driver sin egen selvhostede Fulcio + TSA på sign.frem.sh / tsa.frem.sh på T Cloud Public eu-de, brukt både til commit-signering (se nøkkelfri commit-signering) og, siden 2026-06-21, som den andre signaturen på byggeproveniens. Det som bevisst mangler, er den offentlige Sigstore-installasjonen og Rekor.

Hver attesterings SHA-256 hashkjedes også inn i plattformens revisjonslogg, hvis ankre ligger i en OBS Object Lock i COMPLIANCE-modus i tre år. Det er en nyttig intern kontroll, og den er ikke en erstatning for en transparenslogg, så vi beskriver den ikke lenger som likeverdig manipulasjonsbevis. To grunner, begge konkrete: ankeret kan ikke leses av deg, så det kan ikke være en del av din verifisering; og OBS Object Lock beskytter hver objektversjon: målt 2026-08-14 kan en nyere versjon skygge for en låst nøkkel, og en slettemarkør skjuler den for vanlige lesinger, og begge deler er handlinger som operatøren har tilgang til. Se §Uavhengig verifiserbarhet for hva tillitsmodellen gir deg og ikke gir deg.

Uforanderlige utgivelser

Eiere av organisasjonen kan gjøre hver utgivelse frosset ved publisering: taggen, målcommiten og ressursene kan aldri endres igjen (tittel og notater kan fortsatt redigeres), taggnavnet til en slettet utgivelse kan aldri brukes på nytt, og containertagger kan også fryses (tagger i stilen v1.2.3, eller alle unntatt en liste). Hver uforanderlige utgivelse får ett signert in-toto-utgivelsesstatement som navngir hver ressurs med SHA-256 og taggen med commit, og som kan sjekkes med fremforge release verify. Se Uforanderlige utgivelser og utgivelsesattestering.

Uavhengig verifiserbarhet

Å flytte et repository, en runner og et artefaktlager til Tyskland svarer på hvor dataene ligger. Det svarer ikke i seg selv på om revisjonssporet kan stoles på uten å stole på operatøren som produserte det. Det er forskjellige egenskaper, og vi vil heller være tydelige på hvilken av dem vi leverer i dag.

Hva du får uten å stole på oss ved verifisering. Attesteringen følger med artefaktet, <asset>.intoto.jsonl og ved siden av den <asset>.sigstore.json, så du har begge halvdelene. Byggenøkkelens offentlige nøkkel, Fulcio-rot-CA-en og TSA-kjeden ligger alle i en statisk tillitsrot som du oppfordres til å låse en kopi av i din egen CI. Verifiseringen beregner artefaktets digest på nytt og sjekker signaturene lokalt, med curl / jq / openssl / python3. Ingen API hos fremforge kontaktes, og ingen verktøy fra fremforge trengs. Når du har låst roten, kan en senere endring på vår side ikke i ettertid endre hva pipelinen din godtar.

Hva du ikke får, sagt rett ut. Det finnes nå en transparenslogg som bare kan utvides, for proveniensattesteringer, på log.frem.sh, men den gir deg ennå ikke den egenskapen folk vanligvis mener med ordet. Konkret:

  • Loggen gjør at vi ikke kan skrive om historikken i det stille: hvis du har et inklusjonsbevis og vi senere endrer eller fjerner attesteringen din, slutter beviset ditt å verifisere, og du kan påvise motsigelsen. Det er reelt, og det er nytt.
  • Den er delvis motstandsdyktig mot delt visning, fra 16. september 2026. Ett uavhengig vitne, witness.stagemole.eu, drevet av Mullvad VPN AB, medsignerer hvert sjekkpunkt, så det finnes nå en tredjepart som ville merke at vi signerer noe uvanlig. Det er den egenskapen dette punktet alltid har handlet om, og den mangler ikke lenger. Ett vitne er ikke et quorum: det gjør det dyrere å vise deg et annet tre enn alle andre ser, men kan ikke oppdage samarbeid mellom loggen og dens eneste vitne, og operatøren kaller instansen staging. Merk også at de eneste EU-vitnene av produksjonskvalitet, Glasklar Teknik, Mullvad VPN og Tillitis, har samme morselskap, Amagicom AB; flere av dem ville gi tilgjengelighet, ikke uavhengighet.
  • RFC3161-tidsstemplet som delvis erstatter en logg, utstedes av vår TSA, og tillitsroten distribueres fra vårt domene. Låsing løser det siste for alle som låser ved oppstart; det løser ikke tilliten ved første kontakt.
  • Ankeret i vår interne revisjonskjede kan ikke leses av deg, så det kan ikke inngå i din verifisering.

Hvis operatøren er parten du må kunne holde ansvarlig, som er det riktige spørsmålet for forsvar og kritisk infrastruktur, er det som lukker gapet en transparenslogg med eksterne vitner, en uavhengig tidsstempelutsteder og reproduserbare bygg som du kan bygge på nytt og sammenligne uten at vi er involvert. Den første av de tre finnes nå og er beskrevet ovenfor, med sine begrensninger; tidsstempelutstederen og de reproduserbare byggene finnes ikke. Merk at en selvhostet logg ikke ville lukket gapet: en logg vi driver, er en logg vi kontrollerer. Uavhengig verifiserbarhet krever en part utenfra, og den ærlige avveiningen er at å gjøre det via den offentlige Sigstore-loggen ville publisere artefaktdigester, repositoryidentiteter og utgivelsestidspunkter utenfor EU.

Vi vil heller avgrense det mot et reelt krav enn å gjette på ett. Hvis dette er en anskaffelsesbegrensning for deg, fortell oss hvilke av egenskapene du trenger og innen hvilken tidsramme, så kommer vi tilbake med en datert plan i stedet for en påstand.

Modell for kjerneisolasjon

Hver jobb får sin egen flyktige ECS-virtuelle maskin på T Cloud Public i eu-de, og den ødelegges når jobben er ferdig. VM-ene er enkeltoppgave: runneren slettes etter sin ene oppgave, og legitimasjonen per runner dør med jobben, så en VM kan ikke betjene en ny jobb, selv ikke innenfor den samme workflow-kjøringen. (Før 2026-07-30 kunne påfølgende jobber for én commit dele en VM; det kan de ikke lenger.) Isolasjonsegenskapene:

EgenskapDetalj
Deling av kjerneIngen kjerne deles mellom commits eller mellom organisasjoner: hver commit får en egen VM. Jobber for samme commit kan dele den VM-en, så behandle en pipeline som ett tillitsdomene
PersistensVM-en ødelegges når commitens jobber er ferdige; ingen filsystemtilstand overlever til en annen commit eller en annen organisasjon. Tilstand kan føres mellom jobber for samme commit: et byggesteg som kjører tredjeparts avhengighetskode, deler maskinen med et senere utrullingssteg, så avgrens all legitimasjon en utrulling trenger, til den pipelinen
Tilgang til plattform-APIVM-en har ingen IAM-agency tilknyttet; identiteten fra metadata har ingen roller, så OTC-API-et kan ikke nås innenfra
NettverkSecurity Group per VM med tillatelsesliste kun for utgående trafikk; runnere kan ikke ta imot innkommende tilkoblinger
Metadata i T Cloud PublicBlokkert av den utgående SSRF-proxyen; runnerkode kan ikke nå instansens metadataendepunkt med privilegert scope

Se CI-runnere for den fullstendige isolasjonsspesifikasjonen og registrering av egne runnere (BYO).

Isolasjon mellom tenanter

Hver tenant i fremforge er et separat nøklet navnerom i plattformen. Isolasjonskjeden går fra URL-en gjennom mellomvare, laget for databasespørringer, lagringslaget og revisjonskjeden.

LagMekanismeHva som stopper tilgang på tvers av tenanter
URL-rutingMellomvaren for /<slug>/_admin/* laster tenanten på nytt fra slugen ved hver forespørselEn bruker med en sesjon for organisasjon A som limer inn URL-en til organisasjon B, avvises ved medlemskapssjekken, ikke ved en utdatert informasjonskapsel
MedlemskapssjekkOppslag i Forgejos Owners-team mot slugen, verifisert ved hver forespørselAPI-et stoler aldri på en innkommende påstand om hvilke organisasjoner brukeren tilhører
DatabaseHver tenant-nøklet tabell har en fremmednøkkel tenant_id; hver lesing OG hver skriving filtrerer på denSELECT / UPDATE / DELETE uten tenant_id er strukturelt umulig i stiene som revisjonen har merket
Signerte tokenerHver kortlevde signerte URL (magisk lenke for fakturering, angring av oppsigelse, OIDC-tilstand) har tenant-ID-en i nyttelasten; verifiseringssiden kryssjekker mot slugen i URL-enEt token utstedt for kunde A avvises hvis det spilles av mot URL-en til kunde B
Lagring (OBS)SBOM-er, attesteringer og objekter i revisjonsloggen er alle nøklet <artifact>/<tenant_id>/...; utstedelse av signerte URL-er validerer tenant.id === row.tenant_id på nytt før utstedelseEn direkte forhåndssignert OBS-URL bundet til kunde A kan ikke skrives om til å lese kunde B
RevisjonskjedeManipulasjonsavslørende hashkjede nøklet per tenant; én tenants tillegg endrer aldri en annen tenants kjedehodeEt brudd i tenant As kjede (f.eks. et forsøk på å slette i ettertid) påvirker ikke integriteten i kunde Bs kjede
OperatørtilgangNår ansatte (fremverk) ser på kundedata, logges det i tenantens revisjonskjede som en operatørhandling; tenantens administratorer kan se operatørbesøk i sin egen revisjonsloggIngen stille tilgang for ansatte til kundedata

Forsinkelse ved fjerning fra Owners-teamet

API-et bufrer en kundes medlemskap i Forgejos Owners-team i opptil 5 minutter for å holde sidelastinger under 50 ms (alternativet er en rundtur til Forgejo-API-et ved hver forespørsel, som ville gjort hver side 100–200 ms tregere). Avveiningen:

  • Når du legger noen til i Owners på Forgejo-siden, får de administratortilgang innen 5 minutter.
  • Når du fjerner noen fra Owners på Forgejo-siden, mister de administratortilgangen innen 5 minutter, ikke umiddelbart.

For de fleste operasjoner er denne forsinkelsen akseptabel: en tidligere eier kan ikke opprette nye ressurser i Forgejo (Forgejos egen sjekk er umiddelbar), og alle privilegerte handlinger via fremforges administrasjonsgrensesnitt registreres i kundens revisjonslogg med aktørens brukernavn, så aktivitet etter fjerningen forblir sporbar.

Trenger du umiddelbar fjerning (f.eks. som del av hendelseshåndtering etter at legitimasjon er kompromittert), kan operatøren på vakt sette bufferen til null TTL for din tenant på forespørsel: send en henvendelse til support@frem.sh med emneprefikset [urgent-revocation] og brukernavn og kundens slug. Standard SLA: under 15 minutter i arbeidstiden, under 60 minutter utenom. Den samme effekten oppnås ved å tilbakekalle brukerens Forgejo-sesjon (Forgejo Owner → User profile → Sign out), som ugyldiggjør informasjonskapselen API-et leser, uten at noe må gjøres hos fremforge.

Plattformens sikkerhetsforpliktelser

Alvorlighetsgrad for CVESLA for patching (fra upstreams rettede utgivelse)
Kritisk (CVSS ≥ 9,0)48 timer
Høy (7,0–8,9)72 timer
Middels (4,0–6,9)7 dager
LavNeste planlagte vedlikeholdsvindu

SLA-en for patching er publisert kontraktsmessig og sitert ordrett i DPA-ens sikkerhetsvedlegg.

Integritet i revisjonsloggen: manipulasjonsavslørende hashkjede forankret i WORM-lagring i T Cloud Public OBS hvert 2. minutt; integritetssjekk i FunctionGraph hver time; brudd i kjeden varsler vakten.

Feilsøking

Pushen ble blokkert, men jeg finner ikke secreten i diffen.

Secreten kan ligge i en fil som ble endret, men hvis fulle innhold ikke vises i den vanlige diffen. Kjør Gitleaks lokalt for å finne nøyaktig hvor den er:

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

Ingen pull request med rettelse for en kjent CVE.

Sjekk at pakkens økosystem og manifest får pull requests med rettelser (Støttede økosystemer), og at sårbarhetsvarselet har en rettet versjon. Renovate kjører hver natt for en innmeldt organisasjon; Sikkerhet › Utbedre › Avhengighetsoppdateringer → Kjør nå starter en kjøring utenom tur, og Åpne PR med rettelse på funnet ber om en sikkerhetsrettelse med en gang. Se Når ingen pull request åpnes.

Nedlasting av SBOM gir 404.

SBOM-er genereres bare for imager som er pushet etter at generering av SBOM ble slått på. Imager som ble pushet før det, har ingen SBOM. Push en ny tagg for å generere en.

SSH-signering feiler med ssh-keygen: gpg failed to sign the data.

Sørg for at git config gpg.format er satt til ssh (ikke standardverdien openpgp), og at user.signingkey peker på en fil med en offentlig nøkkel. På macOS må du også bekrefte at ssh-keygen ligger i $PATH (Apples medfølgende ssh-keygen fungerer; har du installert OpenSSH fra Homebrew, sørg for at den skygger riktig).

Se også