SSD Nodes Learn 🎉 VPS dès $5.50/mois
Guides Matt ConnorPar Matt Connor

Attaques npm : sécuriser le déploiement Node sur VPS

Découvrez comment une release npm malveillante, un script postinstall ou un typosquat atteint votre VPS, et quelle pratique de déploiement bloque ces attaques.

Ce qu’est une attaque de supply chain npm sur votre serveur

Une attaque de supply chain npm atteint votre serveur par l’intermédiaire d’un package que vous avez choisi d’installer. Aucun port ouvert n’est impliqué et aucune étape d’exploitation n’est nécessaire. npm (node package manager) installe du code, et l’installation de code exécute du code. Une petite application Node récupère donc plusieurs centaines de packages que vous n’avez jamais examinés. N’importe lequel d’entre eux peut publier une nouvelle version dans une heure.

Votre déploiement récupère une version malveillante parce que votre commande d’installation demande la version correspondante la plus récente. Ce code s’exécute ensuite avec les privilèges de l’utilisateur qui a lancé l’installation. Tout ce qui suit découle de ces deux phrases.

Les scénarios sont classés selon leur fréquence pour une personne qui déploie une application Node sur un seul VPS. Cet ordre ne correspond pas à celui qu’utiliserait une grande entreprise, car celle-ci dispose d’un registre interne, d’une équipe de revue et d’un miroir du registre public. Vous avez un script de déploiement.

Forme 1 : un compte de mainteneur est compromis et publie un correctif

Le registre npm ne permet à personne de modifier le contenu d’une version qui existe déjà. Un attaquant qui hameçonne un mainteneur ou vole un token de publication ne peut donc pas réécrire 4.18.2. Il publie 4.18.3.

Consultez votre package.json. Une ligne comme "express": "^4.18.2" ne signifie pas la version 4.18.2. Le caret signifie « n’importe quelle version 4.x supérieure ou égale à celle-ci », et ~4.18.2 signifie « n’importe quelle version 4.18.x ». npm install résout cette plage au moment de son exécution. Le même commit Git, déployé deux fois le même après-midi, peut donc installer deux ensembles de code différents. Cet écart constitue la surface d’attaque. Aucun élément de votre machine n’a besoin d’être compromis pour qu’elle s’ouvre.

Les versions malveillantes sont généralement signalées et retirées, mais le retrait intervient après leur installation. Toute personne ayant effectué un déploiement pendant cette période a le code sur son disque. Un pipeline qui résout les plages à chaque exécution entre automatiquement dans cette période, plusieurs fois par semaine, sans décision explicite.

Forme 2 : un script d’installation s’exécute avec les droits de l’utilisateur qui lance le déploiement

Le package.json d’un paquet peut déclarer preinstall, install, postinstall et prepare dans son bloc scripts. npm les exécute pendant l’installation. Ils ne sont pas isolés et personne ne les vérifie. Ce sont des commandes shell qui s’exécutent avec les droits de l’utilisateur ayant saisi la commande d’installation, dans le répertoire personnel de cet utilisateur, avec son accès réseau et l’environnement complet de ce shell.

La question utile n’est donc pas ce que le paquet peut faire. Il faut déterminer ce que cet utilisateur peut lire. Sur un serveur de déploiement classique, cela inclut ~/.npmrc, qui contient un token de registry, ~/.ssh/id_ed25519, utilisé comme deploy key pour SSH (secure shell), ~/.aws/credentials, ~/.docker/config.json, ainsi que toutes les variables exportées dans le shell. C’est généralement là que se trouve DATABASE_URL.

Un payload de ce type n’a besoin ni de persistance ni d’escalade de privilèges. Il lit quelques fichiers, les envoie à un hôte via HTTPS, puis se termine avec le code de sortie 0. Vous ne voyez rien, car npm masque la sortie des scripts d’installation par défaut. Désactivez ce comportement pour observer ce qui s’exécute réellement :

npm ci --foreground-scripts

foreground-scripts partage les flux standard d’entrée, de sortie et d’erreur avec le processus npm. Les scripts de build affichent donc leur sortie dans votre terminal, au lieu de l’écrire dans un buffer que npm supprime lorsque l’installation réussit.

Forme 3 : les typosquats et le nom que vous n’avez pas tout à fait saisi

Un typosquat est un package publié sous un nom proche d’un nom populaire. Il attend une commande d’installation mal saisie ou mal collée. Le mécanisme repose sur la commande, pas sur le code. Un lockfile ne vous protège donc pas ici : vous ajoutez une seule fois le mauvais nom, puis le lockfile le verrouille fidèlement.

La variante qui vise les équipes plutôt que les individus est la confusion de dépendances. Votre package interne s’appelle billing-utils et se trouve sur un registry privé. Si aucun package nommé billing-utils n’existe sur le registry public, n’importe qui peut en publier un. npm résout les noms sans scope auprès du registry public par défaut. La copie publique peut donc être sélectionnée. La correction consiste à utiliser un scope que vous contrôlez et un mapping de registry pour ce scope, dans .npmrc :

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

Désormais, @yourorg/billing-utils est toujours récupéré depuis cet hôte, car le mapping scope-registry est consulté avant le registry par défaut. Un nom interne sans scope n’a aucun mapping et ne bénéficie donc d’aucune protection.

Avant d’ajouter une nouvelle dépendance, examinez-la plutôt que son badge de téléchargements :

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

Un package créé le mois dernier et publié par un compte que vous ne pouvez pas relier à un dépôt public présente un risque différent de celui d’un package qui a six ans d’historique. Aucun de ces éléments ne constitue une preuve. Les deux sont faciles à vérifier.

Forme 4 : la dépendance dont le responsable a discrètement changé

Les mainteneurs se transmettent les packages. Quelqu’un s’épuise, une personne inconnue propose son aide, les droits de publication changent de main, et aucun avis ne parvient aux projets qui en dépendent. Rien n’est compromis. La confiance que vous accordiez en 2021 est désormais détenue par une autre personne.

C’est la forme la plus lente et la plus difficile à détecter, et aucune commande n’y répond directement. Deux mesures permettent de réduire le risque. Vérifiez qui peut publier avant d’adopter un package, avec la ligne npm view ci-dessus. Lisez ensuite le diff lorsqu’un package dont vous dépendez réellement évolue :

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

La première forme affiche uniquement les noms des fichiers modifiés. Elle est donc assez rapide à exécuter lors de chaque mise à niveau d’un package important pour vous. Une patch release qui modifie un build script, ajoute un fichier à la racine du package ou édite le bloc scripts mérite d’être lue intégralement avant d’atteindre votre serveur.

Construire à partir d’un lockfile versionné avec npm ci

package-lock.json enregistre la version exacte de chaque package de l’arbre, l’URL d’origine de chacun, un hash d’intégrité sha512 pour chaque tarball et le package qui l’a requis. Versionnez-le. C’est le seul fichier qui indique ce que vous avez réellement testé.

Installez ensuite avec npm ci, jamais avec npm install, sur toute machine qui n’est pas le laptop d’un développeur :

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

npm ci diffère de npm install sur des points qui sont tous importants ici. Il exige qu’un lockfile existe. Il supprime tout node_modules existant avant de commencer, afin que les restes d’un déploiement précédent ne puissent pas subsister dans celui-ci. Il n’écrit jamais dans package.json ni dans le lockfile. Une installation ne peut donc pas vous faire passer silencieusement à une version plus récente. Si le lockfile et package.json ne correspondent pas, la commande s’arrête avec une erreur au lieu de résoudre la différence.

Cette erreur est une fonctionnalité, pas une gêne. Elle signifie qu’une modification de dépendance doit arriver sous la forme d’un commit relu, et non comme un effet secondaire d’un déploiement à 02:00.

Le hash d’intégrité est vérifié à chaque téléchargement. Si les octets d’un tarball ne correspondent pas au hash enregistré, l’installation échoue avec code EINTEGRITY au lieu de le décompresser. Soyez précis sur ce que cela garantit : le fichier reçu est bien celui que le lockfile a épinglé. C’est la même garantie que fournit la vérification des téléchargements avec des checksums, avec les mêmes limites. Cela ne dit rien sur le caractère malveillant de la version épinglée au moment de sa publication.

Un détail concernant --omit=dev : ces packages sont tout de même résolus et écrits dans le lockfile. Ils ne sont simplement pas placés sur le disque. Moins de packages sur le disque signifie moins de scripts d’installation et moins de code chargé à l’exécution. Cela vaut donc la peine. Cette option ne supprime pas une dépendance de votre arbre.

Traitez les scripts d’installation comme du code et sachez les refuser

Vous pouvez désactiver les scripts d’installation. Ajoutez ceci au .npmrc du projet et validez-le avec le lockfile :

ignore-scripts=true
save-exact=true

ignore-scripts=true empêche npm d’exécuter les scripts déclarés dans les dépendances. save-exact=true demande à npm install some-lib d’écrire 1.4.2 dans package.json au lieu de ^1.4.2. Ainsi, une plage de versions ne se retrouve jamais par erreur dans votre manifest.

Cette option casse certaines installations. Vous devez savoir lesquelles avant de l’activer. Les packages qui compilent un addon natif ou téléchargent un binaire précompilé effectuent ce travail dans un script d’installation. Lorsque les scripts sont désactivés, l’installation elle-même réussit, puis l’échec apparaît à l’exécution : le module ne peut pas charger son fichier de binding. La solution consiste à définir une allowlist :

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

npm rebuild <package> exécute les scripts de build de ce package uniquement. Vous prenez ainsi une décision pour chaque package, au lieu d’accorder une autorisation générale d’exécution à plusieurs centaines de personnes que vous ne rencontrerez jamais.

Pour connaître l’étendue actuelle de cette autorisation, interrogez npm :

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

Cette commande affiche tous les packages de l’arborescence installée qui contiennent un script postinstall. Dans une application classique, la liste est plus courte que prévu. C’est précisément ce qui rend l’allowlist pratique.

Séparer le build du processus qui sert le trafic

L’utilisateur de déploiement doit pouvoir écrire dans node_modules. Le processus qui répond aux requêtes HTTP n’en a pas besoin. S’il s’agit du même compte, le code exécuté pendant l’installation peut réécrire le code qui sert vos utilisateurs. Le code exécuté à l’exécution peut également le réécrire.

Séparez-les. Effectuez le build avec un utilisateur et servez l’application avec un autre. Rendez le répertoire servi accessible en lecture seule pour le compte qui sert l’application :

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

Laissez ensuite systemd appliquer cette règle. Écrivez /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 monte l’intégralité du système de fichiers en lecture seule pour ce service, à l’exception de /dev, /proc, /sys et des chemins que vous indiquez dans ReadWritePaths. Ainsi, une tentative de l’application pour écrire dans node_modules échoue avec EROFS: read-only file system. Vous pouvez le reproduire dans vos propres journaux en environ une minute. NoExecPaths concerne le répertoire d’upload accessible en écriture : le service peut y écrire des fichiers, mais le kernel refuse de les exécuter. Cette option nécessite systemd 249 ou une version ultérieure. Ubuntu 24.04 fournit la version 255.

Cette unit file contient deux pièges. Premièrement, n’ajoutez pas MemoryDenyWriteExecute=yes. Cette option figure dans la plupart des listes de hardening systemd. Elle empêche Node de démarrer, car V8 compile le JavaScript en code machine à l’exécution et a besoin de pages à la fois accessibles en écriture et exécutables. Deuxièmement, récupérez le chemin ExecStart depuis command -v node. Si Node a été installé avec un version manager, il se trouve sous le home directory de l’utilisateur de déploiement. ProtectHome=yes masque alors ce répertoire pour le service, et l’unit échoue immédiatement avec status=203/EXEC et une ligne de log indiquant que l’exécutable est introuvable.

Vérifiez le résultat au lieu de faire confiance au fichier :

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 répertorie chaque paramètre de hardening avec son niveau d’exposition. Vous pouvez ainsi voir lesquels utilisent encore leur valeur par défaut. touch doit échouer avec Permission denied, car nodeapp ne possède aucun fichier sous current. S’il réussit, les propriétaires de vos fichiers sont incorrects et les paramètres systemd masquent discrètement le problème.

Une remarque concernant EnvironmentFile : systemd le lit en tant que root avant de passer à User=nodeapp. Ce fichier peut donc être root:root avec le mode 600. L’application reçoit tout de même les variables. Toute personne disposant d’un shell en tant que nodeapp peut encore les lire depuis /proc/<pid>/environ. Cette protection concerne donc le secret au repos, et non le processus en cours d’exécution.

Ne conservez pas les identifiants de déploiement dans l’environnement de build

Les scripts d’installation héritent de l’environnement. Ce seul fait doit déterminer l’endroit où vous effectuez le build.

La solution la plus sûre consiste à effectuer le build ailleurs que sur le serveur de production, puis à y copier le répertoire final. La machine de build ne contient alors qu’un token de registry en lecture seule. Elle ne contient aucune autre information. Ni clé SSH de déploiement, ni clé d’accès cloud, ni mot de passe de base de données, ni identifiants de connexion à une container registry.

npm token create --read-only

Un token en lecture seule peut récupérer des packages, mais il ne peut pas en publier. S’il est volé depuis l’environnement de build, la perte se limite à la possibilité de télécharger des packages publics.

Si vous devez effectuer le build sur le serveur, utilisez l’utilisateur deploy avec un environnement volontairement limité. Conservez les secrets d’exécution dans /etc/nodeapp/env, que deploy ne peut pas lire. Le même raisonnement s’applique à l’automatisation du build que vous hébergez vous-même : un runner GitHub Actions auto-hébergé détient des tokens et exécute du code publié arbitraire à chaque job. Il devient donc la machine qui a le plus de valeur dans un petit déploiement. Tout programme que vous n’avez pas écrit et qui reçoit l’ensemble de votre environnement appartient à la même catégorie. C’est pourquoi conserver les secrets hors de l’environnement d’un agent IA revient au même problème, avec un autre programme intercalé.

Épinglez ou intégrez les dépendances que vous ne pouvez pas auditer

Une dépendance épinglée est une dépendance dont la version ne peut pas changer sans un commit. Le lockfile versionné fait déjà cela pour l’ensemble de l’arbre. Deux cas nécessitent toutefois une protection supplémentaire.

Les dépendances transitives sont le premier cas. Vous ne contrôlez pas les dépendances de vos dépendances. overrides dans package.json force une version partout dans l’arbre :

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

Exécutez npm install une fois après l’avoir ajouté afin que le lockfile enregistre le résultat, puis versionnez les deux fichiers.

Le deuxième cas concerne un package que vous ne pouvez ni auditer ni supprimer. Intégrez-le à votre dépôt. npm pack télécharge l’archive tar exacte que le registry fournirait, et une dépendance file: s’installe depuis votre copie :

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"
  }
}

L’archive tar se trouve maintenant dans votre dépôt et ne peut plus être modifiée à votre insu. Vous devez toutefois assurer ses mises à jour indéfiniment. Utilisez donc cette méthode pour le petit package abandonné dont vous ne pouvez pas vous passer, pas pour votre framework web.

Il existe également une période de refroidissement qui ne coûte rien :

npm install --before=2026-08-01

L’option before reconstruit l’arbre en utilisant uniquement les versions publiées à cette date ou avant. Reculez la date d’une ou deux semaines lorsque vous actualisez vos dépendances. Vous évitez ainsi la période pendant laquelle une mauvaise release est disponible, mais n’a pas encore été signalée. Cette méthode est rudimentaire, car elle retarde aussi les véritables correctifs de sécurité. Utilisez-la pour résoudre les plages de versions, examinez les changements, puis versionnez le lockfile.

Comment savoir quelle version a réellement été déployée ?

Le lockfile dans git indique ce qui aurait dû être installé. Le disque indique ce qui est installé. Seule la seconde information constitue une preuve.

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

npm ls lit node_modules. Il indique donc ce qui est réellement présent, et non ce que le lockfile prévoyait. La ligne node -e lit le manifest installé à partir de son chemin. Cela fonctionne même pour les packages dont le champ exports bloque les imports de sous-chemins. Elle affiche une seule version, sans arborescence autour.

Pour l’autre moitié de la comparaison, consultez git :

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

Rendez le lien entre les deux permanent en intégrant le commit dans l’arborescence de déploiement. Publiez dans /srv/nodeapp/releases/<short commit sha> et faites pointer /srv/nodeapp/current vers cette version avec un symlink. La réponse à « quelle version s’exécute actuellement ? » devient readlink /srv/nodeapp/current. Elle reste disponible à 03:00 pour une personne qui n’a pas effectué le déploiement.

Enfin, vérifiez ce que le registry peut attester :

npm audit signatures

Cette commande vérifie les signatures du registry sur les packages présents dans votre arborescence installée. Elle vérifie également les attestations de provenance pour les packages qui en possèdent. La provenance relie un tarball publié au build public de continuous integration (CI) qui l’a produit. Une attestation vérifiée permet donc de remonter le code jusqu’à un commit plutôt que jusqu’à un laptop inconnu. La couverture n’est pas universelle. Interprétez donc une attestation manquante comme une « absence d’information », et non comme l’indication d’un package défectueux.

Que faire après le déploiement d’une version compromise sur votre serveur

Partez de ce qui a été exécuté et de l’utilisateur sous lequel le code a tourné.

Si le code a été exécuté pendant l’installation, considérez que tout ce qui était lisible par l’utilisateur de build est compromis. Faites tourner le token du registry, les clés SSH du répertoire personnel concerné, les identifiants cloud et tous les secrets exportés dans ce shell. La rotation est la seule réponse honnête, car vous ne pouvez pas prouver qu’un fichier n’a pas été lu.

Si le code a été exécuté au runtime sous un compte de service avec des privilèges limités, l’ensemble accessible est beaucoup plus restreint : les variables d’environnement de l’application et tout ce que ses accès réseau permettent d’atteindre. C’est tout l’intérêt de faire tourner les services avec un compte non privilégié sur un VPS. Cela n’empêche pas la compromission. Cela détermine la partie de la machine à laquelle la compromission accède et si elle persiste après un redémarrage.

Reconstruisez ensuite l’environnement au lieu de le nettoyer. Supprimez node_modules, verrouillez le package concerné sur une version antérieure à la version compromise dans package.json, exécutez npm install une seule fois pour mettre à jour le lockfile, validez la modification, puis déployez avec npm ci. Ne réparez pas une arborescence sur place. Vous ne pouvez pas déterminer tout ce qu’un script d’installation a modifié.

Notez également la fenêtre concernée : le premier déploiement qui pouvait récupérer cette version et le déploiement qui l’a supprimée. Cette plage vous indique quels journaux consulter. Vous ne pouvez répondre à cette question que si vos releases portent le nom des commits.

Ce que tout cela ne résout pas

Un lockfile ne rend pas une dépendance sûre. Il transforme le moment où vous avez accepté cette dépendance en une décision datée et examinée, au lieu d’en faire une conséquence indirecte d’un déploiement. Toutes les pratiques précédentes effectuent la même conversion : passer d’un accident à un choix.

npm audit n’est pas une protection dans ce cas. Il compare votre arborescence à une base de données de vulnérabilités signalées. Il détecte donc les problèmes qui ont déjà été publiés et nommés. Une attaque de la supply chain reste sans nom pendant toute sa période d’exploitation. Exécutez npm audit pour les anciennes vulnérabilités connues, mais n’en attendez rien concernant une release publiée il y a quatre heures.

Réduire le nombre de vos dépendances est plus efficace que n’importe quel outil présenté dans ce guide. C’est aussi le conseil le moins populaire. Chaque package que vous n’ajoutez pas représente un éditeur de moins qui peut être victime de phishing en votre nom, ainsi qu’un script d’installation supplémentaire qui ne s’exécutera jamais avec votre compte de déploiement.

Rien de tout cela ne concerne uniquement npm. Les mêmes quatre cas s’appliquent à PyPI, RubyGems, aux images de conteneurs et au package manager de votre distribution. Le problème est plus visible avec npm, car les arborescences sont plus profondes et les scripts d’installation s’exécutent par défaut. La partie de la machine environnante que vous devez protéger dépend de l’endroit où le code s’exécute. C’est une composante de la question plus générale de savoir si l’hébergement VPS est sûr.

FAQ

Est-ce que npm ci me protège contre un paquet npm compromis ?

Il vous protège contre une modification de version à votre insu. npm ci installe exactement ce que package-lock.json enregistre, vérifie chaque tarball avec son hash d’intégrité sha512 et s’arrête avec une erreur si package.json et le lockfile ne correspondent pas, au lieu de résoudre la différence. Il ne dit rien sur la sécurité de la version épinglée. Si vous validez un lockfile qui épingle une version malveillante, npm ci l’installera fidèlement sur chacun de vos serveurs, à chaque fois.

Dois-je définir ignore-scripts=true pour tout ?

Définissez-le, puis ajoutez des exceptions à une allowlist. ignore-scripts=true dans le .npmrc du projet empêche l’exécution des scripts d’installation des dépendances, ce qui supprime le chemin le plus direct entre un paquet malveillant et les identifiants de votre utilisateur de déploiement. Les paquets qui compilent un addon natif ou récupèrent un binaire précompilé en ont réellement besoin. Avec les scripts désactivés, ils échouent plus tard au runtime avec un fichier de binding manquant, au lieu d’échouer pendant l’installation. Exécutez npm ci --ignore-scripts, puis npm rebuild <package> pour les quelques paquets auxquels vous avez décidé de faire confiance. npm query ":attr(scripts, [postinstall])" indique combien ils sont réellement.

Comment savoir quelle version d’un paquet mon serveur a réellement installée ?

Lisez le contenu du disque, pas le lockfile. npm ls <package> indique ce qui est présent dans node_modules, et node -e "console.log(require('./node_modules/<package>/package.json').version)" affiche uniquement la chaîne de version. Le lockfile dans git répond à une autre question : ce qui aurait dû être installé. La comparaison des deux est précisément le but. Effectuer le déploiement dans un répertoire portant le nom du commit git permet de conserver ces deux informations plusieurs mois plus tard, lorsque vous en avez besoin.

Est-ce que npm audit détecte les attaques de la supply chain ?

Non. npm audit compare votre arborescence à une base de données de vulnérabilités signalées. Il ne détecte donc que les problèmes déjà publiés et associés à un identifiant. Une release malveillante peut rester non signalée pendant les heures ou les jours où son installation est importante. npm audit signatures est la commande la plus utile : elle vérifie les signatures du registry dans toute votre arborescence installée et contrôle les attestations de provenance lorsque l’éditeur en a produit. Elle indique ainsi qu’un tarball provient d’un build public plutôt que d’une machine inconnue.

Pourquoi est-il important d’exécuter l’application avec un utilisateur non privilégié si l’attaque se produit au moment de l’installation ?

Parce que ces deux défaillances n’ont pas la même portée et que vous devez vous protéger contre les deux. Le code exécuté au moment de l’installation s’exécute avec les droits de l’utilisateur de déploiement. Il peut lire les clés SSH, les tokens de registry et les identifiants cloud de cet utilisateur. Le code exécuté au runtime s’exécute avec les droits du compte de service. Avec User=nodeapp, ProtectSystem=strict et sans identifiants sur le disque, sa portée se limite à l’environnement de l’application et à sa base de données. La séparation des comptes signifie également que le processus qui sert le trafic ne peut pas réécrire node_modules. Une compromission au runtime disparaît donc au redémarrage suivant, au lieu de devenir persistante.