En scanner melder en sårbarhed i en pakke, du aldrig har hørt om, i et projekt hvor du erklærede fyrre afhængigheder og installerede seks hundrede. Det brugbare spørgsmål er ikke, om fundet er ægte. Det er, hvordan pakken kom ind — for det afgør løsningen.
Afstanden mellem det, du erklærede, og det, du installerede, er den normale tilstand i moderne software, ikke et tegn på, at noget er gået galt. Du bad om et webframework; det havde brug for en router, en body-parser og en skabelonmotor; hver af dem havde brug for deres egne. Træet folder sig hurtigt og stille ud.
To begreber er værd at slå fast, for skellet driver alt det øvrige:
- En direkte afhængighed er en, dit projekt erklærer i sit manifest, med vilje.
- En transitiv afhængighed er en, der kom med, fordi noget andet havde brug for den. Ingen i dit team har valgt den, og ofte ved ingen i dit team, at den findes.
I de fleste projekter lander det store flertal af sårbarheder i den anden kategori — de pakker, ingen har valgt, er konsekvent flere end dem, nogen har. Det er hele grunden til, at "hvorfor har jeg den her?"-spørgsmålet betyder noget driftsmæssigt frem for akademisk.
Kæden er den handlingsanvisende del
Et fund, der siger qs har en sårbarhed, er tæt på ubrugeligt, hvis du ikke har installeret qs. Der er ingen linje i dit manifest at ændre.
Et fund, der siger express › body-parser › qs, kan der handles på med det samme, for det fortæller dig, at løsningen næsten helt sikkert er at opdatere express. Vedligeholderne af express opdaterede body-parser, som opdaterede qs, og ét versionsløft i toppen af kæden rydder hele stien.
Derfor viser en god scanner den korteste vej fra noget, du faktisk selv har erklæret, ned til den sårbare pakke. Det gør "et uforklarligt fund om en fremmeds kode" til "opdatér den ene ting, du allerede kender".
Når opdateringen ikke findes
Nogle gange har toppen af kæden ikke udgivet en version, der trækker den rettede afhængighed med. Så har du tre muligheder, i faldende orden efter, hvad der er at foretrække:
Vent, hvis risikoen tillader det. Undersøg, om sårbarheden reelt kan nås i jeres brug, og om den bliver udnyttet. En sårbarhed, ingen udnytter, i en kodesti I aldrig kalder, kan legitimt vente på udgivelsen opstrøms — forudsat at vurderingen bliver skrevet ned frem for glemt.
Tving versionen igennem. De fleste økosystemer har en mekanisme til at låse en transitiv afhængighed til en bestemt version — npm har overrides, Yarn har resolutions, Maven har dependency management. Det virker og bruges bredt, men det er en lokal beslutning om at køre en kombination, vedligeholderne opstrøms ikke har testet. Notér det, og fjern det igen, når den rigtige opdatering lander.
Udskift den direkte afhængighed. Sjældent, forstyrrende og af og til rigtigt — typisk når det viser sig, at forælderen ikke længere bliver vedligeholdt, hvilket er et større problem end den sårbarhed, der bragte det frem.
Hvorfor dine værktøjer er uenige om antallet
Melder to scannere forskellige tal for det samme projekt, er forklaringen som regel ikke, at den ene tager fejl. Det er, at de læser forskellige ting.
En lockfile — package-lock.json, poetry.lock, composer.lock, Cargo.lock — registrerer det præcise opløste træ, transitive inkluderet. At læse den giver det rigtige svar.
Et manifest — pom.xml, en bar requirements.txt, package.json alene — registrerer hensigt. Det lister, hvad du bad om, ikke hvad du fik. Et værktøj, der kun læser manifestet, ser dine fyrre pakker og ingen af de seks hundrede — og medmindre det siger det, læses det som et langt sundere projekt, end du har.
Det er det stærkeste praktiske argument for at committe sin lockfile. Det handler ikke kun om reproducerbare builds: uden en kan ingen — heller ikke du — opremse, hvad din applikation faktisk indeholder.
Afhængigheder kun til udvikling fortjener en nuance
En del af træet er build-værktøjer, der aldrig når produktion. En sårbarhed dér er reelt mindre eksponeret, og at behandle den med samme hastegrad som et hul i et betalingsbibliotek er præcis dét, der lærer en udvikler at ignorere scanneren.
Men mindre eksponeret er ikke ueksponeret, og undtagelsen betyder noget: en ondsindet pakke i dine build-værktøjer kører på udviklermaskiner og CI-servere, med repository-nøgler og cloud-tokens i miljøet. Sænk gerne alvorligheden for en sårbar udviklingsafhængighed. Sænk aldrig en ondsindet.
Hvad er en transitiv afhængighed?
En transitiv afhængighed er en pakke, der kom ind i dit projekt, fordi en anden pakke havde brug for den — ikke fordi du erklærede den. Installerer du et webframework, som afhænger af en router, og den router afhænger af en parser, så er routeren og parseren transitive. I de fleste projekter er de langt flere end de pakker, du selv har valgt, og det er dér, flertallet af sårbarheder bliver fundet.
Hvorfor melder min scanner en sårbarhed i en pakke, jeg aldrig har installeret?
Fordi pakken er en transitiv afhængighed: noget, du faktisk installerede, afhænger af den. Der er ingen linje i dit manifest at ændre, og derfor er et nøgent fund svært at handle på. Den brugbare oplysning er afhængighedskæden — den korteste vej fra noget, du erklærede, ned til den sårbare pakke — for at opdatere pakken i toppen af den kæde løser som regel hele stien i ét skridt.
Hvordan retter jeg en sårbarhed i en transitiv afhængighed?
Prøv først at opdatere den direkte afhængighed, der trak den ind, eftersom vedligeholdere som regel opdaterer deres egne afhængigheder, og ét versionsløft rydder kæden. Findes der endnu ingen sådan udgivelse, lader de fleste økosystemer dig tvinge en bestemt version af en transitiv pakke igennem — npm overrides, Yarn resolutions, Maven dependency management — hvilket virker, men betyder, at I kører en kombination, opstrøms ikke har testet. Notér det, og fjern det, når den rigtige opdatering lander.
Hvorfor melder forskellige scannere forskellige antal afhængigheder?
Forskellige scannere melder som regel forskellige antal, fordi de læser forskellige filer. En lockfile registrerer det præcise opløste træ inklusive hver transitiv pakke, så et værktøj, der læser den, giver det rigtige tal. Et manifest som pom.xml eller en almindelig requirements.txt registrerer kun det, du bad om, så et værktøj, der kun læser det, ser dine erklærede afhængigheder og ingen af dem, de trak ind. Den forskel er grunden til, at det at committe en lockfile betyder mere end reproducerbare builds.
Hvor CodeControl passer ind
CodeControl beregner den korteste kæde fra noget, dit projekt faktisk har erklæret, til hvert transitivt fund — express › body-parser › qs — og viser den på fundet, i tabellerne og i fortegnelsen sammen med en note om, hvilken opdatering der som regel rydder den. Hvor et format ikke har afhængighedskanter, viser den ingenting i stedet for et gæt. Afhængigheder, der kun bruges under udvikling, rapporteres ét alvorlighedstrin lavere med begrundelsen anført — men malware-vurderinger ser helt bort fra klassificeringen.