Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@
*.jpeg binary
*.webp binary
*.gif binary
*.ico binary
*.ico binary

# Unified patches preserve a single-space prefix on blank context lines.
patches/*.patch whitespace=-blank-at-eol
10 changes: 9 additions & 1 deletion .github/workflows/group-workbench-python-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,12 @@ jobs:

- name: Audit dependencies
working-directory: ${{ matrix.directory }}
run: uvx pip-audit --progress-spinner off ${{ matrix.arguments }}
run: |
if [ -f uv.lock ]; then
requirements=$(mktemp)
trap 'rm -f "$requirements"' EXIT
uv export --frozen --all-groups --no-emit-project --format requirements.txt --output-file "$requirements"
uvx pip-audit --progress-spinner off --no-deps --disable-pip -r "$requirements"
else
uvx pip-audit --progress-spinner off ${{ matrix.arguments }}
fi
7 changes: 4 additions & 3 deletions .github/workflows/project-liminal-drift-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Audit Liminal Drift dependencies
run: pnpm --config.registry=https://registry.npmjs.org/ --filter liminal-drift --fail-if-no-match audit --audit-level high

- name: Check Liminal Drift
run: pnpm --filter liminal-drift check

Expand Down Expand Up @@ -123,3 +120,7 @@ jobs:
path: apps/liminal-drift/artifacts/
if-no-files-found: error
retention-days: 7

- name: Audit Liminal Drift dependencies
if: ${{ !cancelled() }}
run: pnpm --config.registry=https://registry.npmjs.org/ --filter liminal-drift --fail-if-no-match audit --audit-level high
7 changes: 4 additions & 3 deletions .github/workflows/project-youtube-auto-resume-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ jobs:
BASE_REF: ${{ github.event.before }}
run: pnpm --filter @cedarflake/youtube-auto-resume --fail-if-no-match version:check --base-ref="$BASE_REF"

- name: Audit YouTube userscript dependencies
run: pnpm --config.registry=https://registry.npmjs.org/ --filter @cedarflake/youtube-auto-resume --fail-if-no-match audit --audit-level high

- name: Install Playwright browser
run: pnpm --dir others/userscripts/youtube-auto-resume exec playwright install --with-deps chromium

- name: Check YouTube userscript
run: pnpm --filter @cedarflake/youtube-auto-resume --fail-if-no-match check

- name: Audit YouTube userscript dependencies
if: ${{ !cancelled() }}
run: pnpm --config.registry=https://registry.npmjs.org/ --filter @cedarflake/youtube-auto-resume --fail-if-no-match audit --audit-level high
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ jobs:
- name: Install dependencies
run: pnpm --filter @cedarflake/youtube-auto-resume-extension --fail-if-no-match install --frozen-lockfile

- name: Audit extension dependencies
run: pnpm --config.registry=https://registry.npmjs.org/ --filter @cedarflake/youtube-auto-resume-extension --fail-if-no-match audit --audit-level high

- name: Check extension
run: pnpm --filter @cedarflake/youtube-auto-resume-extension --fail-if-no-match check

Expand All @@ -62,3 +59,7 @@ jobs:

- name: Test extension runtime
run: pnpm --filter @cedarflake/youtube-auto-resume-extension --fail-if-no-match test:e2e

- name: Audit extension dependencies
if: ${{ !cancelled() }}
run: pnpm --config.registry=https://registry.npmjs.org/ --filter @cedarflake/youtube-auto-resume-extension --fail-if-no-match audit --audit-level high
2 changes: 1 addition & 1 deletion apps/personal-email/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@react-email/components": "1.0.10",
"@react-email/render": "^2.0.0",
"dotenv": "^16.0.0",
"nodemailer": "^9.1.1",
"nodemailer": "^10.0.9",
"prompts": "^2.4.2",
"react": "^19.2.4",
"react-dom": "^19.2.4"
Expand Down
6 changes: 3 additions & 3 deletions apps/shika/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"db:check": "drizzle-kit check --config=drizzle.config.ts",
"db:migrate:local": "tsx ./scripts/migrate-local.ts",
"db:migrate": "drizzle-kit migrate --config=drizzle.remote.config.ts",
"check": "pnpm lint && pnpm typecheck && pnpm test && pnpm db:check"
"check": "pnpm --workspace-root check:dependency-security && pnpm lint && pnpm typecheck && pnpm test && pnpm db:check"
},
"dependencies": {
"@better-auth/drizzle-adapter": "1.6.22",
Expand All @@ -24,7 +24,7 @@
"drizzle-orm": "0.45.2",
"kysely": "0.28.17",
"lucide-react": "^1.23.0",
"next": "16.3.3",
"next": "16.3.8",
"next-intl": "4.13.1",
"react": "19.2.4",
"react-dom": "19.2.4",
Expand All @@ -38,7 +38,7 @@
"babel-plugin-react-compiler": "1.0.0",
"drizzle-kit": "0.31.10",
"eslint": "^9",
"eslint-config-next": "16.3.3",
"eslint-config-next": "16.3.8",
"tailwindcss": "^4",
"tsx": "4.22.4",
"typescript": "^5"
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@
"render:email": "pnpm --filter personal_email render:email",
"check:focus-orb-package": "pnpm --filter @cedarflake/focus-orb pack:check && pnpm --filter @cedarflake/focus-orb-demo exec tsc -p package-consumer/tsconfig.json --noEmit",
"test:repository-contract": "tsx --test scripts/repository-contract/*.test.ts",
"typecheck:repository-contract": "tsc -p tsconfig.repository-contract.json --noEmit"
"typecheck:repository-contract": "tsc -p tsconfig.repository-contract.json --noEmit",
"check:dependency-security": "node scripts/checkBracesSecurity.cjs"
},
"devDependencies": {
"@types/node": "22.10.5",
"@vitejs/plugin-react": "4.3.4",
"js-yaml": "5.2.2",
"smol-toml": "1.7.1",
"js-yaml": "5.4.1",
"smol-toml": "1.9.0",
"tsx": "4.22.4",
"typescript": "5.7.3",
"vite": "6.4.3"
Expand Down
36 changes: 36 additions & 0 deletions patches/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Dependency security patches

## braces 3.0.3

`braces@3.0.3.patch` is a local mitigation for
[GHSA-vfj7-8cjw-p6xm](https://github.com/advisories/GHSA-vfj7-8cjw-p6xm), following
the parser-depth mitigation described in [upstream issue #70](https://github.com/micromatch/braces/issues/70).
It is not an upstream release or backport. At the time of this change, the
advisory names 3.0.4 as fixed, but that version is unavailable from npm.

The parser rejects more than 100 combined brace/parenthesis nesting levels.
The compile, expand, and stringify walkers enforce the same bound for callers
that provide an AST directly. Escaped, quoted, and bracketed delimiters remain
literal. Normal inputs, existing length/range limits, and ordinary glob behavior
are preserved; deeply nested valid patterns now throw a controlled `SyntaxError`.
Callers must still handle invalid-input errors. This does not change unrelated
expansion-size behavior.

Run `pnpm check:dependency-security` against the actual installed dependency.
Shika's `check` includes this guard because its ESLint dependency tree owns the
patched package. The guard covers the depth boundary, malformed/mixed nesting,
direct and cyclic AST input, literal delimiters, and ordinary expansion behavior.
It fails if the patch is lost. The optional second package-directory argument
runs seeded differential tests against an unpatched 3.0.3 copy.

The raw package audit still reports this advisory because the installed version
remains 3.0.3. No audit waiver is configured. Browser workflows run their audit
last so functional verification can complete while the audit remains a failing
gate. Replace the local patch with a verified compatible upstream release once
available, rerun the regression and owning checks, then remove this patch entry.

## brace-expansion 5.0.12

`brace-expansion@5.0.12.patch` preserves the existing CommonJS/default-export
compatibility shim used by minimatch 3 and 9 while updating the upstream package
to its security-fixed release. It does not alter the package's expansion logic.
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
diff --git a/dist/commonjs/index.d.ts b/dist/commonjs/index.d.ts
index f3e2de9d87e1ce462517e49f35733bed8bdf85af..6c84d87835182d0670981dc15f488c2a7d061c98 100644
index 1d86e5ec52083b00be390eefad66a03f54c1bffa..8d4bc13621590d805a7dfa978e8cf008a00e7330 100644
--- a/dist/commonjs/index.d.ts
+++ b/dist/commonjs/index.d.ts
@@ -5,4 +5,5 @@ export type BraceExpansionOptions = {
maxLength?: number;
@@ -9,4 +9,5 @@ export type BraceExpansionOptions = {
maxRewrites?: number;
};
export declare function expand(str: string, options?: BraceExpansionOptions): string[];
+export default expand;
//# sourceMappingURL=index.d.ts.map
\ No newline at end of file
diff --git a/dist/commonjs/index.js b/dist/commonjs/index.js
index 869a6bee23807b9f01c18c99ab8e952b4b242f97..985fc869022c17e31a0371ee35a339c6db65528d 100644
index 48cf0d3dabc885249c65909c4912712c834786f5..63babbb7452c79267fcd4707d464d6010124df5e 100644
--- a/dist/commonjs/index.js
+++ b/dist/commonjs/index.js
@@ -286,4 +286,5 @@ function expand_(str, max, maxLength, isTop) {
@@ -330,4 +330,5 @@ function expand_(str, max, maxLength, maxDepth, depth, maxRewrites, isTop) {
}
return acc;
}
+module.exports = Object.assign(expand, exports, { default: expand });
//# sourceMappingURL=index.js.map
\ No newline at end of file
diff --git a/dist/esm/index.d.ts b/dist/esm/index.d.ts
index f3e2de9d87e1ce462517e49f35733bed8bdf85af..6c84d87835182d0670981dc15f488c2a7d061c98 100644
index 1d86e5ec52083b00be390eefad66a03f54c1bffa..8d4bc13621590d805a7dfa978e8cf008a00e7330 100644
--- a/dist/esm/index.d.ts
+++ b/dist/esm/index.d.ts
@@ -5,4 +5,5 @@ export type BraceExpansionOptions = {
maxLength?: number;
@@ -9,4 +9,5 @@ export type BraceExpansionOptions = {
maxRewrites?: number;
};
export declare function expand(str: string, options?: BraceExpansionOptions): string[];
+export default expand;
//# sourceMappingURL=index.d.ts.map
\ No newline at end of file
diff --git a/dist/esm/index.js b/dist/esm/index.js
index fd68f57029207ac1bcafe7fb1c14ad5305b3ffa4..50a837105e22d965e9008dc05e62e448f69dd99a 100644
index 0e0cc962307eb697dc8139ef4c1f86b82380e5cc..dbce6953f0abf8266d3380ca5382abed2531f9fb 100644
--- a/dist/esm/index.js
+++ b/dist/esm/index.js
@@ -282,4 +282,5 @@ function expand_(str, max, maxLength, isTop) {
@@ -326,4 +326,5 @@ function expand_(str, max, maxLength, maxDepth, depth, maxRewrites, isTop) {
}
return acc;
}
Expand Down
133 changes: 133 additions & 0 deletions patches/braces@3.0.3.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
diff --git a/lib/compile.js b/lib/compile.js
index dce69beb90ece5c3b0ca234a949418f9f34eedbc..9058a4418641fde86525445c97945c959f53c7a8 100644
--- a/lib/compile.js
+++ b/lib/compile.js
@@ -2,9 +2,15 @@

const fill = require('fill-range');
const utils = require('./utils');
+const { MAX_DEPTH } = require('./constants');

const compile = (ast, options = {}) => {
- const walk = (node, parent = {}) => {
+ const walk = (node, parent = {}, depth = 0) => {
+ // Guard callers that pass an AST directly instead of using parse().
+ if (node.nodes && depth > MAX_DEPTH) {
+ throw new SyntaxError(`Nesting depth exceeds max depth (${MAX_DEPTH})`);
+ }
+
const invalidBlock = utils.isInvalidBrace(parent);
const invalidNode = node.invalid === true && options.escapeInvalid === true;
const invalid = invalidBlock === true || invalidNode === true;
@@ -47,7 +53,7 @@ const compile = (ast, options = {}) => {

if (node.nodes) {
for (const child of node.nodes) {
- output += walk(child, node);
+ output += walk(child, node, depth + 1);
}
}

diff --git a/lib/constants.js b/lib/constants.js
index 2bb3b8840382c56700869b3a9e1f958fcce01efd..494ce390a6786cfa4a66b91cc286c2b3ddd10ac9 100644
--- a/lib/constants.js
+++ b/lib/constants.js
@@ -2,6 +2,8 @@

module.exports = {
MAX_LENGTH: 10000,
+ // Bound recursive walkers, including mixed brace/parenthesis nesting.
+ MAX_DEPTH: 100,

// Digits
CHAR_0: '0', /* 0 */
diff --git a/lib/expand.js b/lib/expand.js
index 35b2c41d6afdb8f0197959e26079f266e48370d3..7e77fdb42898add58b7b8de3259b2faa9fd1fd0b 100644
--- a/lib/expand.js
+++ b/lib/expand.js
@@ -3,6 +3,7 @@
const fill = require('fill-range');
const stringify = require('./stringify');
const utils = require('./utils');
+const { MAX_DEPTH } = require('./constants');

const append = (queue = '', stash = '', enclose = false) => {
const result = [];
@@ -33,7 +34,12 @@ const append = (queue = '', stash = '', enclose = false) => {
const expand = (ast, options = {}) => {
const rangeLimit = options.rangeLimit === undefined ? 1000 : options.rangeLimit;

- const walk = (node, parent = {}) => {
+ const walk = (node, parent = {}, depth = 0) => {
+ // Guard callers that pass an AST directly instead of using parse().
+ if (node.nodes && depth > MAX_DEPTH) {
+ throw new SyntaxError(`Nesting depth exceeds max depth (${MAX_DEPTH})`);
+ }
+
node.queue = [];

let p = parent;
@@ -100,7 +106,7 @@ const expand = (ast, options = {}) => {
}

if (child.nodes) {
- walk(child, node);
+ walk(child, node, depth + 1);
}
}

diff --git a/lib/parse.js b/lib/parse.js
index 3a6988e629f52aee6609dc5ec290a5e9ff47cecb..8f1cb61f4689107f46d4c97c3c7a8cab8783af5c 100644
--- a/lib/parse.js
+++ b/lib/parse.js
@@ -8,6 +8,7 @@ const stringify = require('./stringify');

const {
MAX_LENGTH,
+ MAX_DEPTH,
CHAR_BACKSLASH, /* \ */
CHAR_BACKTICK, /* ` */
CHAR_COMMA, /* , */
@@ -138,6 +139,12 @@ const parse = (input, options = {}) => {
continue;
}

+ // Check the actual parser stack, since parentheses also create AST nodes.
+ // Escaped, quoted and bracketed delimiters remain literal and do not count.
+ if ((value === CHAR_LEFT_PARENTHESES || value === CHAR_LEFT_CURLY_BRACE) && stack.length > MAX_DEPTH) {
+ throw new SyntaxError(`Nesting depth exceeds max depth (${MAX_DEPTH})`);
+ }
+
/**
* Parentheses
*/
diff --git a/lib/stringify.js b/lib/stringify.js
index 8bcf872c31894f81a8c64d9202fd607e4fd7ea2b..589eaa8a95d412eaed91b1846382db9460c89a27 100644
--- a/lib/stringify.js
+++ b/lib/stringify.js
@@ -1,9 +1,15 @@
'use strict';

const utils = require('./utils');
+const { MAX_DEPTH } = require('./constants');

module.exports = (ast, options = {}) => {
- const stringify = (node, parent = {}) => {
+ const stringify = (node, parent = {}, depth = 0) => {
+ // Guard callers that pass an AST directly instead of using parse().
+ if (node.nodes && depth > MAX_DEPTH) {
+ throw new SyntaxError(`Nesting depth exceeds max depth (${MAX_DEPTH})`);
+ }
+
const invalidBlock = options.escapeInvalid && utils.isInvalidBrace(parent);
const invalidNode = node.invalid === true && options.escapeInvalid === true;
let output = '';
@@ -21,7 +27,7 @@ module.exports = (ast, options = {}) => {

if (node.nodes) {
for (const child of node.nodes) {
- output += stringify(child);
+ output += stringify(child, {}, depth + 1);
}
}
return output;
Loading
Loading