Skip to content

Bump transitive brace-expansion to patched versions (CVE-2026-13149) - #4294

Merged
wojtekn merged 2 commits into
trunkfrom
fix-brace-expansion-dos-advisory
Jul 23, 2026
Merged

Bump transitive brace-expansion to patched versions (CVE-2026-13149)#4294
wojtekn merged 2 commits into
trunkfrom
fix-brace-expansion-dos-advisory

Conversation

@wojtekn

@wojtekn wojtekn commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Related issues

How AI was used in this PR

Claude Code investigated the Dependabot alert, mapped the full set of vulnerable brace-expansion copies in the lockfile, determined that a pi-coding-agent version bump could not resolve the nested copy (npm re-pins the same resolution), and applied the lockfile-only fix. All findings were verified against npm audit and typecheck.

Proposed Changes

brace-expansion had a high-severity DoS advisory (CVE-2026-13149): a tiny crafted input can hang the calling thread for minutes via exponential-time brace expansion. Studio pulled it in transitively across several dependency trees at unpatched versions.

This upgrades those transitive copies to patched releases (1.1.16, 2.1.2, and 5.0.7 where npm can move them), closing alerts #290 (the 2.x range) and #289 (the 1.x range). The change is lockfile-only — no manifest or app-code changes — so there is no user-visible behavior change; it removes a potential denial-of-service vector in dev/build and runtime dependency chains. npm audit fix also carried a handful of other in-range transitive security patches (body-parser, fast-uri, fast-xml-parser, protobufjs) in the same pass.

One brace-expansion@5.0.6 copy bundled deep inside @earendil-works/pi-coding-agent (Dependabot alert #288) is intentionally left untouched here — npm will not upgrade that nested resolution in place, and forcing it would require a package-wide override. It can be addressed separately.

Testing Instructions

  • npm run typecheck passes across all workspaces.
  • npm audit no longer reports brace-expansion in the 1.x or 2.x ranges; the only remaining match is the pi-coding-agent nested 5.0.6 copy (alert Release 1.0.5-beta1 #288).
  • On-disk node_modules versions match the updated lockfile.

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?
Resolves the brace-expansion DoS advisory (CVE-2026-13149) by upgrading
the transitive 1.x and 2.x copies to patched releases via npm audit fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@wojtekn
wojtekn requested a review from a team July 22, 2026 08:12
@wpmobilebot

Copy link
Copy Markdown
Collaborator

📊 Performance Test Results

Comparing 032fa2b vs trunk

app-size

Metric trunk 032fa2b Diff Change
App Size (Mac) 1361.90 MB 1361.90 MB +0.01 MB ⚪ 0.0%

site-editor

Metric trunk 032fa2b Diff Change
load 1085 ms 1060 ms 25 ms ⚪ 0.0%

site-startup

Metric trunk 032fa2b Diff Change
siteCreation 6505 ms 6530 ms +25 ms ⚪ 0.0%
siteStartup 2391 ms 2385 ms 6 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

@wojtekn
wojtekn requested a review from youknowriad July 22, 2026 14:23
import { createRequire as __bundleCreateRequire } from 'node:module';
const require = __bundleCreateRequire(import.meta.url);
import{d as p}from"./chunk-3QFGXLW4.mjs";import"./chunk-2JVHGDER.mjs";import"./chunk-Y7SDG6HL.mjs";import"./chunk-4TKBPMF4.mjs";import{existsSync as d,readFileSync as g,readdirSync as x}from"node:fs";import{join as r}from"node:path";function u(t="<outputDir> [args...]"){let e=process.argv[2];return e||(console.error(`usage: tsx scripts/${h()} ${t} (e.g. output/www.example.com)`),process.exit(1)),e}function h(){return process.argv[1]?.split("/").pop()??"<script>.ts"}var f=u(),s=r(f,"theme","patterns"),m=r(f,"html");function w(t){return t.replace(/<script\b[\s\S]*?<\/script>/gi," ").replace(/<style\b[\s\S]*?<\/style>/gi," ").replace(/<noscript\b[\s\S]*?<\/noscript>/gi," ").replace(/<[^>]+>/g," ").replace(/\s+/g," ").trim()}function S(t){return[...t.matchAll(/\b(?:alt|title|aria-label)=["']([^"']*)["']/gi)].map(e=>e[1].trim()).filter(Boolean)}var $=t=>t.replace(/\.php$/i,"").replace(/^page-/,""),n=[],i=[];for(let t of x(s).filter(e=>e.endsWith(".php"))){let e=$(t),l=r(m,`${e}.html`);if(!d(l)){i.push(`${t} (no source html/${e}.html)`);continue}let c=g(l,"utf8"),a=w(c);n.push({slug:`${t}`,php:g(r(s,t),"utf8"),spec:{interactionModel:"static",expectedAssets:[],expectedText:[a,...S(c)],bodyText:[a]}})}n.length===0&&(console.error(`no pattern\u2194source-html pairs found under ${s} / ${m}`),process.exit(1));var o=p({patterns:n});console.log(`validated ${n.length} pattern(s); skipped ${i.length}`);for(let t of i)console.log(` skipped: ${t}`);console.log("ok:",o.ok);console.log("errors:",JSON.stringify(o.errors,null,2));console.log("warning count:",o.warnings.length);var y=o.warnings.filter(t=>!/possible non-source content/.test(t.message));console.log("non-word-noise warnings:",JSON.stringify(y,null,2));
import{d as p}from"./chunk-5TGR3JN3.mjs";import"./chunk-2JVHGDER.mjs";import"./chunk-Y7SDG6HL.mjs";import"./chunk-4TKBPMF4.mjs";import{existsSync as d,readFileSync as g,readdirSync as x}from"node:fs";import{join as r}from"node:path";function u(t="<outputDir> [args...]"){let e=process.argv[2];return e||(console.error(`usage: tsx scripts/${h()} ${t} (e.g. output/www.example.com)`),process.exit(1)),e}function h(){return process.argv[1]?.split("/").pop()??"<script>.ts"}var f=u(),s=r(f,"theme","patterns"),m=r(f,"html");function w(t){return t.replace(/<script\b[\s\S]*?<\/script>/gi," ").replace(/<style\b[\s\S]*?<\/style>/gi," ").replace(/<noscript\b[\s\S]*?<\/noscript>/gi," ").replace(/<[^>]+>/g," ").replace(/\s+/g," ").trim()}function S(t){return[...t.matchAll(/\b(?:alt|title|aria-label)=["']([^"']*)["']/gi)].map(e=>e[1].trim()).filter(Boolean)}var $=t=>t.replace(/\.php$/i,"").replace(/^page-/,""),n=[],i=[];for(let t of x(s).filter(e=>e.endsWith(".php"))){let e=$(t),l=r(m,`${e}.html`);if(!d(l)){i.push(`${t} (no source html/${e}.html)`);continue}let c=g(l,"utf8"),a=w(c);n.push({slug:`${t}`,php:g(r(s,t),"utf8"),spec:{interactionModel:"static",expectedAssets:[],expectedText:[a,...S(c)],bodyText:[a]}})}n.length===0&&(console.error(`no pattern\u2194source-html pairs found under ${s} / ${m}`),process.exit(1));var o=p({patterns:n});console.log(`validated ${n.length} pattern(s); skipped ${i.length}`);for(let t of i)console.log(` skipped: ${t}`);console.log("ok:",o.ok);console.log("errors:",JSON.stringify(o.errors,null,2));console.log("warning count:",o.warnings.length);var y=o.warnings.filter(t=>!/possible non-source content/.test(t.message));console.log("non-word-noise warnings:",JSON.stringify(y,null,2));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have these "dist" files in the repo? can't we use npm dependencies instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems it was done for Claude Code and Codex plugin purposes. See #4011

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@borkweb @nightnei Do you think there's an alternative here? A build script or something or is it mandatory to have the built artifacts?

Alternatively maybe the built artifact should be a separate repo (auto-deployed)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Regardless, it shouldn't block the current PR though)

@wojtekn
wojtekn merged commit c6d45d7 into trunk Jul 23, 2026
21 checks passed
@wojtekn
wojtekn deleted the fix-brace-expansion-dos-advisory branch July 23, 2026 10:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants