Afhængigheder
Hvad er en transitiv afhængighed?
En transitiv afhængighed er en pakke, din software bruger, fordi en anden pakke har brug for den. Du har aldrig deklareret den, men den er i det, du leverer, med sine egne sårbarheder og sin egen licens. Her kan du se, hvordan du viser dem, retter dem og beviser, hvilke der gik ind i hver release.
Opdateret
Direkte og transitive afhængigheder
En direkte afhængighed er en, du deklarerer i dit manifest. En transitiv afhængighed er en, dine afhængigheder deklarerer, og deres igen, så langt ned som træet går. Pakkehåndteringen resolver hele træet, når den installerer, og registrerer som regel resultatet i en lockfile.
Tag en app, der deklarerer én pakke, express. Kører du npm ls --all, kan du se, hvad den ene linje trækker med sig:
my-app
└─┬ express ← direkte: du har deklareret den
├─┬ body-parser ← transitiv, dybde 1
│ └── raw-body ← transitiv, dybde 2
├── cookie ← transitiv, dybde 1
└─┬ send ← transitiv, dybde 1
└── mime-types ← transitiv, dybde 2 Det rigtige træ er længere. De fleste pakker i en typisk applikation kommer ind på denne måde, så det meste af den kode, du leverer, er valgt af pakkehåndteringen og ikke af dig.
| Direkte afhængighed | Transitiv afhængighed | |
|---|---|---|
| Hvem valgte den | Dit team, i manifestet | Pakkehåndteringen, ud fra dine afhængigheders manifester |
| Hvor den er angivet | Manifest og lockfile | Lockfile, hvis der er en |
| Hvem styrer versionen | Dig | Forælderpakkens versionsinterval, medmindre du tilsidesætter det |
| Sådan retter du en sårbarhed | Opgradér den | Opgradér forælderen, eller tilsidesæt versionen |
Derfor betyder transitive afhængigheder noget
De har de samme risici som direkte afhængigheder, med mindre synlighed og mindre kontrol.
- Sårbarheder. Da Log4Shell, en zero-day i log4j, blev offentliggjort, fandt Googles Open Source Insights-team 35.863 Maven Central-artefakter, der afhang af den berørte log4j-kode. Omkring 7.000 afhang af den direkte. De fleste trak den ind som en transitiv afhængighed, og mange kunne ikke rettes, før en mellemliggende pakke havde leveret en rettelse.
- Forsyningskædeangreb. Bagdøren i xz utils (CVE-2024-3094) nåede OpenSSH, uden at OpenSSH afhang af xz. Flere Linux-distributioner patcher OpenSSH til at understøtte systemd-notifikationer, og libsystemd afhænger af liblzma, det bibliotek, xz leverer.
- Licenser. Hver transitiv pakke har sin egen licens. En copyleft-licens tre niveauer nede er lige så bindende som en, du selv har tilføjet.
- Breaking changes. En forælderpakke, der accepterer et versionsinterval, lukker en ny transitiv release ind ved næste nye installation, selv om intet i dit manifest er ændret.
Sådan viser du dine transitive afhængigheder
Alle større pakkehåndteringer kan udskrive det resolvede træ:
| Økosystem | 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 |
For at finde ud af, hvorfor en enkelt pakke er der, kan du bede om stien til den: npm explain <package>, pnpm why, yarn why eller cargo tree -i <crate>. Vil du arbejde med stierne i stedet for at udskrive dem, så se afhængighedsgrafer.
Sådan retter du en sårbar transitiv afhængighed
- Opgradér forælderen. Find den direkte afhængighed, der trækker pakken ind, og skift til en release, der kræver en rettet version. Det er den reneste rettelse, fordi forælderens maintainers har testet den.
- Opdatér lockfilen. Hvis forælderens versionsinterval allerede tillader den rettede version, er det nok at opdatere posten i lockfilen, for eksempel med
cargo update -p <crate>eller en nynpm update. - Tilsidesæt versionen. Har forælderen ikke leveret en rettelse, så gennemtving selv versionen:
overridesi npm og pnpm,resolutionsi Yarn, en constraints-fil med pip eller et højere krav igo.mod. Test det, for forælderen er aldrig blevet udgivet mod den version. - Erstat eller fjern forælderen. Virker ingen af delene, er den afhængighed, der trækker pakken ind, måske ikke længere værd at beholde.
Nogle værktøjer til software composition analysis tilføjer reachability-analyse, som tjekker, om din kode overhovedet kan kalde den sårbare funktion, for at afgøre, hvilken af disse rettelser der kommer først. Se SCA-værktøjer sammenlignet for mulighederne.
Derfor er lockfilen ikke hele historien
De fleste værktøjer bygger deres billede af transitive afhængigheder ud fra lockfilen. Det virker, når lockfilen er der, er opdateret og er det eneste, buildet installerer fra. I CI er det ofte ikke tilfældet:
- Ingen lockfile. En
requirements.txtmed versionsintervaller eller et projekt, der aldrig har committet sin lockfile, bliver resolvet på ny ved hvert build, så de transitive versioner kan ændre sig mellem builds. - Caches. CI-caches gendanner pakker fra tidligere kørsler i stedet for at hente dem, og det, de indeholder, er ikke altid det, den aktuelle lockfile siger. Se hvad GitHub Actions-cachen skjuler.
- Build-værktøjer. Linters, compilere, kodegeneratorer og setup-trin bringer deres egne afhængighedstræer ind i buildet. De er ikke i din applikations manifest, men de kører med adgang til din kildekode og dine secrets.
- Install-scripts og downloads. En pakkes install-script kan hente mere kode, og et build-trin kan downloade en binærfil direkte. Ingen af delene efterlader en post i en lockfile.
Den eneste komplette registrering af, hvilke transitive afhængigheder der gik ind i en release, er en, der er foretaget, mens releasen blev bygget. SBOM'er fra buildet forklarer forskellen på at registrere et build og at scanne dets filer bagefter.
Transitive afhængigheder og CRA
EU's Cyber Resilience Act kræver, at producenter udarbejder en SBOM, der dækker "som minimum produkternes afhængigheder på øverste niveau" (bilag I, del II, punkt 1). Transitive afhængigheder nævnes ikke, så det lovpligtige minimum er lavere, end mange guider antyder. Men CRA kræver også, at du identificerer og håndterer sårbarheder i de komponenter, dit produkt indeholder, og en sårbar transitiv pakke er en af dem. En SBOM, der stopper ved første niveau, efterlader det meste af dit afhængighedstræ uden for den proces. Se CRA's SBOM-krav for detaljerne.
Sådan sporer CRACI transitive afhængigheder
CRACI er en GitHub Actions-runner, der registrerer buildet i stedet for at scanne det. Mens hvert job kører, registrerer en pakkebevidst proxy hver pakke, jobbet henter, i alle lag af træet, inklusive pakker gendannet fra CI-caches. Det giver et mere komplet billede af de afhængigheder, der er i brug, end scannerbaserede værktøjer.
- Alle lag, ærligt angivet. Hvert jobs SBOM har en fuldstændighedsstatus, så du ved, hvornår en registrering ikke kunne se alt.
- Træet, synligt. En afhængighedsgraf viser SBOM'ens afhængighedslag i flere visninger.
- Eksporter, der indeholder dem. Eksporter i CycloneDX og SPDX indeholder transitive afhængigheder, hver med sin deklarerede licens.
- Hvilke builds der var ramt. Når en ny sårbarhed dukker op, viser build-historikken, hvilke tidligere builds der indeholdt den sårbare pakke, direkte eller transitiv, så du ved, i hvilken periode du leverede den (se er vi ramt af en CVE?). Historikken følger din opbevaringstid, som standard 180 dage.
- Policy gates. Et build, der indeholder en bestemt CVE, kan blokeres, før det leveres.
CRACI laver ikke reachability-analyse. Har du brug for at vide, om din kode kalder en sårbar funktion, så kombinér det med et SCA-værktøj, der gør det. Se SBOM-generering i CRACI, eller book en demo for at registrere et af dine egne builds.
Transitive afhængigheder: ofte stillede spørgsmål
Hvad er en transitiv afhængighed?
En transitiv afhængighed er en pakke, din software bruger, fordi en anden pakke har brug for den. Hvis din app afhænger af bibliotek A, og A afhænger af bibliotek B, er B en transitiv, eller indirekte, afhængighed af din app. Du har aldrig deklareret den, men den leveres med din software.
Hvad er forskellen på en direkte og en transitiv afhængighed?
En direkte afhængighed er en, du deklarerer i dit manifest, for eksempel package.json, pyproject.toml eller go.mod. En transitiv afhængighed er en, dine direkte afhængigheder deklarerer, i et hvilket som helst lag. Du vælger direkte afhængigheder; pakkehåndteringen vælger transitive, når den resolver træet.
Er en transient afhængighed det samme som en transitiv afhængighed?
Ja, i praksis. "Transient afhængighed" er en almindelig fejl for "transitiv afhængighed". Det korrekte udtryk er transitiv, fra den transitive relation: Hvis A afhænger af B, og B afhænger af C, så afhænger A af C.
Hvordan retter jeg en sårbarhed i en transitiv afhængighed?
Opgradér først den direkte afhængighed, der trækker den ind, hvis der findes en release med en rettet version. Hvis ikke, så gennemtving den rettede version med din pakkehåndterings override-mekanisme, for eksempel npm overrides, Yarn resolutions eller en pip constraints-fil, og test, at forælderpakken stadig virker med den.
Hvordan viser jeg alle transitive afhængigheder?
Alle pakkehåndteringer kan udskrive det resolvede træ: npm ls --all, pnpm list --depth Infinity, uv tree eller pipdeptree, go mod graph, cargo tree, mvn dependency:tree eller gradle dependencies. For at se, hvad et produktionsbuild faktisk installerede, skal du registrere selve buildet.
Kræver EU's Cyber Resilience Act transitive afhængigheder i SBOM'en?
Forordningen sætter bunden ved "som minimum produkternes afhængigheder på øverste niveau" (bilag I, del II, punkt 1). Transitive afhængigheder nævnes ikke, men en sårbar transitiv pakke er stadig i dit produkt, og CRA kræver, at du håndterer sårbarheder i de komponenter, du leverer.
Se hver afhængighed, dit build trak ind
Kør ét GitHub Actions-workflow på CRACI, og få en registreret SBOM over hver pakke, det hentede, i alle lag af træet.
Book en demo