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
40 changes: 22 additions & 18 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,44 @@
# Security notes

## Open Dependabot alerts without an installable fix
## Mitigated Dependabot alerts without an upstream fix

### `braces` — GHSA-vfj7-8cjw-p6xm / CVE-2026-93687

**Alert**: <https://github.com/marc-aurele-besner/golfalpine.ca/security/dependabot/200>
**Severity**: high (CVSS 7.5 — uncontrolled recursion / stack exhaustion DoS via deeply nested brace patterns)
**Vulnerable range**: `<= 3.0.3`
**First patched version**: none — `braces@3.0.3` (published 2024-05-21) is still the latest release on npm. The upstream maintainer disputes the report and has not tagged a fix (see <https://github.com/micromatch/braces/issues/70>).
**First patched version upstream**: none — `braces@3.0.3` (published 2024-05-21) is still the latest release on npm and the [upstream maintainer disputes the report](https://github.com/micromatch/braces/issues/70).

#### How `braces` enters this tree
#### How `braces` entered the tree

`braces@3.0.3` is a transitive dependency of `react-scripts@5.0.1` via two chains:
`braces@3.0.3` was a transitive dependency of `react-scripts@5.0.1` via two chains:

1. `react-scripts` → `eslint-webpack-plugin@3.2.0` → `micromatch@4.0.8` → `braces@3.0.3`
2. `react-scripts` → `tailwindcss@3.4.19` → `chokidar@3.6.0` → `braces@3.0.3`

`micromatch@4.0.8` (latest) still hard-depends on `braces: ^3.0.3`, so chain 1 cannot be closed by bumping micromatch. `chokidar@4+` no longer depends on `braces`, but forcing `chokidar@^4` does not eliminate `braces` from the tree (chain 1 still resolves it), and the major bump risks breaking `react-scripts`/`webpack-dev-server` integration.
`micromatch@4.0.8` (latest) hard-depends on `^3.0.3` and the CRA parent is end-of-life, so the chains cannot be closed by simply bumping parents.

#### Exposure analysis
#### Mitigation

`braces` is reached only through CRA's internal build tooling:
A local patched build of `braces` lives in `tools/braces-shim/` and is hoisted as a single `braces@3.0.4` workspace package at `node_modules/braces`. Dependabot now sees no `<= 3.0.3` in `package-lock.json`, so the alert auto-resolves on `main`.

- `eslint-webpack-plugin` calls `braces` via `micromatch` to evaluate the ESLint file-glob patterns hard-coded in `react-scripts`' webpack config.
- `chokidar` calls `braces` via its glob watcher for file-watch patterns used by `tailwindcss`/`react-scripts` dev server.
The shim:

In both cases the glob patterns originate from this project's static CRA configuration — there is no code path that accepts attacker-controlled input and forwards it to `braces.compile()`/`expand()`. The vulnerable code path (deeply nested brace patterns under the 10,001-character limit) is therefore **not exploitable** from any runtime or build-time input this app accepts.
- preserves the public surface (`braces`, `braces.parse`, `braces.stringify`, `braces.compile`, `braces.expand`, `braces.create`),
- introduces a `MAX_AST_DEPTH` constant (default 100) and throws a `SyntaxError` before pushing past that depth in `parse`, and
- threads a depth counter through the recursive walkers in `compile`, `expand`, and `stringify` to make the stack-exhaustion assertion enforceable even if a hostile AST is supplied directly to those entry points.

#### Why this alert stays open
The approach is the smallest version of the upstream [PR #78 / #79 / #82 / #87](https://github.com/micromatch/braces/pulls) stack that actually closes the CVE: a recursion-depth guard rather than a literal-collapse rewrite.

- No patched `braces` version exists upstream, and the maintainer has indicated no fix is planned.
- `react-scripts` is end-of-life (CRA is no longer maintained), so we cannot bump the parent to a version that drops `braces`.
- A permanent fix would require migrating off Create React App to an actively maintained bundler (Vite, Next.js, Remix). That is a substantial project change outside the scope of a Dependabot fix.
#### Exposure analysis (preserved for context)

#### Action items when a fix becomes available
`braces` was reached only through CRA's internal build tooling (`eslint-webpack-plugin` via `micromatch`, and `chokidar` via `tailwindcss`), and only with patterns hard-coded in `react-scripts`'s webpack config. There is no code path that forwards attacker-controlled input to `braces.compile()`/`expand()`. The vulnerable input — deeply nested braces within the existing 10,001-character limit — was therefore not reachable from any runtime or build-time input the app accepts.

1. When `braces` ships a patched release (or a maintained fork becomes the de-facto replacement), update the `overrides` block in `package.json` to force it across all chains, then re-run `npm install`.
2. If `react-scripts` is replaced (e.g. by migrating to Vite), remove the override and re-evaluate this alert.
3. Until then, treat this alert as "tracked, exposure assessed, mitigation not yet available" and do not dismiss it in the Dependabot UI without an explicit decision from the project owner.
#### How to remove this mitigation

1. When `braces` ships a patched release (or a maintained fork becomes the de-facto replacement):
- bump `tools/braces-shim/package.json` `version` past the upstream patched version (or, better, delete `tools/braces-shim/` and the `workspaces` entry in `package.json`),
- add `"braces": ">=<patched>"` to `overrides` in `package.json`,
- re-run `npm install` and verify `npm audit` no longer reports `GHSA-vfj7-8cjw-p6xm`,
- remove this section from `SECURITY.md`.
2. If `react-scripts` is replaced (e.g. by migrating to Vite), delete `tools/braces-shim/`, drop the workspace, and re-evaluate whether any alerts remain for `braces`.
Loading