Skip to content

npm audit in CI blocks on an unpatched braces advisory (GHSA-vfj7-8cjw-p6xm), failing Markdown Lint on main and every rebuilt branch #470

Closed

Description

@basiliskus

Summary

npm audit in the Markdown Lint job rejects GHSA-vfj7-8cjw-p6xm / CVE-2026-93687
(braces, high severity, published 2026-09-18). The advisory's affected range is
<= 3.0.3 with first_patched_version: null — no fixed release exists, so this
cannot be resolved by upgrading. The job fails before markdownlint runs at all.

This is not a branch-specific problem. It is live on main.

Evidence

Failing step, .github/workflows/ci.yml:109-113:

- name: Install linter dependencies
  working-directory: .github/linters
  run: |
    npm ci
    npm audit --audit-level=moderate

The dependency path, from the job log:

braces  *
Severity: high
braces vulnerable to stack-exhaustion denial of service through deeply nested patterns
node_modules/braces
  micromatch  >=0.2.0
    fast-glob  *
      globby  >=8.0.0
        markdownlint-cli2  >=0.0.5
5 high severity vulnerabilities
##[error]Process completed with exit code 1.

So the chain is braces <- micromatch <- fast-glob <- globby <- markdownlint-cli2, where
markdownlint-cli2 is our pinned 0.23.3 in .github/linters/package.json. 0.23.3 is
the current latest upstream, so there is no newer markdownlint-cli2 to move to either.

Confirmed live on main: main tip f2227413's own Markdown Lint check is
failure, and its job log shows the identical braces / GHSA-vfj7-8cjw-p6xm output under
npm audit --audit-level=moderate.

npm audit fix --force is not a usable remedy — npm's own suggestion is to install
markdownlint-cli2@0.0.4, which it labels a breaking change.

Why it looks intermittent (and why that is misleading)

Open PRs split cleanly by whether their Markdown Lint job has run since the advisory and
with the current lockfile:

PR Markdown Lint job ran
#466 failure 2026-10-05
#468 failure 2026-10-05
#469 failure 2026-10-05
#453 success 2026-09-30
#438 success 2026-10-01
#416 success 2026-10-02
#415 success 2026-09-15
#408 success 2026-10-02
#284 success 2026-10-02

Every green result predates the current failing state; no job that has run recently is
green. The older PRs are not unaffected, only unrerun — any push or re-run should turn
them red too. Treating this as "a few bad PRs" will under-count it.

Also affects the playbook

GSA-TTS/agentic-coding-playbook has the same markdownlint-cli2 0.23.3 pin in
.github/linters/package.json and runs npm audit --audit-level=high
(.github/workflows/ci.yml:96). Its #300 failing job log shows the same braces
advisory. There, the install step dies before ruff ever runs, which is why the two open
ruff dependabot PRs (#300, #301) fail for a reason unrelated to ruff.

Impact

  • main is red on Markdown Lint.
  • Markdown linting is not actually running anywhere — the job dies during install, so
    markdown problems currently pass unchecked. This is the part worth prioritizing: the
    visible symptom is a red check, but the real cost is a silently disabled lint gate.
  • No contributor can get a green Markdown Lint check, so the failure carries no signal and
    trains reviewers to ignore a red check.
  • It interacts badly with review feedback. On #466 a reviewer (correctly) asked the author
    to drop an unrelated local markdownlint shim. That shim had replaced markdownlint-cli2
    and so incidentally removed the globby -> braces path, making the job green. Removing
    the shim re-exposed this pre-existing failure on that branch. Authors will keep
    rediscovering this as if it were their own regression.

What this issue is asking for

A decision, not a specific patch. The options each have real costs and this should be a
maintainer call:

  1. Wait for an upstream fix. Honest, but first_patched_version is null and markdown
    linting stays disabled indefinitely.
  2. Scope the audit so a known-unpatchable transitive dev-only advisory does not gate
    the job (for example auditing only production dependencies, or an explicitly documented
    and time-boxed exception for this GHSA). Keeps the lint gate working; needs care not to
    become a blanket suppression.
  3. Replace or drop markdownlint-cli2 in favour of a tool without the globby
    dependency chain. Note #451 deliberately made .markdownlint-cli2.yaml the single
    source for both CI and pre-commit, so this is not a small change.

Worth stating plainly: options 2 and 3 both weaken or churn a supply-chain control, and
option 1 leaves a lint gate dead. There is no cost-free choice here, which is why it needs
an owner rather than a drive-by fix.

Relevant context for whoever picks this up: this is a stack-exhaustion DoS in a
dev-only, CI-only lint dependency
that processes our own repository's glob patterns, not
untrusted input. That materially lowers the real risk relative to the advisory's "high"
severity rating, and is the main argument for option 2 — but the severity rating is
upstream's call, not ours, and the exception should be documented and revisited rather
than silent.

Provenance: identified while investigating why four ruff dependabot PRs
(GSA-TTS/agentic-coding-patterns#468, #469,
GSA-TTS/agentic-coding-playbook#300, #301) were failing CI. Two distinct causes were
found; ruff 0.16.9 itself is clean. This issue covers the braces cause. The other
cause — each dependabot PR moving only one of two ruff pins, which the
test_ruff_pins_match lockstep guard rejects — is separate and not covered here.

Activity

  1. basiliskus commented on Oct 5, 2026

    @basiliskus
    ContributorAuthor

    This is fixed on the patterns side. Recording what landed, and what is left, before this is closed.

    Fixed by #471 (merged 2026-10-05T21:39:29Z), which took option 3 from the list above — replacing the tool rather than scoping the audit. markdownlint-cli2 0.23.3 → markdownlint-cli@0.49.1, plus a js-yaml override for that package's own GHSA-r3ph-w7gj-g6xm.

    Verified on main (dd22daff):

    • braces is absent from .github/linters/package-lock.json, along with the rest of the globby → fast-glob → micromatch chain.
    • Markdown Lint passes, and the job genuinely re-ran at 21:39 after the merge — this is not a stale green of the kind described above.

    Two things it fixed beyond clearing the advisory, both worth noting here because they were separate concerns raised during review of #466:

    • The non-zero file-count assertion used to parse Linting: N file(s) out of markdownlint-cli2's output, which a shim could satisfy with a constant. It now calls run-markdownlint.mjs --count-only, which globs with the same tinyglobby engine markdownlint-cli uses internally and prints the real matched count.
    • The glob/ignore list lives in one file shared by CI and the pre-commit hook, so the single-source guarantee from #451 survives the move to a CLI that takes globs as arguments rather than config keys.

    The quickstart was fixed in parallel by GSA-TTS/agentic-coding-quickstart#548 (merged 21:36:54Z), same swap.

    The playbook was not, and is still on the vulnerable path. GSA-TTS/agentic-coding-playbook main still pins markdownlint-cli2 0.23.3 and still has braces in its linter lockfile. It currently looks green only because its Python Lint, Test & Validate job last ran on 2026-10-01, before the advisory became blocking; #300 and #301 already fail on it. Because the audit sits in the install step, that job aborts before markdown lint, ruff lint, ruff format, and the validators run — so several gates are silently inactive there, not just one.

    That port is now tracked as GSA-TTS/agentic-coding-playbook#303, so it does not get lost when this issue closes. This one can be closed as fixed for patterns.

    One correction to the body above, for anyone reading back: it says the ruff dual-pin skew was "separate and not covered here," which was right, but it did not mention that the skew is already tracked as #404, open since 2026-09-02. It is not a new or untracked problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions