SSD Nodes Learn 🎉 VPS vanaf $5.50/mnd
Gidsen Matt ConnorDoor Matt Connor

Hoe voorkomt u npm supply-chain aanvallen op uw server?

Ontdek hoe kwaadaardige npm-pakketten uw Node-applicatie infecteren via postinstall scripts of typosquats. Leer welke concrete deploy-methode uw server effectief beschermt.

Wat een npm supply-chain aanval op uw server inhoudt

Een npm supply-chain aanval bereikt uw server via een pakket dat u heeft geïnstalleerd. Er komt geen open poort of exploit-stap aan te pas. npm (node package manager) installeert code, en het installeren van code voert code uit. Een kleine Node-applicatie haalt dus honderden pakketten binnen die u nooit heeft gelezen, en elk van deze pakketten kan over een uur een nieuwe versie publiceren.

Uw deploy haalt een kwaadaardige versie op omdat uw installatiecommando vroeg om de nieuwste overeenkomende versie. Die code wordt vervolgens uitgevoerd met de rechten van de gebruiker die de installatie uitvoerde. Alles hieronder vloeit voort uit deze twee zinnen.

De vormen zijn gerangschikt op basis van hoe vaak ze een persoon treffen die één Node-app naar één VPS deployt. Deze volgorde is niet die van een groot bedrijf, omdat een groot bedrijf een interne registry, een reviewteam en een mirror van de publieke registry gebruikt. U heeft een deploy-script.

Scenario 1: een onderhoudersaccount is gecompromitteerd en publiceert een patch

Het npm-register staat niet toe dat de inhoud van een bestaande versie wordt gewijzigd. Een aanvaller die de inloggegevens van een onderhouder steelt via phishing of een publish-token bemachtigt, kan daarom 4.18.2 niet herschrijven. Zij publiceren 4.18.3.

Bekijk uw package.json. Een regel zoals "express": "^4.18.2" betekent niet versie 4.18.2. Het caret-teken betekent "elke 4.x-versie vanaf deze versie", en ~4.18.2 betekent "elke 4.18.x-versie". npm install lost dat bereik op het moment van uitvoering op, waardoor dezelfde git-commit, indien tweemaal op dezelfde middag uitgerold, twee verschillende sets code kan installeren. Dat gat vormt het aanvalsoppervlak. Er hoeft niets op uw machine gecompromitteerd te zijn om dit te openen.

Kwaadaardige releases worden meestal gerapporteerd en verwijderd, maar die verwijdering vindt pas plaats nadat mensen ze hebben geïnstalleerd. Iedereen die tijdens dat tijdsvenster een uitrol heeft gedaan, heeft de code op schijf staan. Een pipeline die bij elke uitvoering bereiken oplost, komt automatisch in dat venster terecht, meerdere keren per week, zonder dat iemand daar expliciet voor kiest.

Vorm 2: een installatiescript wordt uitgevoerd als de gebruiker die de deploy uitvoert

Het package.json van een pakket kan preinstall, install, postinstall en prepare declareren in het scripts-blok. npm voert deze uit tijdens de installatie. Ze zijn niet in een sandbox geplaatst en niemand controleert ze. Het zijn shell-commando's die worden uitgevoerd als de gebruiker die het installatiecommando heeft getypt, in de home-directory van die gebruiker, met de netwerktoegang van die gebruiker en de volledige omgeving van die shell.

De relevante vraag is dus niet wat het pakket kan doen. De vraag is wat die gebruiker kan lezen. Op een standaard deploy-server omvat het antwoord ~/.npmrc met een registry-token, ~/.ssh/id_ed25519 gebruikt als deploy-key voor SSH (secure shell), ~/.aws/credentials, ~/.docker/config.json en elke geëxporteerde variabele in de shell, waar DATABASE_URL meestal te vinden is.

Een payload zoals deze heeft geen persistentie en geen privilege-escalatie nodig. Het leest een paar bestanden, verstuurt deze via HTTPS naar een host en sluit af met status 0. U ziet niets, omdat npm de output van installatiescripts standaard verbergt. Schakel dit uit en bekijk wat er daadwerkelijk wordt uitgevoerd:

npm ci --foreground-scripts

foreground-scripts deelt de standaard input, output en error met het npm-proces, waardoor build-scripts naar uw terminal schrijven in plaats van naar een buffer die npm verwijdert zodra de installatie slaagt.

Vorm 3: typosquats en de naam die u net niet goed typte

Een typosquat is een pakket dat onder een naam wordt gepubliceerd die sterk lijkt op een populaire naam, in afwachting van een verkeerd getypt of verkeerd geplakt installatiecommando. Het mechanisme is het commando, niet de code, dus een lockfile helpt u hier niet: u voegt de verkeerde naam één keer toe en vanaf dat moment pint de lockfile deze getrouw vast.

De variant die teams treft in plaats van individuen is dependency confusion. Uw interne pakket heet billing-utils en staat op een privé-registry. Als er op de publieke registry niets met de naam billing-utils bestaat, kan iedereen er een publiceren. npm lost namen zonder scope op tegen de standaard publieke registry, waardoor de publieke kopie kan winnen. De oplossing is een scope die u bezit plus een registry-mapping voor die scope, in .npmrc:

@yourorg:registry=https://npm.yourorg.example/
//npm.yourorg.example/:_authToken=${NPM_TOKEN}

Nu wordt @yourorg/billing-utils alleen nog maar van die host opgehaald, omdat de mapping van scope naar registry wordt geraadpleegd vóór de standaard registry. Een interne naam zonder scope heeft geen mapping en dus geen bescherming.

Voordat u een nieuwe dependency toevoegt, bekijkt u deze in plaats van alleen naar de download-badge te kijken:

npm view some-lib repository.url maintainers time.created time.modified

Een pakket dat vorige maand is gemaakt en is gepubliceerd door een account dat u niet kunt koppelen aan een publieke repository, vormt een ander risico dan een pakket met zes jaar historie. Geen van beide feiten is een bewijs. Beide zijn eenvoudig te controleren.

Vorm 4: de afhankelijkheid waarvan de eigenaar stilletjes is gewijzigd

Maintainers dragen pakketten over. Iemand raakt opgebrand, een vreemde biedt hulp aan, publicatierechten worden verplaatst en er bereikt geen enkele melding de projecten die ervan afhankelijk zijn. Er is geen sprake van een inbreuk. Het vertrouwen dat u in 2021 gaf, ligt nu bij een ander persoon.

Dit is de traagste vorm en de moeilijkste om te detecteren; er is geen commando dat hier direct antwoord op geeft. Twee zaken helpen dit in te perken. Controleer wie kan publiceren voordat u een pakket adopteert, met de npm view regel hierboven. Lees daarna de diff wanneer een pakket waar u daadwerkelijk van afhankelijk bent, wijzigt:

npm diff --diff-name-only --diff=some-lib@1.4.2 --diff=some-lib@1.4.3
npm diff --diff=some-lib@1.4.2 --diff=some-lib@1.4.3

De eerste vorm toont alleen de gewijzigde bestandsnamen, wat snel genoeg is om uit te voeren bij elke upgrade van een pakket dat voor u van belang is. Een patch-release die een build-script aanpast, een bestand toevoegt aan de root van het pakket of het scripts blok bewerkt, is het waard om volledig te lezen voordat deze uw server bereikt.

Bouwen vanuit een vastgelegd lockfile met npm ci

package-lock.json legt de exacte versie van elk pakket in de boomstructuur vast, de URL waar elk vandaan kwam, een sha512-integriteitshash van elk tarball-bestand en welk pakket erom vroeg. Commit dit bestand. Het is het enige bestand dat aangeeft wat u daadwerkelijk heeft getest.

Installeer vervolgens met npm ci, nooit met npm install, op elke machine die geen ontwikkelaarslaptop is:

npm ci --omit=dev --ignore-scripts

npm ci verschilt op manieren van npm install die hier allemaal van belang zijn. Het vereist dat er een lockfile bestaat. Het verwijdert elk bestaand node_modules voordat het begint, zodat restanten van een eerdere implementatie niet kunnen overleven in de huidige. Het schrijft nooit naar package.json of naar het lockfile, waardoor een installatie u niet stilletjes naar een nieuwere versie kan tillen. Als het lockfile en package.json het niet met elkaar eens zijn, stopt het proces met een foutmelding in plaats van het verschil op te lossen.

Die foutmelding is de functionaliteit, geen ergernis. Het betekent dat een wijziging in een afhankelijkheid moet binnenkomen als een commit die iemand heeft beoordeeld, en niet als een bijwerking van een implementatie om 02:00 uur.

De integriteitshash wordt bij elke download gecontroleerd. Een tarball waarvan de bytes niet overeenkomen met de vastgelegde hash, laat de installatie mislukken met code EINTEGRITY in plaats van deze uit te pakken. Wees nauwkeurig over wat dit u oplevert: het bewijst dat het bestand dat u heeft ontvangen, het bestand is dat in het lockfile is vastgelegd. Dit is dezelfde garantie die downloads verifiëren met checksums u biedt, en het is op dezelfde manier beperkt. Het zegt niets over de vraag of de vastgelegde versie kwaadaardig was op het moment van publicatie.

Eén detail over --omit=dev: die pakketten worden nog steeds opgelost en nog steeds in het lockfile geschreven. Ze worden alleen niet op de schijf geplaatst. Minder pakketten op de schijf betekent minder installatiescripts en minder code die tijdens runtime wordt geladen, dus het is de moeite waard. Het verwijdert geen afhankelijkheid uit uw boomstructuur.

Behandel installatiescripts als code en weet hoe u ze kunt weigeren

U kunt installatiescripts uitschakelen. Plaats dit in de .npmrc van het project en commit het bestand naast het lockfile:

ignore-scripts=true
save-exact=true

ignore-scripts=true voorkomt dat npm de scripts uitvoert die in dependencies zijn gedeclareerd. save-exact=true zorgt ervoor dat npm install some-lib de 1.4.2 in package.json schrijft in plaats van in ^1.4.2, zodat een oplossingsbereik nooit per ongeluk in uw manifest terechtkomt.

Dit kan functionaliteit verbreken en u dient te weten hoe dit werkt voordat u het inschakelt. Pakketten die een native addon compileren of een prebuilt binary downloaden, voeren dat werk uit in een installatiescript. Als scripts zijn uitgeschakeld, slaagt de installatie zelf, maar treedt de fout later op tijdens runtime: als een module die zijn binding-bestand niet kan laden. De oplossing is een allowlist:

npm ci --ignore-scripts
npm rebuild better-sqlite3

npm rebuild <package> voert de build-scripts uit voor dat specifieke pakket. U heeft nu per pakket een beslissing genomen, in plaats van algemene uitvoeringsrechten te verlenen aan honderden vreemden die u nooit zult ontmoeten.

Om te zien hoe groot die toekenning momenteel is, vraagt u het aan npm:

npm query ":attr(scripts, [postinstall])"

Dit drukt elk pakket in de geïnstalleerde boomstructuur af dat een postinstall-script bevat. Bij een typische applicatie is de lijst korter dan men verwacht, wat precies is wat de allowlist praktisch maakt.

Scheid het build-proces van het proces dat verkeer afhandelt

De deploy-gebruiker moet kunnen schrijven naar node_modules. Het proces dat HTTP-verzoeken beantwoordt, hoeft dat niet. Als dit hetzelfde account is, kan code die tijdens de installatie wordt uitgevoerd de code overschrijven die uw gebruikers bedient, en kan code die tijdens runtime wordt uitgevoerd dit eveneens doen.

Splits deze taken. Bouw als de ene gebruiker, serveer als de andere, en maak de map met de bronbestanden alleen-lezen voor het account dat de service draait:

sudo useradd --system --home-dir /srv/nodeapp --shell /usr/sbin/nologin nodeapp
sudo chown -R deploy:nodeapp /srv/nodeapp
sudo chmod -R o-rwx /srv/nodeapp

Laat systemd dit vervolgens afdwingen. Schrijf /etc/systemd/system/nodeapp.service:

[Unit]
Description=Node application
After=network-online.target

[Service]
User=nodeapp
Group=nodeapp
WorkingDirectory=/srv/nodeapp/current
EnvironmentFile=/etc/nodeapp/env
ExecStart=/usr/bin/node server.js
Restart=on-failure

NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadWritePaths=/srv/nodeapp/shared
NoExecPaths=/srv/nodeapp/shared
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

[Install]
WantedBy=multi-user.target

ProtectSystem=strict maakt het volledige bestandssysteem alleen-lezen voor deze service, met uitzondering van /dev, /proc, /sys en alles wat u in ReadWritePaths opneemt. Een poging van de applicatie om naar node_modules te schrijven mislukt daarom met EROFS: read-only file system, wat u binnen een minuut in uw eigen logs kunt reproduceren. NoExecPaths dekt de beschrijfbare uploadmap af: de service kan daar bestanden schrijven, maar de kernel weigert deze uit te voeren. Die optie vereist systemd 249 of nieuwer; Ubuntu 24.04 wordt geleverd met 255.

Er zijn twee valkuilen in dit unit-bestand. Voeg ten eerste geen MemoryDenyWriteExecute=yes toe. Dit staat op de meeste lijsten voor systemd-hardening, maar het voorkomt dat Node start, omdat V8 JavaScript tijdens runtime compileert naar machinecode en pagina's nodig heeft die zowel beschrijfbaar als uitvoerbaar zijn. Gebruik ten tweede het ExecStart-pad uit command -v node. Als Node is geïnstalleerd met een versiebeheerder, bevindt het zich in de home-directory van de deploy-gebruiker; ProtectHome=yes verbergt die directory vervolgens voor de service, waardoor de unit onmiddellijk faalt met status=203/EXEC en een logregel die aangeeft dat het uitvoerbare bestand niet kon worden gevonden.

Controleer het resultaat in plaats van op het bestand te vertrouwen:

sudo systemctl daemon-reload
sudo systemctl enable --now nodeapp
sudo systemd-analyze security nodeapp.service
sudo -u nodeapp touch /srv/nodeapp/current/probe

systemd-analyze security somt elke hardening-instelling op met de bijbehorende blootstelling, zodat u kunt zien welke nog op de standaardwaarde staan. Het touch zou moeten falen met Permission denied, omdat nodeapp niets bezit onder current. Als het slaagt, is uw eigendom van de bestanden onjuist en maskeren de systemd-instellingen dit stilletjes.

Eén opmerking over EnvironmentFile: systemd leest dit als root voordat het overschakelt naar User=nodeapp, dus dat bestand kan root:root zijn met modus 600. De applicatie ontvangt de variabelen nog steeds. Iedereen met een shell als nodeapp kan ze nog steeds lezen via /proc/<pid>/environ, dus dit beschermt het geheim in rusttoestand en niet het draaiende proces.

Houd deploy-credentials buiten de build-omgeving

Installatiescripts erven de omgeving over. Dat ene feit moet bepalend zijn voor waar u bouwt.

De meest robuuste methode is om te bouwen op een locatie die niet de productieserver is en de voltooide directory vervolgens te kopiëren. De build-machine bevat dan enkel een read-only registry-token en niets anders. Geen SSH-deploy-key, geen cloud-access-key, geen database-wachtwoord, geen login voor een container-registry.

npm token create --read-only

Een read-only token kan pakketten ophalen, maar niet publiceren. Als dit token uit een build-omgeving wordt gestolen, is de schade beperkt tot het kunnen downloaden van publieke pakketten.

Als u toch op de server moet bouwen, doe dit dan als de deploy-gebruiker met een bewust beperkte omgeving, en bewaar de runtime-geheimen in /etc/nodeapp/env, wat deploy niet kan lezen. Dezelfde redenering is van toepassing op build-automatisering die u zelf host: een zelf-gehoste GitHub Actions runner bevat tokens en voert bij elke job willekeurige gepubliceerde code uit, wat het de machine met de hoogste waarde maakt in een kleine implementatie. Elk programma dat u niet zelf heeft geschreven en dat uw volledige omgeving ontvangt, valt in dezelfde categorie. Daarom is het buiten de omgeving van een AI-agent houden van geheimen hetzelfde probleem, maar dan met een ander programma als tussenpersoon.

Pin of vendor wat u niet kunt auditen

Een pinned dependency is een afhankelijkheid waarvan de versie niet kan wijzigen zonder een commit. Het gecommitteerde lockfile doet dit al voor de gehele boomstructuur. Twee gevallen vereisen extra aandacht.

Transitive dependencies zijn het eerste geval. U heeft geen controle over waar uw afhankelijkheden van afhankelijk zijn. overrides in package.json dwingt een versie af op elke plek in de boom:

{
  "overrides": {
    "some-transitive-lib": "1.4.2"
  }
}

Voer npm install eenmaal uit na het toevoegen, zodat het lockfile het resultaat vastlegt, en commit vervolgens beide bestanden.

Het tweede geval is een pakket dat u niet kunt auditen en niet kunt verwijderen. Vendor dit pakket. npm pack downloadt exact de tarball die het register zou aanbieden, en een file: dependency installeert vanuit uw kopie:

npm pack some-lib@1.4.2
mkdir -p vendor && mv some-lib-1.4.2.tgz vendor/
{
  "dependencies": {
    "some-lib": "file:vendor/some-lib-1.4.2.tgz"
  }
}

De tarball bevindt zich nu in uw repository en kan niet meer ongemerkt wijzigen. U bent vanaf nu ook verantwoordelijk voor het bijhouden van updates; gebruik dit dus voor kleine, verlaten pakketten waar u aan vastzit, niet voor uw webframework.

Er is ook een afkoelingsperiode, die niets kost:

npm install --before=2026-08-01

De optie before herbouwt de boom met uitsluitend versies die op of vóór die datum zijn gepubliceerd. Zet deze op een week of twee terug wanneer u afhankelijkheden ververst; hiermee vermijdt u de periode waarin een schadelijke release live is maar nog niet is gerapporteerd. Het is een grof instrument, omdat het ook noodzakelijke beveiligingsupdates tegenhoudt. Gebruik het om de bereiken op te lossen, lees wat er is veranderd en commit daarna het lockfile.

Hoe weet ik welke versie ik daadwerkelijk heb uitgebracht?

Het lockfile in git geeft aan wat er geïnstalleerd had moeten worden. De schijf geeft aan wat er daadwerkelijk is geïnstalleerd. Alleen het tweede is bewijs.

npm ls some-lib
node -e "console.log(require('./node_modules/some-lib/package.json').version)"

npm ls leest node_modules, dus het rapporteert wat er fysiek aanwezig is in plaats van wat het lockfile beoogde. De regel node -e leest het geïnstalleerde manifest op pad, wat zelfs werkt voor pakketten waarvan het exports-veld subpad-imports blokkeert, en drukt één versie af zonder boomstructuur eromheen.

Voor de andere helft van de vergelijking, lees git:

git log --oneline -- package-lock.json
git show <commit>:package-lock.json | grep -A3 '"node_modules/some-lib"'

Maak de verbinding tussen beide permanent door de commit in de deploy-layout te plaatsen. Breng uit naar /srv/nodeapp/releases/<short commit sha> en laat /srv/nodeapp/current ernaar verwijzen met een symlink. Het antwoord op "wat draait er op dit moment" wordt readlink /srv/nodeapp/current, en dit is om 03:00 uur beschikbaar voor iemand die niet de persoon was die het heeft uitgerold.

Controleer tot slot waarvoor het register garant staat:

npm audit signatures

Dit verifieert registersignaturen op de pakketten in uw geïnstalleerde boomstructuur en verifieert herkomstverklaringen (provenance attestations) voor pakketten die deze hebben. Herkomst koppelt een gepubliceerde tarball aan de publieke continuous integration (CI)-build die deze heeft geproduceerd. Een geverifieerde verklaring betekent dus dat u de code kunt herleiden naar een commit in plaats van naar een onbekende laptop. De dekking is niet universeel, dus lees een ontbrekende verklaring als "geen informatie", niet als "slecht pakket".

Wat te doen nadat een foutieve release uw server bereikt

Werk vanuit wat er is uitgevoerd en onder welke gebruiker dit gebeurde.

Als de code tijdens de installatie is uitgevoerd, ga er dan vanuit dat alles wat leesbaar is voor de build-gebruiker verloren is. Roteer het register-token, de SSH-keys in die home-directory, de cloud-credentials en elk geheim dat in die shell is geëxporteerd. Rotatie is de enige eerlijke reactie, omdat u niet kunt bewijzen dat een bestand niet is gelezen.

Als de code tijdens runtime onder een beveiligd service-account is uitgevoerd, is de bereikbare set veel kleiner: de omgevingsvariabelen van de applicatie zelf en alles wat de netwerktoegang kan bereiken. Dat is het volledige argument voor het draaien van services als niet-bevoorrechte gebruikers op een VPS. Het voorkomt de inbreuk niet. Het bepaalt wel hoeveel van de machine de inbreuk krijgt en of deze een herstart overleeft.

Bouw vervolgens opnieuw op in plaats van op te schonen. Verwijder node_modules, pin het betreffende pakket onder de foutieve versie in package.json, voer npm install eenmaal uit om het lockfile bij te werken, commit dit en deploy met npm ci. Repareer een boomstructuur niet op locatie. U kunt niet opsommen wat een installatiescript heeft aangepast.

Noteer ook het tijdsvenster: de eerste deploy die de versie had kunnen ophalen en de deploy die deze heeft verwijderd. Dat bereik vertelt u welke van uw eigen logs u moet lezen, en dit is alleen beantwoordbaar als uw releases zijn vernoemd naar commits.

Wat dit alles niet oplost

Een lockfile maakt een dependency niet veilig. Het verandert het moment waarop u die dependency accepteerde in een gedateerde, gecontroleerde beslissing in plaats van een bijwerking van een deploy. Elke bovenstaande werkwijze voert dezelfde conversie uit: van toeval naar keuze.

npm audit is hier geen verdediging. Het vergelijkt uw tree met een database van gerapporteerde kwetsbaarheden, waardoor het problemen vindt die al zijn gepubliceerd en benoemd. Een supply-chain-aanval is gedurende zijn gehele nuttige levensduur naamloos. Voer npm audit uit voor oude bekende bugs en verwacht er niets van met betrekking tot een release die vier uur geleden is uitgebracht.

Het verminderen van uw aantal dependencies helpt meer dan welk hulpmiddel in deze gids dan ook, en het is het minst populaire advies dat iemand geeft. Elk pakket dat u niet toevoegt, is één uitgever minder die namens u kan worden gephisht, en één installatiescript minder dat nooit als uw deploy-gebruiker wordt uitgevoerd.

Niets hiervan is specifiek voor npm. Dezelfde vier vormen zijn van toepassing op PyPI, RubyGems, container images en de package manager van uw distributie. Bij npm komt dit het meest naar voren omdat de trees het diepst zijn en installatiescripts standaard worden uitgevoerd. Hoeveel van de omliggende machine u moet verdedigen, hangt af van waar deze draait; dit is onderdeel van de bredere vraag of VPS-hosting veilig is.

FAQ

Biedt npm ci bescherming tegen een gecompromitteerd npm-pakket?

Het beschermt u tegen het wijzigen van de versie zonder dat u dit weet. npm ci installeert exact wat package-lock.json vastlegt, controleert elk tarball-bestand aan de hand van de sha512-integriteitshash en stopt met een foutmelding als package.json en het lockfile niet overeenkomen, in plaats van het verschil zelf op te lossen. Het zegt niets over de veiligheid van de vastgezette versie. Als u een lockfile commit waarin een kwaadaardige versie is vastgezet, zal npm ci die versie getrouw installeren op elke server die u bezit, elke keer opnieuw.

Moet ik ignore-scripts=true voor alles instellen?

Stel het in en gebruik daarna een allowlist. ignore-scripts=true in het .npmrc van het project voorkomt dat installatiescripts van dependencies worden uitgevoerd. Dit verwijdert de meest directe weg voor een schadelijk pakket om bij de inloggegevens van uw deploy-gebruiker te komen. Pakketten die een native addon compileren of een prebuilt binary ophalen, hebben hun scripts daadwerkelijk nodig. Als scripts zijn uitgeschakeld, falen deze pakketten later tijdens runtime met een ontbrekend binding-bestand in plaats van tijdens de installatie. Voer npm ci --ignore-scripts uit en gebruik daarna npm rebuild <package> voor de weinige pakketten die u besluit te vertrouwen. npm query ":attr(scripts, [postinstall])" laat zien hoeveel dat er werkelijk zijn.

Hoe kom ik erachter welke versie van een pakket mijn server daadwerkelijk heeft geïnstalleerd?

Lees de schijf, niet het lockfile. npm ls <package> rapporteert wat er aanwezig is in node_modules, en node -e "console.log(require('./node_modules/<package>/package.json').version)" print enkel de versiestring. Het lockfile in git beantwoordt een andere vraag, namelijk wat er geïnstalleerd had moeten worden; het vergelijken van beide is het doel. Door te deployen naar een map die vernoemd is naar de git-commit, blijven beide antwoorden maanden later beschikbaar wanneer u ze nodig heeft.

Vindt npm audit supply-chain-aanvallen?

Nee. npm audit vergelijkt uw boomstructuur met een database van gerapporteerde kwetsbaarheden, dus het vindt alleen problemen die al zijn gepubliceerd en een identificatiecode hebben gekregen. Een kwaadaardige release is niet gerapporteerd gedurende de uren of dagen dat installatie ervan ertoe doet. npm audit signatures is het nuttigere commando: het verifieert register-handtekeningen in uw geïnstalleerde boomstructuur en controleert herkomstverklaringen (provenance attestations) waar de uitgever deze heeft gegenereerd. Dit vertelt u dat een tarball afkomstig is van een publieke build in plaats van van een onbekende machine.

Waarom is het draaien van de applicatie als een gebruiker zonder privileges belangrijk als de aanval tijdens de installatie plaatsvindt?

Omdat de twee fouten een verschillend bereik hebben en u zich tegen beide verdedigt. Code die tijdens de installatie wordt uitgevoerd, draait als de deploy-gebruiker en kan de SSH-sleutels, register-tokens en cloud-inloggegevens van die gebruiker lezen. Runtime-code draait als het service-account, en met User=nodeapp, ProtectSystem=strict en zonder inloggegevens op de schijf die het kan lezen, stopt het bereik bij de eigen omgeving van de applicatie en de database. Het scheiden van de accounts betekent ook dat het proces dat verkeer afhandelt, node_modules niet kan herschrijven, waardoor een runtime-compromis bij de volgende herstart verdwenen is in plaats van permanent te worden.