Säkerhetsmodell för hostade runners
fremforges hostade runners kör kunders Forgejo Actions-arbetsflöden på virtuella ECS-maskiner i T Cloud Public, en per commit, i regionen EU-DE. Ett köat jobb som saknar en ledig VM för sin commit får en ny VM med inbyggd Docker. Den VM:en kör bara jobb från samma commit i samma repo, ett i taget, väntar upp till 60 sekunder på ett nytt jobb och stängs sedan av och förstörs. Det finns ingen beständig runnerpool, och ingen VM kör någonsin kod från två commits, två repon eller två kunder.
Den här sidan beskriver vilka isoleringsgarantier fremforge ger, vilka gränser som gäller per tenant och hotmodellen: vad kundkod kan göra, vad den inte kan göra och vilka designval den som skriver ett arbetsflöde bör göra för att hålla sina hemligheter säkra.
Var arbetsflödena körs
| Komponent | Implementering |
|---|---|
| Beräkning | Virtuella maskiner i T Cloud Public ECS (Elastic Cloud Server), region eu-de, tre tillgänglighetszoner |
| Livscykel | En kortlivad VM per commit. Plattformen startar en ny ECS-VM när det första jobbet för en commit köas och förstör den när commitens jobb är klara. Två kunder, och två commits, delar aldrig kärna: jämförbart med GitHubs och GitLabs hostade runners när det gäller isolering mellan tenants. Jobb från samma commit kan dela VM (till exempel build och sedan deploy), och därför bör en pipeline behandlas som en enda förtroendedomän, se Hotmodell. Kunders arbetsflöden delar aldrig VM med plattformens arbetslaster. Det finns ingen varm pool (mål 0, av kostnadsskäl), så varje commit betalar ECS-uppstarten på cirka 40 s. |
| Placering i nätverket | Varje VM får ett eget ENI i ett dedikerat runner-subnät, med en Security Group per VM som styr utgående trafik. VM:arna ligger i ett dedikerat VPC-subnät fördelat över tre tillgänglighetszoner |
| VM-körtid | En VM per commit; skapas när commitens första jobb köas och förstörs när dess jobb är klara. Taket för väggklocktid per jobb (60 min som standard, 360 min som hårt tak) upprätthålls på runnern |
| Reaper som reserv | En separat reaper utanför det ordinarie flödet tar bort föräldralösa VM:ar: runnern registrerades aldrig, runnern avslutades men borttagningen missades, VM:ens ålder har passerat dess deadline, eller en absolut reservgräns på 6 h 10 min. |
| Runner-binär | Upstreams forgejo-runner (act_runner) v13.2.0, ingen fork. Registrerar sig kortlivat med --ephemeral, så att en registrering förbrukas av en uppgift; agenten registrerar sig på nytt för varje ytterligare uppgift i samma commit, och plattformen förstör VM:en när commitens jobb är klara |
| VM-image | Den hostade runner-imagen: debian 13 (trixie) som bas, med verktygskedjor från binärer cachade i EU (inga direkta hämtningar från upstream vid uppstart). Imagen är VM:ens bootdisk; Docker-daemonen är förinstallerad och körs direkt på VM:en (ingen separat sidecar). Kallstarten tar cirka 40 s för varje commit: målet för den varma poolen är 0 av kostnadsskäl, så ingen VM skapas i förväg |
Isoleringsgarantier
Nätverksisolering (per VM)
Varje runner-VM får ett eget ENI kopplat till en Security Group per VM. Security Group nekar som standard all trafik utom:
- DNS (53/UDP, 53/TCP) till plattformens interna DNS-resolver
- Utgående trafik via runnerns egress-proxy, den enda utgående HTTP-vägen
Kundkod kan inte nå:
- fremforges api eller dess privata databas
- Forgejos interna endpoint (runner-VM:ar pratar med Forgejo via den publika apex-domänen bakom Bunny med ett runner-specifikt scope; den interna endpointen går inte att nå)
- Plattformstjänster i T Cloud Public (RDS, DCS, OBS, SFS-Turbo, IAM, KMS)
- Andra tenants runner-VM:ar
- Klustrets metadata-IP (
169.254.169.254), som uttryckligen blockeras i SSRF-skyddet - Privata RFC1918-intervall eller loopback
Security Group tillämpas när VM:en skapas och kan inte ändras inifrån VM:en: VM:en har ingen IAM-agency kopplad, så API:erna för nätverkskonfiguration går inte att nå via den identitet som härleds från metadata.
Utgående trafik
Steg i kunders arbetsflöden som behöver det publika internet (klona ett publikt repo, ladda ned ett verktyg, anropa ett SaaS-API) går via runnerns egress-proxy genom HTTPS_PROXY, som injiceras i VM:en. Proxyn:
- Accepterar alla publika unicast-IP:n, inklusive Carrier-Grade NAT (samma beteende som GitHubs hostade runners, och nödvändigt för T Cloud Publics egna tjänster som SWR, som ligger i CGNAT)
- Blockerar privata RFC1918-adresser, link-local, loopback, multicast, reserverade adresser och uttryckliga metadata-IP:n för molnet (
169.254.169.254, T Cloud Publics100.100.100.200) - Loggar varje försök till CONNECT-tunnel med värdnamn och upplöst IP för granskning
Proxyn är avsiktligt tillåtande i samma mening som GitHubs hostade runners: kunders arbetsflöden kan nå alla publika värdar de behöver. Gränsen går vid SSRF och den interna plattformen, inte vid leverantörslistor.
Paketcacher (suveräna, i EU)
Innan ett arbetsflöde når det publika internet går paketinstallationer och hämtningar av images via din organisations egen beroendeproxy, som finns i EU i samma region som runner-VM:arna. Den delade Docker Hub-spegeln och paketcacherna inne i VPC:n avvecklades i oktober 2026. Varje organisations proxy tillämpar sin egen paketpolicy, sina egna upstream-inloggningsuppgifter, sin egen åtkomstlogg och sitt eget lagringstak; en publik fil som flera organisationer hämtar lagras en gång, och innehåll som hämtats med en organisations egna inloggningsuppgifter lämnas aldrig ut till en annan. Med runner pull-through påslaget (standard för varje organisation som inte har sparat egna inställningar för beroendeproxyn) pekas varje jobb mot proxyn när det startar:
| Trafik | Injiceras som |
|---|---|
| npm, pnpm, pip, uv | jobbets npmrc, PIP_INDEX_URL, UV_DEFAULT_INDEX |
Go, Maven, NuGet, Trivys sårbarhetsdatabas (var och en enligt runner_pull_through_ecosystems, alla som standard) | GOPROXY, en Maven-spegel av Central, en maskingemensam NuGet.Config, TRIVY_DB_REPOSITORY och TRIVY_JAVA_DB_REPOSITORY |
Container-images från ghcr.io, quay.io, mcr.microsoft.com, registry.k8s.io, public.ecr.aws, registry.access.redhat.com; och docker.io, registry.redhat.io, registry.connect.redhat.com endast med organisationens egna inloggningsuppgifter, aldrig för pull requests från forkar | registry-konfigurationen för VM:ens Docker-daemon |
Jobbet autentiserar sig mot proxyn med inloggningsuppgifter som är begränsade till den egna organisationen och körningen, så paketinstallationer passerar organisationens paketpolicy och skanning efter skadlig kod och syns i dess åtkomstlogg. För container-images på den här vägen är proxyn en cache, inte en spärr: en hämtning som proxyn nekar faller igenom till registryt. Proxyn kontrolleras i början av varje jobb; om den inte går att nå installerar och hämtar jobbet från de publika registryna via egress-proxyn, och reservvägen loggas. Se Hosted runners.
I det vanliga fallet (publika beroenden) är detta transparent, så ingenting i ditt arbetsflöde behöver ändras.
Om du använder ett privat index eller registry, observera att det injicerade värdet är standardvärdet, så det har företräde framför ett standardvärde i en incheckad konfigurationsfil (pip.conf, .npmrc, go env). Ange din privata källa uttryckligen så att den vinner; hur du gör det för varje verktyg står nedan.
Använd ett index per installation: proxyn, eller ett privat index som självt speglar det publika registryt. Lägg aldrig till ett andra index bredvid det första (--extra-index-url, PIP_EXTRA_INDEX_URL, UV_EXTRA_INDEX_URL): installeraren tar då ett paketnamn från vilket index som helst som erbjuder det, och det är så dependency confusion-attacker fungerar. Se beroendeproxyns anteckningar om PyPI.
- pip:
pip install --index-url <your-private> ...(ett--index-urlpå kommandoraden åsidosätter det injiceradePIP_INDEX_URL), eller ange det i stegetsenv:i arbetsflödet, när det privata indexet speglar de publika paket du behöver. För att installera dina egna paket från organisationens registry medan publika paket kommer via proxyn använder du två separata installationer, var och en med ett enda index: först de publika beroendena, sedan dina paket med--index-url https://frem.sh/api/packages/<org>/pypi/simple/ --no-deps. - uv: samma regel. Knyt dina egna paket till ditt registry med namn, med ett index markerat
explicit = trueoch en post i[tool.uv.sources]per paket, så att inget namn kan lösas upp från båda. - npm: sätt
NPM_CONFIG_REGISTRY(och, för pnpm 11,PNPM_CONFIG_REGISTRY) i stegetsenv:till ett registry som speglar det publika, eller behåll proxyn och skicka bara ditt scope till ditt registry (@<your-scope>:registry=…), vilket knyter varje namn i det scopet till ett enda registry. Se Paketcacher. - Go: sätt
GOPRIVATE(t.ex.GOPRIVATE=git.example.com/*) så att verktygskedjan går förbi proxyn och checksumdatabasen för dina privata modulsökvägar.
Isolering på VM-nivå
| Yta | Upprätthålls genom |
|---|---|
| Process | Stegen i arbetsflödet körs som standard som VM:ens användare runner; sudo finns så att arbetsflöden kan köra apt install, köra docker, konfigurera systemverktyg och så vidare (samma förväntningar som på GitHubs hostade runners). Gränsen är hypervisorn under VM:en, inte user namespaces |
| Arbetsyta | Lokalt utrymme på VM:en i /workspace (och /tmp), som rensas mellan jobb och förstörs med VM:en. Se Jobb som delar en VM för vad som kan följa med mellan jobb i samma commit |
| Identitet | VM:en har ingen IAM-agency kopplad. Frågor till metadatatjänsten returnerar bara den nätverksidentitet som behövs för att starta; ingen åtkomst till plattformens API:er |
| Resursgränser | VM:ens ECS-flavor sätter ett hårt tak för CPU och minne. En skenande kund kan inte förbruka kapacitet utöver VM:ens egna gränser. Skenande last totalt begränsas av taket för samtidiga jobb per tenant och plattformens beräkningskapacitet |
| Container-körtid | Docker (direkt på VM:en): daemonen körs på VM-värden, inte som sidecar. docker build, docker compose, kind och testcontainers fungerar direkt, utan DinD-komplexitet. För arbetsflöden som behöver extra möjligheter i container-körtiden skickar etiketten dind-privileged jobbet till en flavor med nästlad KVM påslagen |
| Hypervisor | T Cloud Publics KVM-baserade ECS-hypervisor, samma gräns mellan virtuella maskiner för flera tenants som används för alla kommersiella T Cloud Public-kunder som kör ECS i regionen |
Kärnisolering mellan och inom tenants
Det här är det bärande påståendet i modellen för hostade runners. Varje VM är knuten till en commit i ett repo: en Linux-gästkärna som körs på T Cloud Publics hypervisor. Den kör commitens jobb ett i taget, och plattformen förstör den när de är klara. Ett jobb från en annan commit, ett annat repo eller en annan organisation får alltid en annan VM med en annan kärna.
| Mekanism | Hur |
|---|---|
| VM per commit | Plattformen startar en VM för ett väntande jobb när ingen VM för den commiten är ledig. Ett utbrott på kärnnivå (t.ex. CVE-2024-1086 nf_tables) komprometterar bara DEN engångs-VM:en och jobben i samma commit, inte tenants som körs bredvid |
| Snabb nedstängning när jobben är klara | Efter sitt sista jobb väntar VM:en upp till 60 sekunder på ett nytt jobb från samma commit, stängs sedan av och tas bort. En separat reaper utanför det ordinarie flödet fångar föräldralösa VM:ar (runnern registrerades aldrig, borttagningen missades) inom en absolut reservgräns på 6 h 10 min |
| ENI och SG per VM | Nätverksisolering som är oberoende av kärngränsen. Tenant A:s VM kan inte nå tenant B:s VM över nätverket: separata ENI, separata Security Groups |
| Livscykel för en enda commit | Varje jobb körs i en ny jobbcontainer. En VM återanvänds aldrig för en annan commit, ett annat repo eller en annan tenant, så den håller aldrig en tidigare tenants hemligheter |
| OIDC-token | Varje VM får en ny OIDC-token, signerad av runnerns OIDC-utfärdare med aud=fremforge och sub=repo:<owner>/<repo>:ref:<refname>. Kunders förtroendepolicyer i molnet utgår från denna audience och sub för assume_role_with_web_identity |
Jobb som delar en VM
En VM återanvänds bara för jobb från samma commit i samma repository (och därmed samma organisation): andra arbetsflöden, körningar eller omkörningar av den commiten, eller en deploy efter dess build. Mellan de jobben återställer fremforge allt som vi vet kan innehålla inloggningsuppgifter: jobbcontainern, /workspace och /tmp, Docker-inloggningen och npm-konfigurationen samt de vanliga filerna med inloggningsuppgifter. Om återställningen inte kan bekräftas tar VM:en inte emot fler jobb, och nästa jobb får en ny VM.
Återställningen är inte detsamma som en ny VM. Jobb kan använda VM:ens Docker-daemon, som är root på VM:en, så ett jobb kan lämna något efter sig till ett senare jobb i samma commit. Images som hämtats med inloggningsuppgifter, verktygscachen och Gos build-cache ligger också kvar på VM:en. Behandla alla jobb i en commit som en enda förtroendedomän:
- Kör inte opålitlig kod (fixtures från bidragsgivare, ogranskade beroendeuppdateringar) i samma commits pipeline som ett jobb som har tillgång till produktionshemligheter.
- Lägg deployer bakom ett godkännande för en miljö (se Dela upp jobben
validateochapply), och föredra kortlivade OIDC-inloggningsuppgifter framför lagrade nycklar. - Om en deploy aldrig får dela maskin med en build, kör den på en egen runner (BYO).
Gränser per tenant
| Gräns | Standard | Åsidosättning |
|---|---|---|
| Samtidiga jobb per tenant | 2 per användare garanterat, minst 10 och högst 60 (test: 2). Därutöver kan en betalande tenant använda ledig kapacitet upp till 60, men bara så länge 20 platser i plattformen förblir lediga, så att ingen tenants topplast fördröjer en annans garanterade jobb. Som jämförelse har GitHubs hostade runners: Free 20, Pro 40, Team 60, Enterprise 500 | Enterprise-on-Demand: en åsidosättning per tenant ersätter den garanterade andelen |
| Samtidiga jobb totalt för alla tenants | Begränsas av plattformens beräkningskapacitet i eu-de | — |
| Tak för jobbtakt | 7 jobbstarter per rullande 5-minutersfönster per plats i tenantens topptak: 420 för en betalande tenant, 14 för en testtenant. Det skalar med ett höjt tak för samtidighet, så det är ett skydd mot missbruk och aldrig den gräns som en normal pipeline når först. GitHub publicerar ingen gräns för jobbstarter | Åsidosättning per tenant |
| Väggklocktid per jobb | 3600 s (60 min) | Kan konfigureras per steg i arbetsflödet men har ett tak på 6 h |
| Pool med runner-minuter | seat_cap × 1000 minutes/month (eller ett uttryckligt tak per tenant) | Operatörskonfiguration; motsvarar den publika prislistan |
| VM-flavor | c9.large.2 (2 vCPU / 4 GiB RAM) som standard; c9.xlarge.4 (4 vCPU / 16 GiB) för runs-on: [fremforge, large] | Etiketten runs-on: i arbetsflödet skickar jobbet till andra flavors |
När en tenant överskrider poolen med runner-minuter och överförbrukning inte är påslagen (eller är fryst) vägrar plattformen att skicka ut jobb: arbetsflödets köade jobb går över till Cancelled i Forgejo. Tenants med överförbrukning påslagen fortsätter att köra och faktureras för överskottet.
När en tenant når taket för samtidiga jobb väntar ytterligare köade jobb och skickas ut så snart en plats blir ledig.
Vad kundkod INTE kan göra
- Nå fremforges api, Forgejos interna endpoint eller någon annan plattformstjänst (api:t är bara tillgängligt via den publika Bunny-apex-domänen med korrekt autentisering, precis som för alla externa anropare)
- Läsa en annan tenants runner-VM (separat ENI; trafik mellan VM:ar nekas uttryckligen i Security Group)
- Läsa en annan tenants hemligheter (hemligheter i arbetsflöden skickas bara till den runner som kör arbetsflödet som äger dem; när VM:en förstörs raderas inloggningsuppgifterna i minnet)
- Spara tillstånd utöver sin egen commit (VM:en förstörs när commitens jobb är klara, och ingen disk delas mellan VM:ar)
- Nå OTC-API:t direkt med privilegierat scope (ingen IAM-agency kopplad; identiteten som härleds från metadata har inga roller)
- Nå molnets metadata-endpoints (
169.254.169.254,100.100.100.200, båda uttryckligen nekade i SSRF-skyddet) - Tömma runnerpoolen (tak för samtidiga jobb per tenant plus plattformens globala beräkningstak)
- Svälta ut kön för andra tenants (tak för samtidiga jobb per tenant)
- Bryta sig ut ur hypervisorn (VM:en är gränsen; värden under den tillhör T Cloud Public och isoleras av samma VMM som separerar varje kommersiell ECS-tenant i regionen)
Rekommenderade upplägg för arbetsflöden
Dela upp jobben validate och apply
Kör opålitlig kod (builds av PR:er, beroendeskanningar, testfixtures från bidragsgivare) i ett validate-jobb utan produktionsinloggningsuppgifter, och lägg apply-jobbet bakom ett godkännande för en miljö. Validate-jobbet ser aldrig apply-jobbets hemligheter. Båda jobben hör dock till samma commit, så de kan köras på samma VM efter varandra: se Jobb som delar en VM.
Fäst uses:-referenser med SHA när leveranskedjan spelar roll
uses: actions/checkout@v4 löses upp till frem.sh/actions/checkout (fremforges inbyggda Forgejo-pull-spegel av github.com/actions/checkout, synkas var 8:e timme). Spegeln följer upstreams taggar, inklusive force-pushar, så att fästa med SHA är det enda sättet att garantera att innehållet är identiskt byte för byte:
# Bytewise-stable across maintainer force-pushes
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.2.0
# Picks up upstream's current HEAD at last mirror sync — including
# any maintainer-credential-compromise force-push
- uses: actions/checkout@v4Använd OIDC för autentisering mot molnet, inte långlivade hemligheter
Varje runner-VM får en ny OIDC-token med aud=fremforge. Konfigurera molnleverantörens förtroendepolicy så att den accepterar denna token och byter den mot kortlivade inloggningsuppgifter. Undvik att lagra långlivade AWS-åtkomstnycklar eller JSON-filer för GCP-tjänstekonton i arbetsflödets hemligheter, där de hamnar i processens miljövariabler.
Sätt deadlines för stegen i arbetsflödet
Sätt timeout-minutes: på enskilda steg för att begränsa hur länge ett steg som har hängt sig kan köras. Taket för väggklocktid på VM-nivå (activeDeadlineSeconds=3600) är plattformens golv; timeouts på stegnivå ger snabbare återkoppling.
Sammanfattning av hotmodellen
| Hot | Åtgärd |
|---|---|
| Kundens arbetsflöde innehåller inloggningsuppgifter | Push-skyddet (Gitleaks) blockerar commiten innan den hamnar i historiken |
| Kundens arbetsflöde försöker läsa fremforges plattform | ENI och SG per VM nekar; ingen IAM-agency kopplad till VM:en; api:t nås bara via den publika, autentiserade vägen |
| En tenants jobb försöker läsa en annans VM | ENI per VM på separata IP:n; SG nekar som standard i hela runner-subnätet; och de ligger på olika VM:ar, med olika kärnor |
| Utbrott på kärnnivå (root i VM:en → värdens kärna via en CVE) | Komprometterar bara engångs-VM:en som kör den commitens jobb. Tenants som körs bredvid påverkas INTE, de har sina egna VM:ar. VM:en förstörs i vilket fall som helst ungefär en minut efter commitens sista jobb |
| Kundens arbetsflöde försöker föra ut data via DNS | DNS är tillåtet (arbetsflöden behöver det av legitima skäl), men egress-proxyn loggar varje CONNECT med fullständigt värdnamn för granskning |
Komprometterad act_runner hos upstream | Binär som testats av upstream (vi forkar inte). Speglas via EU-cachen med Trivy-spärrar på runner-imagen. CVE-rättningar kommer med den veckovisa ombyggnaden |
| Kundens arbetsflöde försöker nå metadata-IP:n | Uttryckligt nekande i SSRF-skyddet: 169.254.169.254 (AWS-stil) och 100.100.100.200 (T Cloud Public) |
| En kund monopoliserar kapaciteten | Garanterad andel per tenant (2 per användare, 10 till 60) där topplast bara får använda ledig kapacitet som lämnar 20 platser lediga, tak för jobbtakt (7 starter per topplats per 5 min), tak för poolminuter |
| En inaktuell runner-VM lever kvar efter uppgiften | Taket för väggklocktid per jobb dödar hårt vid deadline; plattformen tar bort VM:en när runnern avslutas; en separat reaper utanför det ordinarie flödet fångar föräldralösa VM:ar med en absolut reservgräns på 6 h 10 min |
Spårbarhet
Varje utskick av jobb loggas på tre ställen:
- Tabellen
runner_jobs: tenant_id, repo, forgejo_task_id, started_at, finished_at, duration_seconds, vm_id. Källa för aggregering till fakturering och för fliken Infrastructure i operatörskonsolen - Tabellen
metering_events: rader medcode=runner_minutesom härleds frånrunner_jobs.duration_seconds. Styr fakturaraderna och tenantens panel för förbrukade runner-minuter - Operatörens loggström: strukturerade loggar över startade VM:ar för operatören, med löpande larm vid nekade utskick, VM:ar som har fastnat, överskridna deadlines och slut på kapacitet
Tenants ser sin egen förbrukning under Organisationens administration → CI-användning, inklusive antalet minuter per månad och en tabell med varaktighet per jobb.