Skip to content

fix(ci): replace markdownlint-cli2 with markdownlint-cli, drop the shim - #471

Merged
mogul merged 1 commit into
mainfrom
fix/markdownlint-audit
Oct 5, 2026
Merged

mogul merged 1 commit into
mainfrom
fix/markdownlint-audit

Conversation

@mogul

@mogul mogul commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fix the shared Markdown Lint CI failure (npm audit --audit-level=moderate failing on an unpatched braces CVE pulled in transitively by markdownlint-cli2) by replacing markdownlint-cli2 entirely with markdownlint-cli, rather than keeping markdownlint-cli2 as the command surface via a shim.

This supersedes an earlier approach on this same branch (a local markdownlint-cli2 bin-shim package + a hand-rolled recursive file walker reimplementing globby's glob matching). That walker was verified correct for this repo's current **/*.md glob after a close call where I almost "fixed" a symlink-handling difference that was actually correct behavior — which is exactly the risk of reimplementing glob matching by hand instead of using a real glob engine. Simplifying to markdownlint-cli removes that whole class of risk.

Changes

  • .github/linters/package.json: markdownlint-cli2 → [email protected], with a js-yaml override to clear markdownlint-cli's own single moderate transitive CVE (GHSA-r3ph-w7gj-g6xm). Zero vulnerable dependencies in its tree otherwise.
  • .markdownlint-cli2.yaml → .markdownlint.yaml: markdownlint-cli auto-discovers this filename directly, no --config flag needed. Only the rule-config block and frontMatter regex carry over — globs/ignores aren't a markdownlint-cli config-file concept.
  • .github/linters/run-markdownlint.mjs: a ~50-line script holding the glob/ignore list once, shared by CI and the pre-commit hook (same single-source-of-truth guarantee the old globs: key gave us). Supports --fix passthrough and --count-only (prints the matched file count via the same tinyglobby engine markdownlint-cli uses internally, replacing the old Linting: N file(s) stdout-scrape).
  • ci.yml / .pre-commit-config.yaml: both now call node .github/linters/run-markdownlint.mjs instead of npx markdownlint-cli2.

Verification

$ npm ci && npm audit --audit-level=moderate
found 0 vulnerabilities

$ node .github/linters/run-markdownlint.mjs --count-only
190

190 is an exact match to the real [email protected] binary's own reported file count on the pre-existing **/*.md glob — verified directly against the real tool's own output, not a reimplementation, before trusting this number.

$ node .github/linters/run-markdownlint.mjs
(exit 0, clean — matches main's current passing state)

$ pre-commit run markdownlint --hook-stage manual
markdownlint (npm-pinned, --fix)............Passed

$ pre-commit run markdownlint-check --hook-stage manual --all-files
markdownlint (npm-pinned, check only)........Passed

$ python scripts/validate_repo.py
✓ All validators passed

Safety Checklist

  • No secrets, tokens, credentials, or private keys
  • No PII, CUI, customer data, or sensitive info
  • No internal URLs or system details
  • Examples use placeholders where applicable

Security Impact

Removes markdownlint-cli2's entire vulnerable dependency chain (globby → fast-glob → micromatch → braces, plus markdown-it) rather than working around it, and removes a hand-rolled file-selection reimplementation in favor of the glob engine a widely-used, actively maintained CLI already ships and tests.

AI Disclosure

This PR was prepared with OpenCode AI assistance. Human review is required before merge.

@mogul
mogul requested a review from a team as a code owner October 5, 2026 17:39
@mogul
mogul force-pushed the fix/markdownlint-audit branch 2 times, most recently from 549bc62 to 3565c52 Compare October 5, 2026 17:47
@mogul
mogul added this pull request to stack #472 October 5, 2026 17:49
@mogul
mogul force-pushed the fix/markdownlint-audit branch from 3565c52 to a3ac8fe Compare October 5, 2026 17:51
wz-gsa
wz-gsa previously approved these changes Oct 5, 2026

@wz-gsa wz-gsa left a comment

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.

Independently verified in an isolated worktree, not just trusting the PR description:

Security fix confirmed real:

  • npm audit --audit-level=moderate: 6 vulnerabilities (1 moderate, 5 high, including the unpatched CVE-2026-93687 in braces) → 0 vulnerabilities, via a fresh npm ci on the PR branch.

File-selection parity confirmed exact (with a correction on my own part worth recording):

  • I initially suspected the hand-rolled walker in run-markdownlint.mjs double-counted the skills/ → .agents/skills/ symlinked directory, based on a 193 vs 153 file-count mismatch against my own globby reimplementation of the old tool's selection.
  • Before applying a fix, I re-verified against the real markdownlint-cli2 v0.23.3 binary's own direct output rather than my reimplementation. It reports exactly 190 files — matching this PR's unmodified walker exactly (193 total, minus 3 of my own leftover test fixtures from manual testing = 190). My own comparison had used globby's non-default dot:false option, which was the actual error.
  • My proposed lstatSync-based "fix" to skip symlinked directories would have been a regression, silently dropping 36 real skill files (the entire skills//.agents/skills/ tree) from lint coverage. I discarded it before applying anything to the PR.
  • Net: the walker in this PR is correct as submitted. No changes needed.

Also confirmed:

  • Rule-violation detection still works: injected real MD018/MD012 violations into a test file, both caught correctly, exit code 1.
  • Frontmatter-skip behavior preserved: injected malformed YAML frontmatter, correctly left unlinted per the frontMatter regex config.
  • The hand-rolled glob matcher only supports **/*.md and **/*SUFFIX forms, not arbitrary globs — checked .markdownlint-cli2.yaml on main, which only uses the single **/*.md form, so this is not a practical gap for this repo's current config.

Not independently verified: the mkdtempSync + spawnSync-with-generated-JSON-config pattern for injection/race risk. Low-risk given this is an unprivileged CI step with no untrusted input reaching the generated path, but flagging that I didn't do a dedicated check on it.

This correctly unblocks the shared CI failure affecting the whole repo (confirmed independently — this was failing on main itself, not just this PR, before this fix). Approving.

[email protected] pulls a vulnerable braces path through
micromatch/globby (CVE-2026-93687, no patched release available), which
was failing `npm audit --audit-level=moderate` in CI for every PR and
on main itself.

A prior approach (now superseded) kept markdownlint-cli2 as the command
surface via a local bin-shim package plus a hand-rolled recursive file
walker reimplementing globby's glob matching. That walker was verified
correct for this repo's current **/*.md glob, but reimplementing glob
matching by hand is exactly the kind of code most likely to drift from
the real tool's behavior on edge cases (symlinked directories, nested
wildcards, etc.) without anyone noticing until something silently stops
being linted.

This replaces markdownlint-cli2 entirely with [email protected],
which has zero vulnerable transitive dependencies (confirmed via a
fresh `npm ci && npm audit`) and takes globs/ignores directly as CLI
arguments, resolved by its own tinyglobby glob engine rather than any
custom code of ours.

Changes:
- .github/linters/package.json: markdownlint-cli2 -> markdownlint-cli,
  with a js-yaml override to clear markdownlint-cli's own single
  moderate transitive CVE (GHSA-r3ph-w7gj-g6xm).
- .markdownlint-cli2.yaml -> .markdownlint.yaml: markdownlint-cli
  auto-discovers this filename directly, no --config flag needed. Only
  the rule-config block and frontMatter regex carry over; globs/ignores
  are not a markdownlint-cli config concept.
- .github/linters/run-markdownlint.mjs: a ~50-line script holding the
  glob/ignore list once, shared by CI and the pre-commit hook (same
  single-source-of-truth guarantee the old globs: key gave us).
  Supports --fix passthrough and --count-only (prints the matched file
  count via the same tinyglobby engine, for CI's "did this actually
  lint anything" guard, replacing the old `Linting: N file(s)` stdout
  scrape).
- ci.yml / .pre-commit-config.yaml: both now call
  `node .github/linters/run-markdownlint.mjs` instead of
  `npx markdownlint-cli2`.

Verification:
- npm ci && npm audit --audit-level=moderate: 0 vulnerabilities.
- node .github/linters/run-markdownlint.mjs --count-only: 190, an exact
  match to the real [email protected] binary's own reported
  count on the pre-existing **/*.md glob (verified directly against the
  real tool, not a reimplementation, before trusting this number).
- node .github/linters/run-markdownlint.mjs: clean exit 0 against the
  current repo content, matching main's current passing state.
- pre-commit run markdownlint --hook-stage manual (fix) and
  markdownlint-check --hook-stage manual --all-files (check-only): both
  pass.
- make validate: all validators pass.

Supersedes the markdownlint-cli2 shim approach on this same branch.

Co-authored-by: OpenCode Agent <[email protected]>
@wz-gsa
wz-gsa force-pushed the fix/markdownlint-audit branch from a3ac8fe to 6aa576e Compare October 5, 2026 19:43
@wz-gsa wz-gsa changed the title fix(ci): unblock markdownlint audit fix(ci): replace markdownlint-cli2 with markdownlint-cli, drop the shim Oct 5, 2026
@mogul

mogul commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

This will likely get closed; @wz-gsa has a separate PR brewing with a preferable solution.

@mogul

mogul commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

This will likely get closed; @wz-gsa has a separate PR brewing with a preferable solution.

Scratch that, it looks like Will is commandeering this PR!

@mogul

mogul commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Blocker-only adversarial review of current head 6aa576e:

Blockers: none.

Scope reviewed:

  • .github/linters/package.json
  • .github/linters/package-lock.json
  • .github/linters/run-markdownlint.mjs
  • .github/workflows/ci.yml
  • .markdownlint.yaml
  • .pre-commit-config.yaml

Blocker criteria used: only issues that should stop an ASAP merge, such as CI still broken, npm audit still failing, markdown lint silently disabled for meaningful repo content, or a concrete security problem.

Evidence:

  • Subagent adversarial review result: Blockers: none.
  • Current PR checks are green, including Markdown Lint, Python Lint & Test, Pattern Validation, acq-kits Tests, zizmor, and the conventional-title check.
  • The diff removes the vulnerable markdownlint-cli2 dependency path and uses [email protected] with the js-yaml override.
  • The CI count guard now uses run-markdownlint.mjs --count-only, and the normal lint path uses the same centralized glob/ignore list in that runner.

No merge-blocking findings from this review.

@mogul
mogul merged commit dd22daf into main Oct 5, 2026
14 of 21 checks passed
@mogul
mogul deleted the fix/markdownlint-audit branch October 5, 2026 21:39
wz-gsa added a commit that referenced this pull request Oct 6, 2026
…toml) (#478)

## Summary

Two new advisories landed and were GitHub-reviewed **today**, both
reachable through `[email protected]`'s dependency tree (the
version adopted in #471/#548's `markdownlint-cli2` → `markdownlint-cli`
migration):

- `katex` 0.11.0–0.18.1: `CVE-2026-103923` / `GHSA-238p-pmpm-9mq7`, a
prototype-pollution bypass of trust restrictions (low severity). Reached
via `markdownlint` → `micromark-extension-math` → `katex`.
- `smol-toml` ≤1.8.0: `GHSA-r4xh-jqrq-34v2`, a quadratic-time `parse()`
DoS on ordinary valid TOML input (moderate severity). Reached via
`markdownlint-cli` → `smol-toml` directly.

Both have patched releases (`[email protected]`, `[email protected]`) — unlike
the `braces` CVE that prompted the `markdownlint-cli2` →
`markdownlint-cli` migration in the first place (that one had no patched
release at all). This is a straightforward override bump.

## Not caused by this PR, not caused by #471/#548 either

Confirmed independently: this reproduces on current `main`, unmodified,
before making any change:

```text
$ npm ci && npm audit --audit-level=moderate   # on main, as-is
5 vulnerabilities (3 low, 2 moderate)
```

Same "advisory lands, previously-green gate goes red with zero local
changes" pattern as the `braces` CVE #471 fixed — these two were just
GitHub-reviewed hours after #471/#548 merged.

## Fix

Two `overrides` entries added to `.github/linters/package.json`.
`markdownlint-cli` itself stays pinned at `0.49.1`; only its own
transitive `katex` and `smol-toml` resolutions move.

```json
"overrides": {
  "js-yaml": "4.3.2",
  "katex": "0.18.2",
  "smol-toml": "1.9.0"
}
```

## Verification

```text
$ npm ci && npm audit --audit-level=moderate
found 0 vulnerabilities

$ node .github/linters/run-markdownlint.mjs --count-only
190

$ node .github/linters/run-markdownlint.mjs
(exit 0, clean)
```

190 matches the exact file-count parity established in #471.

## Safety Checklist

- [x] No secrets, tokens, credentials, or private keys
- [x] No PII, CUI, customer data, or sensitive info
- [x] No internal URLs or system details
- [x] Examples use placeholders where applicable

## Security Impact

Clears a moderate-severity DoS advisory and a low-severity
prototype-pollution advisory in dev-only, CI-only tooling (markdown
linting). No application runtime impact.

## AI Disclosure

This PR was prepared with OpenCode AI assistance. Human review is
required before merge.

Co-authored-by: OpenCode Agent <[email protected]>
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