Sådan virker CodeControl i virkeligheden
Det her er den lange udgave, skrevet til den, der bliver spurgt, om værktøjet dur — en udvikler, en bureauchef, en CTO eller den teknisk interesserede ejer, der står for vurderingen. Den dækker, hvad der bliver tjekket, hvor hver konklusion kommer fra, hvor dybt dækningen går pr. økosystem, og hvor grænserne går. Intet her er ønsketænkning; det beskriver det, der kører.
Leder du efter udgaven i almindeligt sprog? CodeControl forretningsmæssigt
Hvad der sker mellem et push og et fund
En scanning opløser afhængighedssættet, beriger hver post, matcher den mod live-data og lander som én samlet observation, der forgrener sig til ét fund pr. berørt element. Fund er afledte, aldrig gemt to gange, og hver dimension lukker sig selv, når den er ryddet.
express › body-parser › qs — så hvert transitivt fund kan fortælle, hvordan pakken kom ind. Afhængigheder, der kun bruges under udvikling, identificeres ud fra det, manifesterne selv angiver, og rapporteres ét alvorlighedstrin lavere med begrundelsen i fundets tekst.Fire udløsere, fordi de svarer på hver sit spørgsmål
Næsten alt, hvad en scanning melder, ændrer sig kun, når koden ændrer sig — afhængigheder, licenser, hemmeligheder, base-images, pipeline-konfiguration. Udnyttelse er undtagelsen: en afhængighed kan komme i CISAs katalog over udnyttede sårbarheder i løbet af natten, uden at der er committet noget som helst. Den skævhed er grunden til, at planen er delt op i stedet for ensartet.
Fuld scanning · hver 14. dag
Alle dimensioner, fra bunden. Forskudt på tværs af mål, så de ikke alle går i gang på samme dato.
Nyt tjek af sikkerhedsadvarsler · hver nat
Sårbarheds- og udnyttelsesdata opdateres mod det kendte afhængighedssæt. Alt uden for sårbarhedsbilledet føres ordret videre fra forrige øjebliksbillede — ellers ville et delvist øjebliksbillede lukke hvert eneste fund om hemmeligheder og licenser som "rettet".
Push til hovedgrenen · inden for et minut
Leveret via webhook, så en nøgle committet klokken 14.00 er meldt 14.01. Et push, der lander oven i en scanning, der allerede er i kø, bliver slugt frem for at blive lagt i kø to gange.
Pull request · et check på ændringerne
Samme detektion af hemmeligheder, kun på de linjer, den pågældende pull request tilføjer, sendt tilbage som et GitHub-check med markeringer linje for linje — mens rettelsen stadig er en omskrivning af en gren og ikke en udskiftning af en nøgle.
Tretten dimensioner, og hvor hver konklusion kommer fra
Hver dimension har sit eget afsnit på fundets detaljeside og tre niveauer af vejledning: teknisk detalje, en trin-for-trin med konkrete eksempler, og en forklaring i almindeligt sprog. Hver eneste af dem kan lukke sig selv.
| Dimension | Hvad den finder | Kilde |
|---|---|---|
| Kendte sårbarheder | Pakker med offentliggjorte sikkerhedsadvarsler, i hele det transitive træ | OSV.dev |
| Prioritering efter udnyttelse | Hvilke af dem der er bekræftet udnyttet — eller statistisk set er de næste | CISA KEV + FIRST EPSS |
| Ondsindede pakker | Pakker udgivet for at stjæle adgangsoplysninger eller installere en bagdør | OpenSSF-advarsler via OSV |
| Hemmeligheder i koden | API-nøgler, betalingsnøgler og databaseadgangskoder i arbejdstræet | Tarball-scanning i hukommelsen, ~45 udstedersignaturer |
| Hemmeligheder i git-historikken | Oplysninger slettet i koden, men stadig mulige at hente frem | Gennemgang af de nyeste commit-ændringer, adskilt fra ovenstående |
| Forældet / udløbet | Pakker langt bagud eller ude over deres understøttede levetid | 8 pakke-registries + endoflife.date |
| Uvedligeholdt | Afhængigheder forfatteren har udfaset, forladt eller holdt op med at røre | npm deprecated / Packagist abandoned / manglende aktivitet |
| Licensrisiko | Licenser, der juridisk kan række ind i dit eget produkt, fx stærk copyleft | Licensvurderinger fra SBOM + registry som reserve |
| Typosquatting | Pakkenavne, der ligger ét tegns ændring fra en populær pakke | Offline Damerau-Levenshtein mod lister over populære pakker |
| Pakkers omdømme | Helt nye pakker — slopsquatting-angrebet, der rammer AI-foreslåede navne | Oprettelsesdato i registry (npm / PyPI / Packagist) |
| Dependency confusion | Interne pakkenavne, som hvem som helst kan overtage på et offentligt registry | Tjek af eksistens på det offentlige registry |
| Container-base-images | Styresystem-images ude over deres levetid | Dockerfile / compose + endoflife.date |
| CI/CD-pipeline | Svagheder i den automatik, der ligger inde med dine deployment-nøgler | Gennemgang af .github/workflows |
To oplysninger følger med hver afhængighed og ændrer, hvordan alt ovenstående skal læses: kæden der trak den ind, og om den kun bruges under udvikling.
De dimensioner, hvor designet er produktet
Flere af dem er nemme at lave dårligt og svære at lave brugbart. Det her er, hvad forskellen består i.
Prioritering gør en liste til en arbejdskø
Alvorlighed siger, hvor slemt et hul ville være. KEV siger, at udnyttelse er observeret. EPSS giver en 30-dages sandsynlighed for den lange hale, KEV ikke dækker. Tre niveauer — udnyttet i virkeligheden, sandsynligt mål (EPSS ≥ 0,5), forhøjet risiko (≥ 0,10) — og sårbarhedstabellen sorterer først efter udnyttelse, derefter efter alvorlighed. Selve fundet er strengt, så det aldrig råber ulv.
Hemmeligheder: to adskilte gennemgange, plus omgåelse
Arbejdstræet streames som en tarball og parses udelukkende i hukommelsen. Historikken gennemgår de nyeste commit-ændringer og melder kun oplysninger, arbejdstræet ikke længere indeholder — så de to fund aldrig overlapper, og historik-fundet er præcis den type læk, hvor man "slettede linjen". En tredje gennemgang fanger nøgler, der er sat sammen af tilstødende tekststumper eller pakket ind i base64; begge kører kun signaturregler, aldrig entropi, fordi rekonstrueret tekst ser tilfældig ud af helt uskyldige grunde.
Selve værdien bliver aldrig gemt
Et fund om en hemmelighed bærer en fil, en linje, den detektor der matchede, og et maskeret uddrag — sk_l…(28 tegn). Den rå nøgle bliver ikke gemt, ikke logget og ikke lagt i noget, der sendes tilbage til GitHub. Samme disciplin gælder markeringerne i pull request-checket.
Pakkers omdømme, rettet mod slopsquatting
AI-assistenter importerer med fuld overbevisning pakker, der ikke findes; angribere registrerer netop de plausible navne, og assistenterne bliver ved med at anbefale dem. Sådan en pakke ligger ikke én redigering fra noget populært, så typosquatting er blind over for den, og på dag ét nævner ingen advarsel den, så malware-feedet er det også. Det ene, den ikke kan forfalske, er en historie — derfor er alder den eneste udløser: en direkte afhængighed, der er udgivet første gang inden for 90 dage. Installationsscripts, et manglende kildekode-repository og en enlig vedligeholder er skærpende omstændigheder på en ung pakke, aldrig fund mod en etableret. Fundet lukker sig selv, når pakken bliver gammel nok.
Dependency confusion kræver ingen opsætning
En direkte afhængighed, hvis navn giver 404 på sit offentlige registry, er eksponeringen, uanset hvem der ejer den — en angriber kan udgive det navn, og dit næste build kan tage deres. Et privat registry konfigureret i repositoryet er angrebets forudsætning og hæver konfidensen; npm-workspaces bliver genkendt, så den uskyldige monorepo-forklaring bliver tilbudt frem for begravet.
CI/CD graderet efter foranderlighed
Et workflow kører med repositoryets hemmeligheder og en skrive-token, så en svaghed dér rækker helt ind i deployment-vejen (se GitHub Actions-sikkerhed). Ikke-fastlåste actions graderes efter, hvor foranderlig referencen er — tredjeparts-gren er høj, tredjeparts-tag er middel, et førstepartstag lavt. At gradere førstepartstags højere ville få hvert eneste repository på GitHub til at lyse rødt for at følge GitHubs egen dokumentation. pull_request_target kombineret med et checkout af pull request'ens kode behandles som kritisk.
Økosystemdækning, sagt på det niveau hvor det faktisk passer
Dækningen er ikke ens overalt, og en scanner, der antyder noget andet, vildleder dig. Native parset betyder autoritativt: præcise versioner og en reel opdeling i direkte og transitive, hvor formatet har en.
package-lock.json, yarn.lock (klassisk og Berry), requirements.txt, poetry.lock, Pipfile.lock, composer.lock, go.mod, pom.xml, Gemfile.lock, Cargo.lock. Kundens manifester behandles som input, der ikke kan stoles på: XML-parsing afviser DOCTYPE fuldstændigt, hvilket lukker XXE ved roden, og der findes en test, der forsøger at læse /etc/passwd igennem den.gradle.lockfile først (en rigtig lockfile — præcise versioner, transitive inkluderet), derefter gradle/libs.versions.toml, derefter de bogstavelige koordinater i build.gradle(.kts), idet settings.gradle-includes følges. En version skrevet som en variabel, et + eller et interval står blank — buildet skulle køre for at vide det — og Gradle bliver ved med at være markeret som et build, vi måske ikke har læst fuldt ud, fordi det at lave "vi kunne ikke læse det her" om til en ren attest er præcis den fejl, der er værd at undgå.pom.xml er et manifest, ikke en lockfile: i Java-verdenen angiver den som regel ingen af sine afhængigheders versioner — de kommer fra en parent. Vi følger den samme vej, som Maven gør: POM'ens egen dependency management, op ad parent-kæden og derefter ind i importerede BOM'er. Multi-modul-builds følges bredde-først med properties nedarvet ned ad grenen, fordi en aggregators rod-POM ofte slet ingen afhængigheder erklærer, og at læse den alene ville melde repositoryet som tomt. ${project.version} hører til den POM, der skriver den — at dele ét property-kort fik en BOM til at arve kundens projektversion og opløse en pakke til en udgivelse, der ikke findes. En opdigtet version er værre end en blank: blank bliver meldt som ikke tjekket, mens en forkert version bliver slået op mod advarsler som en kendsgerning.requirements.txt fastlåser kun det, forfatteren selv har skrevet; pip opløser resten ved installation og skriver ingen lockfile, så det meste af et Python-projekts reelle angrebsflade ville ganske enkelt mangle — hverken meldt som risikabelt eller meldt som ikke tjekket. Vi går de erklærede krav igennem bredde-først og vælger den nyeste stabile udgivelse, der opfylder hver PEP 440-specifikation. Hver sådan række er markeret beregnet — et stærkt estimat af, hvad en frisk installation giver, aldrig den fastlåste kendsgerning, en lockfile angiver — og en betingelse, der ikke kan opfyldes, opløses til ingenting; det bliver talt op og meldt frem for gættet. Et repository med en rigtig Python-lockfile bliver ikke rørt.SBOM og VEX, genereret ud fra den scanning, du allerede har kørt
Leveret som en fil-download, fordi standardværktøjer forventer selve dokumentet og ikke en indpakning.
SPDX 2.3
Det format, indkøb, revisorer og lovgivning beder om ved navn. En licens, vi ikke genkender, udstedes som NOASSERTION frem for et gæt — værktøjer nedstrøms behandler en SBOM som en kendsgerning, så et gæt breder sig som en.
CycloneDX 1.5
Det, sikkerhedsværktøjer læser, og det har en indbygget plads til sårbarheder — så eksporten bærer det, vi ved om fortegnelsen, ikke bare fortegnelsen. Begge formater genereres ud fra samme scanning, så de stemmer overens af konstruktion.
VEX: hvad en SBOM ikke svarer på
En SBOM siger, hvad softwaren indeholder; en VEX siger, hvad leverandøren har besluttet om hver kendt sårbarhed. Kvittér-med-begrundelse-arbejdsgangen har indsamlet præcis det, så status oversættes til en CycloneDX-analysetilstand med menneskets egen note vedhæftet. En advarsel, ingen endnu har vurderet, står som under vurdering, aldrig udeladt — en VEX, der kun nævner de flatterende poster, er ingenting værd.
NIS2 kræver allerede forsyningskædesikkerhed af de virksomheder, der er omfattet, og deres leverandører mærker det som et spørgeskema. Cyber Resilience Act faser krav om softwarestykliste ind hen over 2026-27. Det her er den billigste måde at have svaret klar.
Hvad vi får lov til, og hvad vi gør med det
Læseadgang til indhold, pr. repository
En GitHub App pr. kunde, oprettet og installeret med ét klik, med læseadgang til repository-indhold — det mindste GitHub tilbyder — på de repositories, du vælger enkeltvis. Den kan ikke skrive kode, åbne pull requests eller ændre indstillinger. Pull request-checket tilføjer præcis to rettigheder: checks:write til at sende resultatet og pull_requests:read til at hente ændringerne. Begge er låst fast af en test, så en bredere tilladelse ikke kan snige sig ubemærket ind i manifestet.
Kildekode analyseres i hukommelsen
Repositoryet streames som en tarball og parses i hukommelsen. Kundens kildekode bliver aldrig skrevet til disk og aldrig gemt. Det, der bliver liggende, er den afledte fortegnelse og fundene.
Webhooken verificerer, før den læser
Den er platformens eneste uautentificerede skrivevej, og hver beslutning i den følger af det. HMAC'en verificeres over den rå request-body, før ét eneste felt bliver parset — at genserialisere et parset objekt ændrer byte-rækkefølgen og får hver verifikation til at fejle — med konstant-tids-sammenligning. Ingen konfigureret hemmelighed betyder en hård fejl, aldrig "accepteret": en instans, der ikke kan verificere, må ikke handle på ubekræftede påstande, heller ikke påstanden om at en kunde har afinstalleret.
Identiteten kommer fra vores egne data
Kunden slås op ud fra installations-id'et i vores egen database, aldrig ud fra repository-navnet i beskeden. En gyldig signatur beviser, at GitHub har sendt beskeden; den beviser ikke, at afsenderen må handle på en given kundes data. Nul eller flere match droppes frem for at blive gættet.
En fejl bliver aldrig et grønt flueben
Kan vi ikke hente en pull requests ændringer, sender vi slet intet check — en godkendelse, scanneren ikke har fortjent, er værre end tavshed. Kan et check ikke sendes, siger vi præcis det, så den ansvarlige kan give rettigheden frem for at undre sig.
Opdagelse af tavse fejl
Én webhook-hemmelighed betjener hver installation, så en uoverensstemmelse fejler for alle kunder på én gang, uden andet end afvisninger i en log, ingen læser. Afvisninger i træk er signalet — tælleren nulstilles ved enhver accepteret levering, så almindelig støj aldrig hober sig op til falsk alarm — og en stribe af dem bliver eskaleret.
Præcisionen bliver målt på begge sider af en udrulning
Det her produkt bliver brugt af folk, der tror på alt, hvad det siger — eller holder op med at tro på noget som helst af det ved den første fejl, de tilfældigvis opdager. Derfor bliver præcisionen — og lige så vigtigt: om vi overser noget — målt, ikke påstået.
Før udgivelse: en korpusport i hvert build
Hver dimension i motoren kører offline mod et plantet repository, hvis forventede fund er skrevet ned i hånden i et facit. Kravet er nøjagtig ens mængder i begge retninger: en manglende post betyder, at vi har overset et rigtigt fund — et værn, der er blevet så bredt, at det sluger et rigtigt fund, hvilket én gang kostede otte plantede hemmeligheder i én enkelt ændring — og en ekstra post er et tab af præcision. Ingen af delene kommer ud. Facit er specifikationen, ikke et spejl af koden; en regel, der legitimt ændrer adfærd, opdaterer facit i samme commit, med begrundelsen.
Fælderne er den interessante del
Plantet ved siden af de rigtige fund ligger hver eneste falske positiv, vi har lært af i produktion: referencer til miljøvariabler over for standardværdier med en rigtig værdi i, dokumenterede eksempelnøgler, danske pladsholder-adgangskoder, SHA-fastlåste actions, Dockerfile-trinaliasser, det YAML-on:-nøgleord der parses som en boolean. At designe korpusset fandt i sig selv en rigtig fejl — en udløbsregel, der aldrig udløste for Django stavet med det sædvanlige store D.
Efter udgivelse: falske positive bliver talt
"Ikke et rigtigt fund" er sin egen handling, bevidst adskilt fra at kvittere. At kvittere er en risikobeslutning om dit system; en indberetning af en falsk positiv er dokumentation om vores detektionskvalitet. Det bliver talt op pr. regel og vist som et kvalitetskort, så en støjende detektor er synlig for os, før den brænder din tillid af.
Ende-til-ende-fixtures med offentliggjort facit
To private repositories bakker den offline port op: ét med 108 plantede elementer på tværs af ti dimensioner, bygget bevidst svært — syntetiske værdier med korrekte udstederformater og uden pladsholder-ordforråd, så vores egne filtre ikke kan dække over et overset fund — og ét Maven-aggregator-projekt, hvis rod-POM erklærer ingen af sine fem afhængigheder. Begge fandt rigtige fejl, som ingen unittest havde fanget.
Fire regler, koden rent faktisk følger
Det er ikke idealer. Hver enkelt findes på grund af en bestemt måde at tage fejl på, som vi helst ikke vil gentage.
Hvad CodeControl ikke gør
Sagt her frem for opdaget senere, fordi en køber, der selv finder en begrænsning, holder op med at stole på alt andet på siden.
Det læser, hvad repositoryet erklærer
Ikke hvad buildet producerer. For de fleste projekter er det det samme. Hvor et build beregner sine afhængigheder undervejs, er nogle uden for rækkevidde — og rapporten siger det frem for i stilhed at vise færre.
Ingen analyse af jeres egen kode
Der er ingen statisk analyse af håndskrevet applikationskode, og det er bevidst. For den her målgruppe er koden, I selv har skrevet, en lille del af det, I reelt kører, og regelbaseret kodeanalyse er den disciplin inden for sikkerhed, der giver flest falske positive. Præcisionen i alt det andet er mere værd end en støjende ekstra dimension.
Ikke alle økosystemer er lige dybe
Java, JavaScript, PHP, Python, Go, Ruby, Rust og .NET er godt dækket. Noget mere eksotisk afhænger af, hvad GitHub selv kan opløse — og scanningen melder, hvilke dele den ikke kunne nå.
Ikke en penetrationstest, ikke en garanti
Det melder, hvad der kan vides ud fra repositoryet. Det kan ikke se, hvordan applikationen opfører sig i produktion, og det forsøger ikke at udnytte noget. Til blikket udefra på et kørende site er det VulnControl
Verifikation af hemmeligheder er bevidst ikke bygget endnu
At tjekke, om en fundet nøgle stadig er aktiv hos sin udbyder, betyder at sende kundens rå nøgle ud af huset. Det skal designes sammen med gennemgangen af databehandlingen — tilvalg pr. kunde, kun læse-endpoints, aldrig gemt — ikke sættes på, fordi det demonstrerer godt.
Prisen er ikke offentliggjort endnu
Produktet er bygget og kører; den kommercielle beslutning er den udestående del, og i dag bliver det slået til pr. kunde i hånden. Tal med os, så siger vi det, som det er
Hold det op mod det, I bruger i dag
Kører I allerede en scanner til forsyningskæden, er den brugbare sammenligning prioriteringen, afhængighedskæderne og rapporteringen af dækning. Forbind ét repository og stil dem side om side.