Skip to content

fix(security): ferme les 6 alertes CodeQL + 3 alertes Dependabot - #45

Open
Kvnbbg wants to merge 4 commits into
mainfrom
fix/codeql-alerts
Open

fix(security): ferme les 6 alertes CodeQL + 3 alertes Dependabot#45
Kvnbbg wants to merge 4 commits into
mainfrom
fix/codeql-alerts

Conversation

@Kvnbbg

@Kvnbbg Kvnbbg commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Ferme #1, #4, #5, #6, #7, #8 (CodeQL) et les 3 alertes Dependabot ouvertes.

#6 / #7 — critical : une garde qui échoue en mode ouvert

const files = req.files ?? [];   // avant

?? protège contre null/undefined, pas contre le type. Si req.files est un objet — ce que multer produit dès qu'on passe de .array() à .fields() ou .single() — alors files.length vaut undefined, et undefined > MAX_FILES_PER_UPLOAD est false. Les deux limites d'upload cessent silencieusement de s'appliquer. C'est le pire cas : la garde a l'air présente et ne bloque plus rien.

const files = Array.isArray(req.files) ? req.files : [];   // après

#4 / #8 — le vrai risque ici est l'injection de prompt, pas le XSS

/<script[\s\S]*?<\/script>/gi ne matche pas </script > (avec espace) ni </script\n>. Le corps du script fuitait donc dans le texte extrait — et ce texte alimente le prompt de Laura. Une page hostile pouvait cacher des instructions dans un <script> à balise fermante espacée et les faire passer pour du « contenu de page » auprès du modèle.

Vérifié avant/après :

cas avant après
</script > hi evil() LEAK hi LEAK
</script\n> hi evil() LEAK hi LEAK
</style > ok a{} LEAK ok LEAK

Commentaires HTML également retirés.

#5 — durcissement, pas une faille

href recevait la saisie utilisateur validée par un simple préfixe regex. Le préfixe ^https?:// bloquait déjà javascript:, donc ce n'était pas exploitable. Remplacé par un parsing new URL + liste blanche de protocoles, qui rejette en plus les URL malformées ; tout ce qui échoue retombe en texte brut.

#1 — permissions du workflow

permissions: contents: read ajouté à ci.yml (le workflow ne fait que builder et tester).

Dependabot — lockfile uniquement

La plage déclarée ^7.14.1 était déjà satisfaisante ; seules des versions vulnérables étaient figées dans le lock.

  • react-router / react-router-dom 7.18.1 → 7.18.3 (HIGH, GHSA-qwww-vcr4-c8h2, contournement CSRF en mode RSC)
  • js-yaml4.3.2 (HIGH, CPU quadratique sur !!omap)
  • postcss8.5.26 (MEDIUM, correctif incomplet de GHSA-6g55-p6wh-862q)

Vérification

gate résultat
npm run lint 0
tsc --noEmit 0
vitest 29/29
npm run build OK
npm run security:scan passed
npm run provenance:check passed
npm audit 0 vulnérabilité

🤖 Generated with Claude Code

https://claude.ai/code/session_01AFfrQjSxUQ5EPDFNt6Qd6r

Kvnbbg and others added 2 commits September 2, 2026 12:27
server/index.js (#6, #7 — critical, type confusion)
  `req.files ?? []` ne garde que null/undefined, pas le type. Si req.files
  est un objet (ce que multer produit avec .fields()/.single()), files.length
  vaut undefined et `undefined > MAX` est false : les deux limites d'upload
  cessent silencieusement de s'appliquer. Remplacé par Array.isArray().

terminal-plugins/{moltbook,french-dev-social}.mjs (#4, #8 — bad-tag-filter)
  /<script[\s\S]*?<\/script>/ ne matche pas `</script >`, donc le corps du
  script fuitait dans le texte extrait. Ce texte alimente le prompt de Laura :
  le risque réel ici est l'injection de prompt, pas le XSS. Ajout de \b et
  \s* sur les balises fermantes, et suppression des commentaires HTML.
  Vérifié : `</script >`, `</script\n>`, `</style >` ne fuient plus.

src/components/ChatWidget.tsx (#5 — xss-through-dom)
  href recevait la saisie utilisateur validée par un simple préfixe regex.
  Remplacé par un parsing `new URL` + liste blanche de protocoles ; tout ce
  qui échoue retombe en texte brut. Durcissement (le préfixe bloquait déjà
  javascript:), pas une faille exploitée.

.github/workflows/ci.yml (#1 — missing workflow permissions)
  Ajout de `permissions: contents: read`.

Vérifié : npm run lint (0), tsc --noEmit (0), vitest 29/29.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFfrQjSxUQ5EPDFNt6Qd6r
Lockfile uniquement — la plage déclarée (^7.14.1) était déjà satisfaisante,
les versions vulnérables étaient juste figées dans le lock.

- react-router / react-router-dom 7.18.1 -> 7.18.3 (HIGH, GHSA-qwww-vcr4-c8h2,
  contournement CSRF en mode RSC)
- js-yaml -> 4.3.2 (HIGH, consommation CPU quadratique sur !!omap)
- postcss -> 8.5.26 (MEDIUM, correctif incomplet de GHSA-6g55-p6wh-862q)

npm audit : 0 vulnérabilité. Vérifié : vitest 29/29, build OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFfrQjSxUQ5EPDFNt6Qd6r
@railway-app

railway-app Bot commented Sep 2, 2026

Copy link
Copy Markdown

This PR was not deployed automatically as @Kvnbbg does not have access to the Railway project.

In order to get automatic PR deploys, please add @Kvnbbg to your workspace on Railway.

@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
laura Ready Ready Preview Sep 5, 2026 10:20pm UTC

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@socket-security

socket-security Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedreact-router-dom@​7.18.1 ⏵ 7.18.31001006597 +2100

View full report

Comment thread terminal-plugins/french-dev-social.mjs Fixed
Comment thread terminal-plugins/moltbook.mjs Fixed
Le correctif CodeQL précédent déplaçait le retrait des commentaires avant
celui des <script>/<style>. Un « <!-- » isolé dans un corps de script (légal
en JS via les commentaires HTML-like de l'Annexe B, fréquent dans les bundles
minifiés) faisait alors courir la correspondance paresseuse au-delà de
</script> jusqu'au « --> » suivant : le terminateur était consommé, la regex
script ne matchait plus, et le code JS brut se retrouvait dans le texte —
puis dans le prompt LLM via callBridge, depuis une page distante contrôlable
par un tiers. L'ordre inverse a le défaut miroir : un <script> commenté
avalait le texte visible qui suivait.

Une alternation unique, balayée de gauche à droite, n'a aucun des deux
défauts : la construction qui s'ouvre en premier gagne.

Ajoute les tests de non-régression correspondants ; terminal-plugins/
n'avait aucune couverture, d'où la régression.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012k6VRd5iRauyF9xndGLMe9
// either way: comments-first lets a stray `<!--` inside a script body run
// past `</script>` and leak raw JS into the prompt, while scripts-first
// lets a commented-out `<script>` swallow the visible text after it.
.replace(/<!--[\s\S]*?-->|<script\b[\s\S]*?<\/script\s*>|<style\b[\s\S]*?<\/style\s*>/gi, ' ')
// past `</script>` and leak raw JS into the prompt below, while
// scripts-first lets a commented-out `<script>` swallow the visible text
// after it. A single pass has neither failure.
.replace(/<!--[\s\S]*?-->|<script\b[\s\S]*?<\/script\s*>|<style\b[\s\S]*?<\/style\s*>/gi, ' ')
DÉPENDANCES — qs 6.15.3 -> 6.16.0 : contournement de la limite de tableau par
virgule dans une clé entre crochets (GHSA-x5fp-wj9c-mxmx) et déni de service
par isBuffer contrôlé par l'attaquant (GHSA-4mjr-xmp4-gh2g).

`npm audit fix` ne pouvait rien : qs vient d'express@4.22.2, déjà la dernière
4.x, et la seule voie automatique passait par Express 5 — une rupture majeure
pour un correctif mineur. Un `overrides` cible la dépendance transitive sans
toucher à Express, et le bloc existait déjà dans ce package.json (cookie, send,
serve-static, body-parser) : c'est la convention du projet, pas une de plus.

SCAN — la règle `public-local-or-private-origin` échouait à CHAQUE exécution,
sur `http://localhost` NU. Cette chaîne est la base de repli de react-router
lorsque `location.origin` vaut « null » (contexte bac à sable), inlinée dans
tout bundle. Elle ne joint rien et ne révèle rien.

Un contrôle rouge en permanence cesse d'être lu, et c'est alors la vraie fuite
qui passe : le bruit coûte plus cher que l'absence de règle. Vérifié sur la
branche de sauvegarde — le scan échouait déjà avant ce lot, ce n'est pas une
régression du correctif qs.

La règle distingue désormais une adresse de DÉVELOPPEMENT (port ou chemin :
`localhost:5173/api`, IP privée complète, `/home/…`, `file:///…`) du repli nu.
Deux resserrages sont venus des tests eux-mêmes :
  - `10.0.\d` attrapait la version « 10.0.1 » -> IP complète exigée ;
  - `localhost[:/]\d` MANQUAIT `http://localhost/api/admin`, un chemin
    commençant par une lettre -> `[\w-]`.

Les fichiers de test sont exclus du scan : les tests d'un détecteur portent par
construction des échantillons de ce qu'il détecte, et signaler le garde parce
qu'il connaît le visage du voleur n'apprend rien. L'exclusion ne touche aucun
code livré.

13 cas figent la frontière dans les deux sens — ce qui doit encore être
attrapé, ce qui ne doit plus l'être — et un test compare le motif du test à
celui du script, deux copies d'une même règle finissant toujours par diverger.

Vérifié : 0 vulnérabilité sur l'audit, build vert, 52 tests verts, scan vert,
et détection confirmée en déposant une vraie fuite dans public/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants