Beroenden
Vad är ett transitivt beroende?
Ett transitivt beroende är ett paket som din programvara använder eftersom ett annat paket behöver det. Du har aldrig deklarerat det, men det finns i det du levererar, med egna sårbarheter och en egen licens. Så här ser du dem, åtgärdar dem och bevisar vilka som ingick i varje release.
Uppdaterad
Direkta och transitiva beroenden
Ett direkt beroende är ett som du deklarerar i ditt manifest. Ett transitivt beroende är ett som dina beroenden deklarerar, och deras beroenden i sin tur, så djupt trädet går. Pakethanteraren löser hela trädet vid installationen och registrerar oftast resultatet i en lockfile.
Ta en app som deklarerar ett enda paket, express. Om du kör npm ls --all ser du vad den enda raden för med sig:
my-app
└─┬ express ← direkt: du deklarerade det
├─┬ body-parser ← transitivt, djup 1
│ └── raw-body ← transitivt, djup 2
├── cookie ← transitivt, djup 1
└─┬ send ← transitivt, djup 1
└── mime-types ← transitivt, djup 2 Det verkliga trädet är längre. De flesta paketen i en typisk applikation kommer på det här sättet, vilket betyder att det mesta av koden du levererar valdes av pakethanteraren, inte av dig.
| Direkt beroende | Transitivt beroende | |
|---|---|---|
| Vem valde det | Ditt team, i manifestet | Pakethanteraren, utifrån dina beroendens manifest |
| Var det listas | Manifest och lockfile | Lockfile, om det finns en |
| Vem styr versionen | Du | Föräldrapaketets versionsintervall, om du inte åsidosätter det |
| Hur du åtgärdar en sårbarhet | Uppgradera det | Uppgradera föräldern, eller åsidosätt versionen |
Varför transitiva beroenden är viktiga
De bär samma risker som direkta beroenden, med mindre insyn och mindre kontroll.
- Sårbarheter. När Log4Shell, en zero-day i log4j, offentliggjordes fann Googles team Open Source Insights 35 863 artefakter i Maven Central som var beroende av den berörda log4j-koden. Omkring 7 000 var direkt beroende av den. Majoriteten drog in den som ett transitivt beroende, och många kunde inte åtgärdas förrän ett paket däremellan först hade släppt en fix.
- Attacker mot leveranskedjan. Bakdörren i xz utils (CVE-2024-3094) nådde OpenSSH utan att OpenSSH var beroende av xz. Flera Linux-distributioner patchar OpenSSH för att stödja systemd-notifieringar, och libsystemd är beroende av liblzma, biblioteket som xz tillhandahåller.
- Licenser. Varje transitivt paket har en egen licens. En copyleft-licens tre nivåer ner är lika bindande som en du lagt till själv.
- Brytande ändringar. Ett föräldrapaket som accepterar ett versionsintervall släpper in en ny transitiv release vid nästa nya installation, även när inget i ditt manifest har ändrats.
Så listar du dina transitiva beroenden
Alla stora pakethanterare kan skriva ut det upplösta trädet:
| Ekosystem | Kommando |
|---|---|
| npm | npm ls --all |
| pnpm | pnpm list --depth Infinity |
| Python (uv) | uv tree |
| Python (pip) | pipdeptree |
| Go | go mod graph |
| Rust | cargo tree |
| Maven | mvn dependency:tree |
| Gradle | gradle dependencies |
För att ta reda på varför ett enskilt paket finns där kan du fråga efter sökvägen till det: npm explain <package>, pnpm why, yarn why eller cargo tree -i <crate>. För att arbeta med sökvägarna i stället för att skriva ut dem, se beroendegrafer.
Så åtgärdar du ett sårbart transitivt beroende
- Uppgradera föräldern. Hitta det direkta beroende som drar in paketet och gå över till en release som kräver en åtgärdad version. Det här är den renaste åtgärden, eftersom förälderns underhållare har testat den.
- Uppdatera lockfilen. Om förälderns versionsintervall redan tillåter den åtgärdade versionen räcker det att uppdatera posten i lockfilen, till exempel med
cargo update -p <crate>eller en nynpm update. - Åsidosätt versionen. Om föräldern inte har släppt någon fix tvingar du fram versionen själv:
overridesi npm och pnpm,resolutionsi Yarn, en constraints-fil med pip eller ett högre krav igo.mod. Testa det, eftersom föräldern aldrig släpptes mot den versionen. - Byt ut eller ta bort föräldern. Om inget av detta fungerar kanske beroendet som drar in paketet inte längre är värt att behålla.
Vissa verktyg för software composition analysis lägger till reachability-analys, som kontrollerar om din kod över huvud taget kan anropa den sårbara funktionen, för att avgöra vilken av dessa åtgärder som kommer först. Se SCA-verktyg i jämförelse för alternativen.
Varför lockfilen inte är hela sanningen
De flesta verktyg bygger sin bild av transitiva beroenden utifrån lockfilen. Det fungerar när lockfilen finns, är aktuell och är det enda bygget installerar från. I CI är det ofta inte fallet:
- Ingen lockfile. En
requirements.txtmed versionsintervall eller ett projekt som aldrig checkat in sin lockfile löses på nytt i varje bygge, så de transitiva versionerna kan ändras mellan byggen. - Cachar. CI-cachar återställer paket från tidigare körningar i stället för att hämta dem, och det de innehåller är inte alltid det som den aktuella lockfilen säger. Se vad GitHub Actions-cachen döljer.
- Byggverktyg. Linters, kompilatorer, kodgeneratorer och setup-steg för in egna beroendeträd i bygget. De finns inte i applikationens manifest, men de körs med åtkomst till din källkod och dina hemligheter.
- Installationsskript och nedladdningar. Ett pakets installationsskript kan hämta mer kod, och ett byggsteg kan ladda ner en binär direkt. Inget av dem lämnar en post i en lockfile.
Den enda kompletta registreringen av vilka transitiva beroenden som ingick i en release är en som görs medan releasen byggdes. SBOM:er från bygget förklarar skillnaden mellan att registrera ett bygge och att skanna dess filer i efterhand.
Transitiva beroenden och CRA
EU:s Cyber Resilience Act kräver att tillverkare tar fram en SBOM som omfattar "at the very least the top-level dependencies of the products" (bilaga I, del II, punkt 1). Transitiva beroenden nämns inte, så den rättsliga miniminivån är lägre än många guider antyder. Men CRA kräver också att du identifierar och hanterar sårbarheter i de komponenter som din produkt innehåller, och ett sårbart transitivt paket är en av dem. En SBOM som stannar vid den första nivån lämnar större delen av ditt beroendeträd utanför den processen. Se CRA:s krav på SBOM:er för detaljerna.
Så spårar CRACI transitiva beroenden
CRACI är en GitHub Actions-runner som registrerar bygget i stället för att skanna det. Medan varje jobb körs registrerar en paketmedveten proxy varje paket som jobbet hämtar, i alla led av trädet, inklusive paket som återställs från CI-cachar. Det ger en mer komplett bild av de beroenden som används än skannerbaserade verktyg.
- Alla led, redovisat ärligt. Varje jobbs SBOM har en fullständighetsstatus, så du vet när en registrering inte kunde se allt.
- Trädet synligt. En beroendegraf visar SBOM:ens beroendelager, i flera vyer.
- Exporter som tar med dem. Exporter i CycloneDX och SPDX inkluderar transitiva beroenden, vart och ett med sin deklarerade licens.
- Vilka byggen som berördes. När en ny sårbarhet dyker upp visar bygghistoriken vilka tidigare byggen som innehöll det sårbara paketet, direkt eller transitivt, så att du vet under vilken period du levererade det (se är vi drabbade av en CVE?). Historiken följer din lagringstid, 180 dagar som standard.
- Policyspärrar. Ett bygge som innehåller en specifik CVE kan blockeras innan det levereras.
CRACI gör inte reachability-analys. Om du behöver veta om din kod anropar en sårbar funktion, kombinera det med ett SCA-verktyg som gör det. Se SBOM-generering i CRACI eller boka en demo för att registrera ett av dina egna byggen.
Transitiva beroenden: vanliga frågor
Vad är ett transitivt beroende?
Ett transitivt beroende är ett paket som din programvara använder eftersom ett annat paket behöver det. Om din app är beroende av bibliotek A och A är beroende av bibliotek B, så är B ett transitivt, eller indirekt, beroende för din app. Du har aldrig deklarerat det, men det levereras med din programvara.
Vad är skillnaden mellan ett direkt och ett transitivt beroende?
Ett direkt beroende är ett som du deklarerar i ditt manifest, till exempel package.json, pyproject.toml eller go.mod. Ett transitivt beroende är ett som dina direkta beroenden deklarerar, på vilket djup som helst. Du väljer direkta beroenden; pakethanteraren väljer transitiva när den löser trädet.
Är ett transient beroende samma sak som ett transitivt beroende?
Ja, i praktiken. ”Transient beroende” är en vanlig felsägning för ”transitivt beroende”. Den korrekta termen är transitivt, från den transitiva relationen: om A är beroende av B och B är beroende av C, så är A beroende av C.
Hur åtgärdar jag en sårbarhet i ett transitivt beroende?
Uppgradera först det direkta beroende som drar in det, om det finns en release med en åtgärdad version. Om inte, tvinga fram den åtgärdade versionen med din pakethanterares mekanism för åsidosättning, till exempel npm overrides, Yarn resolutions eller en constraints-fil för pip, och testa att föräldrapaketet fortfarande fungerar med den.
Hur listar jag alla transitiva beroenden?
Alla pakethanterare kan skriva ut det upplösta trädet: npm ls --all, pnpm list --depth Infinity, uv tree eller pipdeptree, go mod graph, cargo tree, mvn dependency:tree eller gradle dependencies. För vad ett produktionsbygge faktiskt installerade, registrera själva bygget.
Kräver EU:s Cyber Resilience Act transitiva beroenden i SBOM:en?
Förordningen sätter miniminivån vid "at the very least the top-level dependencies of the products" (bilaga I, del II, punkt 1). Transitiva beroenden nämns inte, men ett sårbart transitivt paket finns ändå i din produkt, och CRA kräver att du hanterar sårbarheter i de komponenter du levererar.
Se varje beroende som ditt bygge drog in
Kör ett GitHub Actions-arbetsflöde på CRACI och få en registrerad SBOM över varje paket det hämtade, i alla led av trädet.
Boka en demo