En udvikler skal have ændret størrelsen på et billede. Et par søgninger, en pakke, der kan det, en installation — og opgaven er løst på fire minutter. På intet tidspunkt i de fire minutter var sikkerhed det spørgsmål, der blev besvaret — og det er ikke manglende disciplin. Det er, hvordan arbejdet ser ud.
Næsten enhver diskussion om risiko i afhængigheder starter fra den forkerte antagelse: at der et sted sidder en skødesløs udvikler, som burde have tjekket efter. Ret skødesløsheden, så retter du problemet.
Den udlægning er behagelig, den er almindelig, og den har ikke givet en målbar forbedring nogen steder, den er blevet prøvet i stor skala. Det er værd at forstå hvorfor, for alternativet er reelt bedre.
Hvad der faktisk sker i det øjeblik, en pakke bliver hentet
Forløbet er stort set altid det samme. Der er en opgave — læse en dato, validere en adresse, lave en PDF, tale med et API. Udvikleren er midt i et større stykke arbejde og har hele formen på det i hovedet. Udvikleren skal have løst netop dette ene stykke for at kunne komme videre.
Udvikleren søger. Der dukker noget op: en pakke med et fornuftigt navn, en README med et eksempel, der ligner opgaven, nok downloads til at se etableret ud. Udvikleren installerer pakken, indsætter eksemplet, tilpasser det, og det virker. Samlet tidsforbrug: minutter. Opmærksomhed brugt på selve afhængigheden: næsten ingen — for afhængigheden var aldrig pointen. Opgaven var pointen.
Og her er den del, der bliver overset: det var den rigtige beslutning. At skrive sin egen datohåndtering er en glimrende måde at få fejl på. Refleksen til at gribe efter et vedligeholdt bibliotek er den rigtige refleks, og det er den, der overhovedet gør moderne softwareudvikling økonomisk mulig. Vi kigger ikke på en fejl, der skal rettes.
Hvorfor "gennemgå dine afhængigheder" ikke virker
Rådet er ikke så meget forkert som umuligt. Tre grunde, og hver af dem er nok i sig selv:
Oplysningerne er ikke synlige i beslutningsøjeblikket. For at vurdere en pakke ordentligt ville du gerne vide: bliver den stadig vedligeholdt, hvornår kom sidste rigtige udgivelse, hvor mange kan udgive til den, hvad trækker den med sig, kører den noget ved installation, hvilken licens, har den haft advarsler. Intet af det står på den side, udvikleren kigger på. At samle det tager femten-tyve minutter — til en beslutning, der tog fire, på en opgave, der er en ud af ni den dag.
Antallet slår dig. Selv en udvikler med ubegrænset disciplin når kun at gennemgå den pakke, der blev valgt bevidst — ikke de flere hundrede, den pakke trak med sig, for dem har ingen valgt, og de fleste af dem er usynlige, medmindre man bevidst går på jagt. At gennemgå fyrre direkte afhængigheder er en hård eftermiddag. At gennemgå seks hundrede transitive er ikke en opgave, det er et job.
Den bliver ikke ved med at være gennemgået. Det er den, folk glemmer. En pakke, der var velvedligeholdt, da du hentede den, kan være forladt to år senere. En vedligeholders konto kan blive overtaget. Der kan blive offentliggjort et hul i kode, ingen har rørt siden. Den gennemgang, du lavede i marts, var rigtig i marts og fortæller dig ingenting om i dag. Et engangstjek besvarer et spørgsmål, der udløber.
Den skævhed, der gør det værd at løse
At hente en afhængighed koster omkring tredive sekunders overvejelse. At fjerne den igen tager uger, hvis det overhovedet kan lade sig gøre — til den tid er den importeret i fyrre filer, og noget andet afhænger også af den. Beslutningen er billig og i princippet omgørlig, men dyr og i praksis permanent.
Den skævhed kan ikke rettes ved at bede folk tænke sig bedre om i de tredive sekunder. Den kan kun rettes ved at gøre konsekvenserne synlige bagefter — billigt, og uden at nogen skal huske det.
AI-assistenter gør det samme, bare hurtigere
Griber en menneskelig udvikler efter en pakke uden at vurdere den, gør en AI-assistent det samme med mindre kontekst og større overbevisning. Den foreslår et bibliotek, der passer til problemet, med et eksempel, der virker. Den ved ikke, om pakken bliver vedligeholdt. Den ved ikke, om den blev udgivet i sidste uge. Den foreslår af og til pakker, der slet ikke findes — og det har angribere lært at registrere.
Den, der gennemgår forslaget, er ofte ikke rustet til at se forskellen — og i stigende grad virker koden godt nok til, at ingen kigger grundigt efter. Mængden af afhængigheder, der kommer ind i projekter, er steget, og overvejelsen pr. afhængighed er faldet. Begge bevægelser peger samme vej.
Og kommer assistenterne, fordi nogen længere oppe har bedt om dem, er der et forudgående spørgsmål værd at få afklaret: hvad agenten må nå, og hvilke data der følger med ud.
Hvad man skal gøre i stedet
Princippet er enkelt: hold op med at bede om opmærksomhed dér, hvor den er mindst tilgængelig, og læg oplysningen dér, hvor arbejdet allerede foregår.
Gør tjekket automatisk og løbende. Ikke en portal, nogen skal huske at åbne. Kræver det en bevidst handling at vide besked, sker det to gange og så ikke mere. Tjekket skal køre, uanset om nogen tænker over det, og det skal køre igen næste måned — for svaret ændrer sig, uden at koden gør.
Læg det i pull requesten. Det er den enkelte ændring, der giver mest, for det er det ene øjeblik, hvor udvikleren allerede er stoppet op og allerede tænker på netop den kode. Et fund i en pull request er kontekst. Det samme fund i en ugentlig mail er en afbrydelse — og bliver behandlet derefter.
Sig, hvad der skal gøres, ikke hvad der er galt. "CVE-2024-XXXX, CVSS 9,1" beder udvikleren om at gå og undersøge noget. "Opdatér express til 4.19.2 — det rydder også de tre andre" er en instruktion. Producerer dit værktøj det første, skal nogen oversætte hver eneste gang, og til sidst er der ingen, der gør det.
Giv dem de to-tre, der betyder noget. En liste med enogtredive fund bliver lukket. Værdien ligger i prioriteringen: hvilke af dem bliver reelt udnyttet, og hvilke kan vente på den almindelige opdateringsrytme. En uprioriteret liste sender den svære del tilbage til den, der har mindst tid til den.
Forklar, hvorfor de overhovedet har den. Et fund om en pakke, udvikleren aldrig har hørt om, læses som støj, indtil der står express › body-parser › qs. Så læses det som én opdatering.
Pointen er ikke at få udviklere til at bekymre sig mere
De bekymrer sig allerede. Det, de ikke har, er de femten-tyve minutter pr. pakke, indblikket i seks hundrede transitive afhængigheder eller en grund til at tjekke noget efter, de allerede tjekkede sidste år.
Hver eneste af de ting er et værktøjsproblem forklædt som et disciplinproblem. Behandler du det som disciplin, får du en politik, ingen følger, og en kvartalsvis påmindelse, alle overser. Behandler du det som værktøj, bliver udvikleren ved med præcis det samme — løse opgaven på fire minutter — mens tjekket foregår et sted, der ikke koster noget.
Hvorfor tjekker udviklere ikke pakker for sårbarheder?
Udviklere tjekker ikke efter, fordi sikkerhed ikke er det spørgsmål, der bliver besvaret i det øjeblik, en pakke bliver hentet — det er at blive færdig med en opgave. Udvikleren skal have løst ét stykke for at komme videre med et større stykke arbejde, finder en pakke, der kan det, og den virker på minutter. At vurdere den pakke ordentligt ville kræve oplysninger, der ikke står på den side, udvikleren har foran sig, og tage fire-fem gange så lang tid som selve opgaven. Det er ikke skødesløshed; refleksen til at bruge et vedligeholdt bibliotek i stedet for at skrive det selv er den rigtige.
Er det nok at gennemgå afhængigheder, før man tilføjer dem?
Nej — det er ikke nok at gennemgå afhængigheder, før man tilføjer dem, og det er der tre grunde til. De oplysninger, du ville have brug for, er ikke tilgængelige i beslutningsøjeblikket uden femten-tyve minutters research pr. pakke. Du kan kun gennemgå de pakker, du selv valgte — ikke de flere hundrede, de trak med sig, og det er dér, de fleste sårbare pakker dukker op. Og en gennemgang udløber: en pakke, der var velvedligeholdt, da du hentede den, kan blive forladt, overtaget eller få offentliggjort et hul imod sig år senere, uden at nogen rører koden.
Hvordan får man udviklere til at tage sikkerhed i afhængigheder alvorligt?
Mest ved at lade være med at bede dem om at bekymre sig mere. Problemet er ikke motivation, men prisen på oplysningerne: femten-tyve minutter pr. pakke, intet indblik i transitive afhængigheder og ingen grund til at tjekke noget efter, der allerede er tjekket. Læg tjekket i pull requesten, hvor udvikleren allerede er stoppet op og allerede tænker på den kode, lad det sige, hvad der skal gøres, i stedet for hvad der er galt, prioritér det, så kun to-tre ting kræver opmærksomhed, og lad det køre løbende, så ingen skal huske det.
Gør en AI-kodeassistent det her værre?
En AI-kodeassistent ændrer både mængden af afhængigheder og overvejelsen pr. afhængighed — i samme retning. En assistent foreslår et bibliotek, der passer til problemet, med et eksempel, der virker, uden at vide, om det bliver vedligeholdt, om det blev udgivet i sidste uge, eller i nogle tilfælde om det overhovedet findes — angribere registrerer de plausible navne, assistenter opfinder. Der kommer flere afhængigheder ind i projekter, med mindre overvejelse pr. afhængighed, og den, der gennemgår forslaget, er ofte ikke rustet til at se forskellen.
Hvor CodeControl passer ind
Det er det problem, CodeControl er formet omkring. Det forbinder sig til dit GitHub én gang og tjekker derefter løbende, så ingen skal huske det. Hver pull request får et hemmelighedstjek på de linjer, den tilføjer, så en committet nøgle bliver et rødt check og ikke en nøgleudskiftning. Fund i afhængigheder lander inden for et minut efter et push og bliver gentjekket hver nat: hvert fund viser den kæde, der trak pakken ind, åbner med de to-tre punkter, der reelt bliver udnyttet, og kommer med de kommandoer, der retter dem — plus en knap, der kopierer det hele som en prompt til den AI-assistent, udvikleren allerede sidder med.
Udvikleren bliver ved med at løse opgaven på fire minutter. Tjekket foregår et sted, der ikke koster noget.