Závislosti
Co je tranzitivní závislost?
Tranzitivní závislost je balíček, který váš software používá, protože ho potřebuje jiný balíček. Nikdy jste ho nedeklarovali, ale je v tom, co dodáváte, s vlastními zranitelnostmi a licencí. Zde je návod, jak je zobrazit, opravit a doložit, které z nich šly do každého vydání.
Aktualizováno
Přímé a tranzitivní závislosti
Přímou závislost deklarujete ve svém manifestu. Tranzitivní závislost deklarují vaše závislosti, jejich závislosti zase své, a tak dál, jak hluboko strom sahá. Správce balíčků při instalaci vyřeší celý strom a výsledek obvykle zapíše do lockfilu.
Vezměme aplikaci, která deklaruje jediný balíček, express. Příkaz npm ls --all ukáže, co tento jeden řádek přináší:
my-app
└─┬ express ← přímá: deklarovali jste ji
├─┬ body-parser ← tranzitivní, hloubka 1
│ └── raw-body ← tranzitivní, hloubka 2
├── cookie ← tranzitivní, hloubka 1
└─┬ send ← tranzitivní, hloubka 1
└── mime-types ← tranzitivní, hloubka 2 Skutečný strom je delší. Většina balíčků v typické aplikaci přichází právě takto, což znamená, že většinu kódu, který dodáváte, vybral správce balíčků, ne vy.
| Přímá závislost | Tranzitivní závislost | |
|---|---|---|
| Kdo ji vybral | Váš tým, v manifestu | Správce balíčků, podle manifestů vašich závislostí |
| Kde je uvedená | Manifest a lockfile | Lockfile, pokud existuje |
| Kdo řídí verzi | Vy | Rozsah verzí nadřazeného balíčku, pokud ho nepřepíšete |
| Jak opravíte zranitelnost | Upgradem | Upgradem nadřazeného balíčku nebo přepsáním verze |
Proč na tranzitivních závislostech záleží
Nesou stejná rizika jako přímé závislosti, jen s menší viditelností a menší kontrolou.
- Zranitelnosti. Když byla zveřejněna Log4Shell, zero-day zranitelnost v log4j, tým Open Source Insights společnosti Google našel 35 863 artefaktů v Maven Central, které závisely na dotčeném kódu log4j. Zhruba 7 000 z nich na něm záviselo přímo. Většina ho stahovala jako tranzitivní závislost a mnohé nešlo opravit, dokud opravu nevydal některý balíček mezi nimi.
- Útoky na dodavatelský řetězec. Zadní vrátka v xz utils (CVE-2024-3094) se dostala do OpenSSH, aniž by OpenSSH na xz záviselo. Několik linuxových distribucí upravuje OpenSSH kvůli podpoře notifikací systemd a libsystemd závisí na liblzma, knihovně, kterou poskytuje xz.
- Licence. Každý tranzitivní balíček má vlastní licenci. Copyleftová licence o tři úrovně níž je stejně závazná jako ta, kterou jste přidali sami.
- Nekompatibilní změny. Nadřazený balíček, který přijímá rozsah verzí, pustí nové tranzitivní vydání dovnitř při příští čisté instalaci, i když se ve vašem manifestu nic nezměnilo.
Jak vypsat tranzitivní závislosti
Každý hlavní správce balíčků umí vypsat vyřešený strom:
| Ekosystém | Příkaz |
|---|---|
| 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 |
Chcete-li zjistit, proč tam konkrétní balíček je, zeptejte se na cestu k němu: npm explain <package>, pnpm why, yarn why nebo cargo tree -i <crate>. Pokud chcete s cestami pracovat, a ne je jen vypisovat, podívejte se na grafy závislostí.
Jak opravit zranitelnou tranzitivní závislost
- Upgradujte nadřazený balíček. Najděte přímou závislost, která balíček stahuje, a přejděte na vydání, které vyžaduje opravenou verzi. Je to nejčistší oprava, protože ji otestovali správci nadřazeného balíčku.
- Obnovte lockfile. Pokud rozsah verzí nadřazeného balíčku už opravenou verzi povoluje, stačí aktualizovat záznam v lockfilu, například pomocí
cargo update -p <crate>nebo novéhonpm update. - Přepište verzi. Pokud nadřazený balíček opravu nevydal, vynuťte verzi sami:
overridesv npm a pnpm,resolutionsv Yarn, soubor constraints u pip nebo vyšší požadavek vgo.mod. Otestujte to, protože nadřazený balíček s touto verzí nikdy vydán nebyl. - Nahraďte nebo odstraňte nadřazený balíček. Pokud nefunguje ani jedno, závislost, která balíček přináší, už možná nestojí za to si ponechat.
Některé nástroje pro analýzu softwarového složení přidávají analýzu dosažitelnosti, která ověřuje, zda váš kód může zranitelnou funkci vůbec zavolat, a podle ní určují, která z těchto oprav má přednost. Možnosti najdete ve srovnání nástrojů SCA.
Proč lockfile neřekne všechno
Většina nástrojů sestavuje svůj pohled na tranzitivní závislosti z lockfilu. Funguje to, když lockfile existuje, je aktuální a build instaluje jen z něj. V CI to často neplatí:
- Žádný lockfile. Soubor
requirements.txts rozsahy verzí nebo projekt, který lockfile nikdy necommitoval, se při každém buildu řeší znovu, takže se tranzitivní verze mohou mezi buildy měnit. - Cache. Cache CI obnovují balíčky z dřívějších běhů místo jejich stažení a jejich obsah ne vždy odpovídá aktuálnímu lockfilu. Podívejte se, co cache GitHub Actions skrývá.
- Nástroje pro build. Lintery, kompilátory, generátory kódu a kroky setup přinášejí do buildu vlastní stromy závislostí. Nejsou v manifestu vaší aplikace, ale běží s přístupem k vašemu zdrojovému kódu a tajným klíčům.
- Instalační skripty a stahování. Instalační skript balíčku může stáhnout další kód a krok buildu může stáhnout binárku přímo. Ani jedno nezanechá záznam v lockfilu.
Jediný úplný záznam toho, které tranzitivní závislosti šly do vydání, je ten pořízený během buildu daného vydání. Stránka SBOM z doby buildu vysvětluje rozdíl mezi zaznamenáním buildu a dodatečným skenováním jeho souborů.
Tranzitivní závislosti a CRA
Cyber Resilience Act EU požaduje, aby výrobci vypracovali SBOM, která zahrnuje „přinejmenším nejdůležitější závislosti produktu“ (příloha I, část II, bod 1). Tranzitivní závislosti v něm uvedeny nejsou, takže zákonné minimum je nižší, než mnoho příruček naznačuje. CRA ale také požaduje, abyste identifikovali a řešili zranitelnosti v komponentách, které váš produkt obsahuje, a zranitelný tranzitivní balíček je jednou z nich. SBOM, která končí na první úrovni, vynechává z tohoto procesu většinu vašeho stromu závislostí. Podrobnosti najdete na stránce požadavky CRA na SBOM.
Jak CRACI sleduje tranzitivní závislosti
CRACI je runner GitHub Actions, který build zaznamenává, místo aby ho skenoval. Během každého jobu zaznamenává proxy, která rozumí balíčkům, každý balíček, který job stáhne, v jakékoli hloubce stromu, včetně balíčků obnovených z cache CI. To dává úplnější pohled na používané závislosti než nástroje založené na skenerech.
- Každá hloubka, poctivě uvedená. SBOM každého jobu nese stav úplnosti, takže víte, kdy záznam nemohl vidět všechno.
- Strom na očích. Graf závislostí zobrazuje vrstvy závislostí SBOM v několika pohledech.
- Exporty, které je obsahují. Exporty CycloneDX a SPDX obsahují tranzitivní závislosti, každou s její deklarovanou licencí.
- Které buildy byly dotčené. Když se objeví nová zranitelnost, historie buildů ukáže, které dřívější buildy obsahovaly zranitelný balíček, ať přímo, nebo tranzitivně, takže víte, v jakém období jste ho dodávali (viz týká se nás tato CVE?). Historie odpovídá vaší době uchování, ve výchozím nastavení 180 dní.
- Policy gates. Build, který obsahuje konkrétní CVE, lze zablokovat dříve, než se dodá.
CRACI neprovádí analýzu dosažitelnosti. Pokud potřebujete vědět, zda váš kód volá zranitelnou funkci, zkombinujte ho s nástrojem SCA, který to umí. Podívejte se na generování SBOM v CRACI, nebo si rezervujte demo a zaznamenejte některý ze svých buildů.
Tranzitivní závislosti: časté dotazy
Co je tranzitivní závislost?
Tranzitivní závislost je balíček, který váš software používá, protože ho potřebuje jiný balíček. Pokud vaše aplikace závisí na knihovně A a A závisí na knihovně B, pak je B tranzitivní, neboli nepřímou, závislostí vaší aplikace. Nikdy jste ji nedeklarovali, ale dodává se s vaším softwarem.
Jaký je rozdíl mezi přímou a tranzitivní závislostí?
Přímou závislost deklarujete ve svém manifestu, například v package.json, pyproject.toml nebo go.mod. Tranzitivní závislost deklarují vaše přímé závislosti, v jakékoli hloubce. Přímé závislosti vybíráte vy; tranzitivní vybírá správce balíčků, když řeší strom.
Je „transient dependency“ totéž co tranzitivní závislost?
V praxi ano. „Transient dependency“ je v angličtině častá záměna za „transitive dependency“. Správný termín je tranzitivní, podle tranzitivní relace: pokud A závisí na B a B závisí na C, pak A závisí na C.
Jak opravím zranitelnost v tranzitivní závislosti?
Nejprve upgradujte přímou závislost, která ji stahuje, pokud existuje vydání s opravenou verzí. Pokud ne, vynuťte opravenou verzi mechanismem přepsání ve vašem správci balíčků, například npm overrides, Yarn resolutions nebo souborem constraints u pip, a otestujte, že s ní nadřazený balíček stále funguje.
Jak vypíšu všechny tranzitivní závislosti?
Každý správce balíčků umí vypsat vyřešený strom: npm ls --all, pnpm list --depth Infinity, uv tree nebo pipdeptree, go mod graph, cargo tree, mvn dependency:tree nebo gradle dependencies. Abyste věděli, co produkční build skutečně nainstaloval, zaznamenejte samotný build.
Vyžaduje Cyber Resilience Act EU tranzitivní závislosti v SBOM?
Nařízení stanoví minimum jako „přinejmenším nejdůležitější závislosti produktu“ (příloha I, část II, bod 1). Tranzitivní závislosti v něm uvedeny nejsou, ale zranitelný tranzitivní balíček je stále ve vašem produktu a CRA požaduje, abyste řešili zranitelnosti v komponentách, které dodáváte.
Podívejte se na každou závislost, kterou váš build stáhl
Spusťte jedno workflow GitHub Actions na CRACI a získejte zaznamenanou SBOM každého balíčku, který stáhlo, v jakékoli hloubce stromu.
Rezervujte si demo