{"area": "package.json", "auditor": "audit", "body": "## Problem\n\nEvery package this meta-package depends on declares `\"postinstall\": \"node scripts/postinstall.cjs\"`. Current package managers no longer run dependency lifecycle scripts by default. pnpm 12 turns the ignored script into a hard install error (`ERR_PNPM_IGNORED_BUILDS`), so adding or installing `@ivuorinen/base-configs` fails outright; Yarn 4 skips the scripts silently, so none of the starter config files the individual READMEs promise is ever written.\n\n## Evidence\n\nConsumer installs in empty scratch projects (npm 11.19.0, pnpm 12.4.1, Yarn 4.18.0, Node 24.21.0):\n\n```text\n$ pnpm add -D @ivuorinen/base-configs\nexit 1\nError: ERR_PNPM_IGNORED_BUILDS\n  ╰─▶ Ignored build scripts: @ivuorinen/browserslist-config 1.3.22, @ivuorinen/commitlint-config 2.0.2,\n      @ivuorinen/eslint-config 1.5.17, @ivuorinen/markdownlint-config 1.3.12, @ivuorinen/prettier-config 2.0.12,\n      @ivuorinen/semantic-release-config 2.1.0, @ivuorinen/stylelint-config ...\n\n$ CI=true pnpm install --frozen-lockfile     # same project, lockfile present\nexit 1  ERR_PNPM_IGNORED_BUILDS\n\n$ pnpm add -D prettier                       # control: no build script\nexit 0\n```\n\nYarn 4: `YN0004: … lists build scripts, but all build scripts have been disabled.` — exit 0, no config file created. npm 11.19: still runs the script but warns `install-scripts not yet covered by allowScripts`.\n\n## Impact\n\nEvery pnpm 12 consumer's install — including CI with a frozen lockfile — fails until they discover `pnpm approve-builds` or `onlyBuiltDependencies`. Yarn 4 consumers get no configs and no error, so their tools run on defaults while they believe the shared config is active.\n\n## Fix\n\nThe root cause and fix live in each config package (see the matching findings in the seven `base-configs-*` repos): stop relying on `postinstall` — ship an explicit init command (e.g. a `bin` such as `npx @ivuorinen/prettier-config init`) and document it. Until then, document in this README that pnpm users must add the seven packages to `pnpm.onlyBuiltDependencies` (or run `pnpm approve-builds`) and that Yarn 4 users must create the config files by hand.\n\n## Resolution\nOwner chose the document-only fix. README now states the Node floor and how each package manager treats the configs' postinstall: npm runs it, Yarn 4 does not, pnpm fails until approved. The pnpm instruction was verified on pnpm 12.4.1: `pnpm approve-builds --all` writes an `allowBuilds` map to pnpm-workspace.yaml, after which `CI=true pnpm install --frozen-lockfile` exits 0 (the `onlyBuiltDependencies` key no longer works on pnpm 12 and is not documented). Root cause stays in each config package by owner decision.\n", "category": "reliability", "found": "2026-09-25", "id": "audit-9f1c6ad4", "resolved": "2026-09-25", "severity": "high", "status": "fixed", "title": "Installing base-configs with pnpm 12 fails: all seven configs ship an unapproved postinstall"}
{"area": "package.json", "auditor": "contract", "body": "## Problem\n\nDeclared-surface drift. `package.json` declares `\"engines\": { \"node\": \">=20\" }`, but the production dependency closure this meta-package exists to deliver cannot run on most of that range. Every Node 20 release and every Node 22 release before 22.19.0 is excluded by at least one dependency's own `engines` field, as is Node 24 before 24.10.0.\n\n## Evidence\n\nFresh `yarn install --immutable --mode=skip-build` of the committed `package.json` + `yarn.lock`, then every package in the `dependencies` closure (723 packages) checked against candidate Node versions with `semver.satisfies`:\n\n```text\nversions satisfying the whole closure: 22.19.0, 22.22.2, 24.10.0, 24.15.0, 26.0.0\nnode 20.20.2 blocked by: @ivuorinen/commitlint-config 2.0.0 (>=22.12.0); @commitlint/config-conventional 21.2.2 (>=22.12.0); markdownlint-cli 0.49.0 (>=22); @semantic-release/github 12.0.8 (^22.14.0 || >= 24.10.0)\nnode 22.12.0 blocked by: eslint 10.5.0 (^20.19.0 || ^22.13.0 || >=24); @semantic-release/github 12.0.8 (^22.14.0 || >= 24.10.0)\nnode 22.14.0 blocked by: undici 8.10.0 (>=22.19.0)\nnode 24.10.0 blocked by: nothing\n```\n\n`yarn.lock` resolves `@ivuorinen/commitlint-config` to 2.0.0 via 721b4ee (#569), released as base-configs v4.0.0 without touching `engines`.\n\n## Impact\n\nA consumer on Node 20.x, 22.0-22.18 or 24.0-24.9 is told by the manifest that it is supported, installs without a hard error (Yarn ignores engines; npm only warns `EBADENGINE`), and then runs commitlint, semantic-release and markdownlint versions that do not support that runtime.\n\n## Fix\n\nSet `\"engines\": { \"node\": \"^22.19.0 || >=24.10.0\" }` — the intersection of the dependencies' own floors — and say in the next release notes that the effective floor already rose in v4.0.0.\n\n## Resolution\nOwner chose (s)pec: engines.node set to \"^22.19.0 || >=24.10.0\"; README and CLAUDE.md state the same floor.\n", "category": "correctness", "found": "2026-09-25", "id": "contract-9f44a52c", "resolved": "2026-09-25", "severity": "high", "status": "fixed", "title": "engines promises Node >=20 but the bundled toolchain needs ^22.19.0 || >=24.10.0"}
{"area": "yarn.lock", "auditor": "security", "body": "## Problem\n\nDependency vulnerability. `yarn.lock` resolves `smol-toml` 1.6.1, affected by GHSA-7w5x-hrqm-74c2 / CVE-2026-85730 (High, fixed in 1.7.1).\n\n## Evidence\n\nDetected by osv-scanner 2.6.0 (`GHSA-7w5x-hrqm-74c2`, HIGH), grype 0.118.0 (`High`, fix `1.7.1`) and `yarn npm audit --all --recursive` (advisory 1193945, `vulnerable: <=1.7.0`, tree version `1.6.1`).\n\nThe same advisory fails MegaLinter's osv-scanner and trivy steps in the `Lint` job of `publish.yml`: `gh run view 33985615249` shows `❌ REPOSITORY | osv-scanner | … | 1 error` and `❌ REPOSITORY | trivy | … | 1 error`. `Publish` has `needs: [Linter]`, and the last three `main` runs (cf52ed7, cdeafcc, 6ee9ca2) are all `failure`.\n\n## Impact\n\nBeyond the vulnerable parser in the dev/CI tree, the advisory currently blocks the release pipeline: nothing merged since cf52ed7 publishes until the lockfile is fixed.\n\n## Fix\n\nRaise the resolution and refresh the lockfile:\n\n```sh\nyarn up -R smol-toml\n```\n\nIf the parent's range caps it below 1.7.1, add `\"smol-toml\": \"^1.7.1\"` to `resolutions`, then `yarn install`. Re-run `osv-scanner scan source --lockfile=yarn.lock` to confirm.\n\n## Resolution\nsmol-toml was pinned ~1.6.1 by markdownlint-cli 0.49.0. `yarn up -R @ivuorinen/markdownlint-config` moved it within the declared ^1.1.3 range to 1.3.12 (markdownlint-cli 0.49.1), resolving smol-toml 1.7.2. osv-scanner: No issues found.\n", "category": "security", "found": "2026-09-25", "id": "security-1ca83a72", "resolved": "2026-09-25", "severity": "high", "status": "fixed", "title": "smol-toml 1.6.1 is vulnerable to GHSA-7w5x-hrqm-74c2 (High) and blocks every release"}
{"area": "yarn.lock", "auditor": "security", "body": "## Problem\n\nDependency vulnerability. `yarn.lock` resolves `colord` 2.9.3, affected by GHSA-2wm5-q62r-hmrv / CVE-2026-85062 (Moderate, fixed in 2.9.4).\n\n## Evidence\n\nosv-scanner (`GHSA-2wm5-q62r-hmrv`, MODERATE), grype (`Medium`, fix `2.9.4`), `yarn npm audit` (advisory 1193675, `vulnerable: <2.9.4`, tree `2.9.3`). Pulled in through the stylelint toolchain of `@ivuorinen/stylelint-config`.\n\n## Impact\n\nVulnerable colour parser in the dev/CI tree; it also contributes to the MegaLinter osv-scanner failure that blocks `publish.yml`.\n\n## Fix\n\n`yarn up -R colord` (or `resolutions: { \"colord\": \"^2.9.4\" }`), then `yarn install` and re-scan.\n\n## Resolution\nyarn up -R colord: now 2.10.0. osv-scanner: No issues found.\n", "category": "security", "found": "2026-09-25", "id": "security-ab0b6100", "resolved": "2026-09-25", "severity": "medium", "status": "fixed", "title": "colord 2.9.3 is vulnerable to GHSA-2wm5-q62r-hmrv (Moderate)"}
{"area": "yarn.lock", "auditor": "security", "body": "## Problem\n\nDependency vulnerability. `yarn.lock` resolves `postcss-selector-parser` 7.1.1, affected by GHSA-w9m9-85wc-3x92 / CVE-2026-9358 (Low, fixed in 7.1.3).\n\n## Evidence\n\nosv-scanner (`GHSA-w9m9-85wc-3x92`, LOW), grype (`Low`, fix `7.1.3`), `yarn npm audit` (advisory 1153169, `vulnerable: >=7.1.0 <7.1.3`, tree `7.1.1`).\n\n## Impact\n\nLow-severity parser issue in the dev/CI tree; counted in the osv-scanner failure that blocks publishing.\n\n## Fix\n\n`yarn up -R postcss-selector-parser` (or `resolutions: { \"postcss-selector-parser\": \"^7.1.3\" }`), then `yarn install` and re-scan.\n\n## Resolution\nyarn up -R postcss-selector-parser: now 7.1.6. osv-scanner: No issues found.\n", "category": "security", "found": "2026-09-25", "id": "security-6a76dfb1", "resolved": "2026-09-25", "severity": "low", "status": "fixed", "title": "postcss-selector-parser 7.1.1 is vulnerable to GHSA-w9m9-85wc-3x92 (Low)"}
{"area": "package.json", "auditor": "audit", "body": "## Problem\n\n`package.json` runs `./node_modules/.bin/commitlint` (script `commitlint`, line 42) and `.simple-git-hooks.json` wires it as the `commit-msg` hook, but no manifest declares `@commitlint/cli`. It used to arrive transitively through `@ivuorinen/commitlint-config` 1.x. Commit 721b4ee (#569, `chore(deps)!: update dependency @ivuorinen/commitlint-config (1.0.32 → 2.0.0)`) moved to 2.0.0, which declares `@commitlint/cli` only as an optional peer, so nothing installs it any more.\n\n## Evidence\n\n```text\n$ grep -c '^\"@commitlint/cli@' yarn.lock\n0\n```\n\nFresh `yarn install --immutable --mode=skip-build` of the committed `package.json` + `yarn.lock` in a scratch directory:\n\n```text\nnode_modules/.bin/commitlint: MISSING\n$ printf 'feat: test\\n' > msg; yarn commitlint --edit msg\nexit 127\n```\n\nThis clone still works only because its `node_modules` predates the bump (it holds `@ivuorinen/commitlint-config` 1.0.32 while the lockfile pins 2.0.0).\n\nSeparately, Yarn 4.18 defaults `enableScripts: false` (`yarn config get enableScripts` → `false`; a probe install logs `YN0004: simple-git-hooks lists build scripts, but all build scripts have been disabled`), so `simple-git-hooks`' own postinstall never writes `.git/hooks/commit-msg` on a fresh clone either. `CLAUDE.md` states commit messages are \"enforced by commitlint via a simple-git-hooks commit-msg hook\".\n\n## Impact\n\nTwo failure modes, both silent until hit. A clone that already has the hook installed aborts every `git commit` with exit 127 after its next clean install. A fresh clone never gets the hook, so Conventional Commits — which decide this package's semver bump through semantic-release — are not checked locally at all, contrary to CLAUDE.md.\n\n## Fix\n\nDeclare the CLI the hook runs and install the hook explicitly:\n\n```json\n\"devDependencies\": {\n  \"@commitlint/cli\": \"^21.2.3\",\n  ...\n},\n\"scripts\": {\n  \"prepare\": \"simple-git-hooks\",\n  ...\n}\n```\n\nThen `yarn install` to update `yarn.lock`, and run `yarn simple-git-hooks` once in existing clones. Root `prepare` runs even with `enableScripts: false` because it is the project's own script, not a dependency's.\n\n## Resolution\nDeclared \"@commitlint/cli\": \"^21.2.3\" in devDependencies. Fresh-clone simulation (committed package.json + yarn.lock, `yarn install --immutable --mode=skip-build`): commitlint bin present; after `yarn simple-git-hooks`, `git commit -m 'bad message'` exits 1 and `git commit -m 'feat: good'` exits 0. The finding's suggested `prepare` script was wrong: Yarn 4 does not run root `prepare` on install (verified), and a root `postinstall` would also run in npm consumers where simple-git-hooks is absent. CLAUDE.md now documents the one-time `yarn simple-git-hooks` step instead.\n", "category": "correctness", "found": "2026-09-25", "id": "audit-e563b2b2", "resolved": "2026-09-25", "severity": "medium", "status": "fixed", "title": "commitlint CLI is undeclared, so the commit-msg hook exits 127 or is never installed"}
{"area": ".github/workflows/publish.yml", "auditor": "ci", "body": "## Problem\n\n`publish.yml:86-89` passes `extra_plugins: @semantic-release/changelog @semantic-release/github @semantic-release/npm` to `cycjimmy/semantic-release-action`. The action installs these with `npm install` at run time, unversioned — the latest tag at that moment, with npm lifecycle scripts enabled — inside the job that holds `secrets.PAT` and `id-token: write`. None of them is needed: `.releaserc.json` extends `@ivuorinen/semantic-release-config`, whose plugin chain is commit-analyzer, release-notes-generator, npm and github (all shipped inside `semantic-release` itself), and `@semantic-release/changelog` appears in no config at all.\n\n## Evidence\n\n```yaml\n# .github/workflows/publish.yml:83-91\n      - name: Semantic Release\n        uses: cycjimmy/semantic-release-action@b12c8f6015dc215fe37bc154d4ad456dd3833c90 # v6.0.0\n        with:\n          extra_plugins: |\n            @semantic-release/changelog\n            @semantic-release/github\n            @semantic-release/npm\n        env:\n          GITHUB_TOKEN: ${{ secrets.PAT }}\n```\n\n`base-configs-semantic-release/index.cjs` plugins: `@semantic-release/commit-analyzer`, `@semantic-release/release-notes-generator`, `@semantic-release/npm`, `@semantic-release/github`. No repo in the workspace lists `@semantic-release/changelog` in a release config. None of the sibling repos' `publish.yml` passes `extra_plugins`.\n\n## Impact\n\nEvery release executes whatever versions of three packages npm serves that minute, with install scripts, in a job that can mint an OIDC token npm's trusted publisher accepts and holds a PAT with `contents: write`. A compromised or broken plugin release lands directly in the publish path, and the floating `github`/`npm` copies can differ from the ones `semantic-release` pins. The unused changelog plugin also suggests to readers that `CHANGELOG.md` is maintained, which it is not.\n\n## Fix\n\nDelete the `with: extra_plugins:` block from the Semantic Release step. The plugins the config uses already resolve from `semantic-release`'s own dependency tree.\n\n## Resolution\nRemoved the `with: extra_plugins` block from the Semantic Release step in publish.yml, with a comment explaining why. actionlint and zizmor --pedantic clean.\n", "category": "security", "found": "2026-09-25", "id": "ci-8c9daa22", "resolved": "2026-09-25", "severity": "medium", "status": "fixed", "title": "Release job npm-installs three unpinned semantic-release plugins, one of them unused"}
{"area": ".github/workflows/pr-lint.yml", "auditor": "ci", "body": "## Problem\n\nExcess secret exposure. `pr-lint.yml` puts `NPM_TOKEN: ${{ secrets.NPM_TOKEN }}` in the workflow-level `env:` (line 17), so every step receives it, and re-passes it explicitly to `Simek/yarn-lock-changes` (line 51) and `ivuorinen/actions/pr-lint` (line 58). Neither needs an npm token: the repo has no private dependencies, and `publish.yml` states \"npm auth uses Trusted Publishing (OIDC) … so no NPM_TOKEN is needed\".\n\n## Evidence\n\n```yaml\n# .github/workflows/pr-lint.yml:16-18\nenv:\n  NPM_TOKEN: ${{ secrets.NPM_TOKEN }}\n  GITHUB_TOKEN: ${{ github.token }}\n...\n      - name: Yarn Lock Changes\n        uses: Simek/yarn-lock-changes@59f47ee499424d2c2437c5aebf863b5c6d50a5bc # v0.14.1\n        ...\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}\n```\n\nThe sibling `base-configs-commitlint/.github/workflows/pr-lint.yml` removed exactly this and documents why: \"No workflow-level `env:` either — a secret placed there is handed to every step in the job, including third-party actions that have no use for it.\"\n\n## Impact\n\nIf the `NPM_TOKEN` secret still exists and can publish, any compromise of a third-party action in this job (or of MegaLinter's many bundled tools) can exfiltrate an npm publish credential on every same-repo PR. Fork PRs do not receive secrets, which limits but does not remove the exposure (Renovate/Dependabot branches are same-repo).\n\n## Fix\n\nRemove the workflow-level `env:` block and the `NPM_TOKEN` lines from both steps; pass `token: ${{ secrets.GITHUB_TOKEN }}` via `with:` only. Then delete the `NPM_TOKEN` repository/environment secret if nothing else reads it (publishing uses OIDC).\n\n## Resolution\nRemoved the workflow-level env block and the NPM_TOKEN lines from both steps in pr-lint.yml; steps keep only GITHUB_TOKEN. Deleting the NPM_TOKEN secret remains the owner's action.\n", "category": "security", "found": "2026-09-25", "id": "ci-e39a3120", "resolved": "2026-09-25", "severity": "medium", "status": "fixed", "title": "NPM_TOKEN is set workflow-wide on pull_request and handed to a third-party action"}
{"area": ".github/workflows/publish.yml", "auditor": "ci", "body": "## Problem\n\n`publish.yml` restores `node_modules` keyed on `hashFiles('.nvmrc', '**/yarn.lock')` and runs `yarn install` only `if: steps.cache.outputs.cache-hit != 'true'` (lines 69-81), without `--immutable`. A change to `package.json` that does not change `yarn.lock` keeps the cache key, so the release runs against the previous tree. When the install does run, a manifest/lockfile mismatch is silently re-resolved instead of failing.\n\n## Evidence\n\n```yaml\n# .github/workflows/publish.yml:76-81\n          key: node-modules-${{ hashFiles('.nvmrc', '**/yarn.lock') }}\n      - name: Install Dependencies\n        shell: bash\n        if: steps.cache.outputs.cache-hit != 'true'\n        run: yarn install\n```\n\nThis exact pattern shipped a wrong release in the sibling repo: `base-configs-stylelint/.github/workflows/publish.yml:86-93` records that a package.json-only bump \"restored the previous node_modules, skipped this step, and released against a dependency tree that no longer matched the manifest. That shipped 1.4.0 -- … cut a minor where 2.0.0's rules say major.\"\n\n## Impact\n\nsemantic-release reads its release rules from `@ivuorinen/semantic-release-config` inside `node_modules`; a stale tree applies the old rules and can cut the wrong version, as already happened in stylelint.\n\n## Fix\n\nRun the install unconditionally and immutably, keeping the cache only as a speed-up:\n\n```yaml\n      - name: Install Dependencies\n        shell: bash\n        run: yarn install --immutable --mode=skip-build\n```\n\n## Resolution\npublish.yml install now runs unconditionally as `yarn install --immutable --mode=skip-build` (cache kept as a speed-up, key includes .nvmrc).\n", "category": "reliability", "found": "2026-09-25", "id": "ci-79629388", "resolved": "2026-09-25", "severity": "medium", "status": "fixed", "title": "Release installs are skipped on a cache hit and not --immutable, so a stale tree can release"}
{"area": "CHANGELOG.md", "auditor": "docs", "body": "## Problem\n\nChangelog drift. `CHANGELOG.md` says \"All notable changes to this project will be documented in this file\" and its newest entry is `## [1.0.23] … (2026-06-15)`. The release config no longer writes it: `@ivuorinen/semantic-release-config` 2.x has no `@semantic-release/changelog` or `@semantic-release/git` plugin.\n\n## Evidence\n\n```text\n$ git log -1 --format='%h %ad %s' --date=short -- CHANGELOG.md\n04757c4 2026-06-15 chore(release): 1.0.23 [skip ci]\n$ git tag --sort=-v:refname | head -3\nv4.0.2\nv4.0.1\nv4.0.0\n```\n\nThree majors (v2.0.0, v3.0.0, v4.0.0) and their patches are absent. The README's \"See [CHANGELOG]\" link points to GitHub Releases, which is the maintained source.\n\n## Impact\n\nAnyone reading the file (npm shows it; the repo lists it) concludes the package has not changed since 1.0.23 and misses three breaking releases, including the Node floor raise.\n\n## Fix\n\nDelete `CHANGELOG.md` (release notes live on GitHub Releases, as the README already says), or replace its body with one line pointing at https://github.com/ivuorinen/base-configs/releases.\n\n## Resolution\nCHANGELOG.md replaced with a pointer to GitHub Releases stating it is no longer generated.\n", "category": "docs", "found": "2026-09-25", "id": "docs-3a6a3b3c", "resolved": "2026-09-25", "severity": "medium", "status": "fixed", "title": "CHANGELOG.md stops at 1.0.23 while releases reached v4.0.2"}
{"area": ".github/renovate.json", "auditor": "audit", "body": "## Problem\n\n`.github/renovate.json` only extends the shared preset. It lacks the `packageRules` entry that `base-configs-semantic-release` and `base-configs-stylelint` added to give GitHub Actions and pre-commit updates the `actions` scope. Without it Renovate titles a major action bump `chore(deps)!: …`, and `@ivuorinen/semantic-release-config` 2.x maps `{ breaking: true, type: \"chore\", scope: \"deps\" }` to `release: \"major\"`.\n\n## Evidence\n\nHistory shows the pattern cutting majors: `v1.0.0` here was produced by, among others, `8276507 chore(deps)!: update actions/checkout (v5.0.1 → v6.0.0) (#427)` and `6a7e7c1 chore(deps)!: update actions/setup-node (v5.0.0 → v6.0.0) (#406)`. `base-configs-stylelint/.github/renovate.json` documents the mechanism: \"Renovate was otherwise emitting `chore(deps)` for some of these updates, which releases a patch, or `chore(deps)!` for majors\".\n\n## Impact\n\nThe next major bump of any action (checkout, setup-node, cache) publishes a new major of `@ivuorinen/base-configs` although nothing consumers install changed; consumers pinned to `^4` stop receiving updates until they notice.\n\n## Fix\n\nAdd the rule both sibling repos use:\n\n```json\n\"packageRules\": [\n  {\n    \"matchManagers\": [\"github-actions\", \"pre-commit\"],\n    \"semanticCommitType\": \"chore\",\n    \"semanticCommitScope\": \"actions\"\n  }\n]\n```\n\n## Resolution\nAdded packageRules to .github/renovate.json giving github-actions and pre-commit updates semanticCommitType chore, scope actions.\n", "category": "conventions", "found": "2026-09-25", "id": "audit-0498bf96", "resolved": "2026-09-25", "severity": "medium", "status": "fixed", "title": "Renovate CI-only major bumps are labelled chore(deps)! and would cut a major release"}
{"area": "yarn.lock", "auditor": "audit", "body": "## Problem\n\nThe repo has no `.github/dependabot.yml`, so Dependabot security PRs use the default `chore(deps): bump …` prefix, which the release rules map to a patch. The three unreleased commits since `v4.0.2` change only `yarn.lock`, which reaches no consumer.\n\n## Evidence\n\n```text\n$ git log --format='%h %s' v4.0.2..HEAD\n6ee9ca2 chore(deps): bump browserslist from 4.28.4 to 4.28.8 (#575)   yarn.lock | 56\ncdeafcc chore(deps): bump fast-uri from 3.1.5 to 3.1.6 (#573)         yarn.lock | 6\ncf52ed7 chore(deps): bump @humanfs/node from 0.16.7 to 0.16.8 (#574)  yarn.lock | 26\n```\n\n`base-configs-semantic-release/.github/dependabot.yml` solves this with `versioning-strategy: lockfile-only` and `commit-message: prefix: \"chore(actions)\"`.\n\n## Impact\n\nOnce the release pipeline is unblocked, v4.0.3 publishes with no consumer-visible change; every future lockfile-only bump repeats that.\n\n## Fix\n\nAdd `.github/dependabot.yml` mirroring semantic-release's: `package-ecosystem: npm`, `versioning-strategy: lockfile-only`, `commit-message: { prefix: \"chore(actions)\" }`.\n\n## Resolution\nAdded .github/dependabot.yml (npm, versioning-strategy lockfile-only, weekly, cooldown 7 days, commit-message prefix chore(actions)) mirroring base-configs-semantic-release, so lockfile-only bumps no longer cut a patch release.\n", "category": "conventions", "found": "2026-09-25", "id": "audit-909a30de", "resolved": "2026-09-25", "severity": "low", "status": "fixed", "title": "Dependabot lockfile-only bumps are labelled chore(deps) and cut patch releases"}
{"area": ".gitignore", "auditor": "audit", "body": "## Problem\n\nDead configuration. `.gitignore` lines 2-15 ignore and re-include `packages/**` config files for a monorepo layout that no longer exists (the configs moved to separate repositories), `.prettierignore` line 2 ignores `/.nx/workspace-data` (no Nx here), and `.yarnclean` is a Yarn 1 `autoclean` file that Yarn 4 does not read.\n\n## Evidence\n\n```text\n$ git ls-files | grep -c '^packages/'\n0\n$ git ls-files | grep -ci nx\n0\n$ yarn --version\n4.18.0\n```\n\n`.gitignore:16` ignores all of `.yarn`.\n\n## Impact\n\nMisleads maintainers about the repo layout and tooling; `.yarnclean`'s patterns (e.g. `*.md`, `test`, `docs`) suggest pruning that never happens.\n\n## Fix\n\nReduce `.gitignore` to `node_modules/` and the Yarn 4 artefacts (`.yarn/cache`, `.yarn/unplugged`, `.yarn/build-state.yml`, `.yarn/install-state.gz`, `.pnp.*`), drop the `/.nx/workspace-data` line from `.prettierignore`, and delete `.yarnclean`.\n\n## Resolution\n.gitignore reduced to node_modules and the Yarn 4 artefacts; /.nx line dropped from .prettierignore; .yarnclean removed (git rm).\n", "category": "maintainability", "found": "2026-09-25", "id": "audit-145c5485", "resolved": "2026-09-25", "severity": "low", "status": "fixed", "title": "Ignore files and .yarnclean describe a monorepo and Yarn 1 layout that no longer exist"}
{"area": "README.md", "auditor": "docs", "body": "## Problem\n\nBroken link. `README.md:80` defines `[contributing-link]: https://github.com/ivuorinen/.github/blob/main/CONTRIBUTING.md`.\n\n## Evidence\n\n```text\n$ gh api repos/ivuorinen/.github/contents/CONTRIBUTING.md\nNot Found (HTTP 404)\n$ gh api 'repos/ivuorinen/.github/git/trees/HEAD?recursive=1' --jq '[.tree[].path | select(test(\"(?i)contributing\"))]'\n[]\n```\n\n## Impact\n\nWould-be contributors following the README land on a 404.\n\n## Fix\n\nAdd `CONTRIBUTING.md` to `ivuorinen/.github`, or drop the \"contribution guidelines\" clause from the README's Contributing section.\n\n## Resolution\nDropped the contribution-guidelines clause and the dead ivuorinen/.github CONTRIBUTING.md link definition.\n", "category": "docs", "found": "2026-09-25", "id": "docs-f36867be", "resolved": "2026-09-25", "severity": "low", "status": "fixed", "title": "README contributing link points to a file that does not exist"}
{"area": ".github/SECURITY.md", "auditor": "docs", "body": "## Problem\n\nMissing information. `.github/SECURITY.md:9`: \"If you find a vulnerability, please send me email and I'll fix it as soon as possible.\" No address, no link to GitHub private vulnerability reporting.\n\n## Evidence\n\nThe whole file is 9 lines; `grep -c '@' .github/SECURITY.md` → 0.\n\n## Impact\n\nA reporter has no private channel and may open a public issue instead.\n\n## Fix\n\nReplace the sentence with a concrete channel, e.g. \"Report privately via GitHub: Security → Report a vulnerability\" (and enable private vulnerability reporting), or give the address.\n\n## Resolution\nSECURITY.md now points to https://github.com/ivuorinen/base-configs/security/advisories/new; private vulnerability reporting is enabled on this repo (gh api .../private-vulnerability-reporting → enabled: true).\n", "category": "docs", "found": "2026-09-25", "id": "docs-215f2d7e", "resolved": "2026-09-25", "severity": "low", "status": "fixed", "title": "SECURITY.md asks reporters to email but gives no address"}
{"area": "package.json", "auditor": "deps", "body": "## Problem\n\nUnused dev dependencies. `package.json` declares `\"@types/node\": \"^26.0.0\"` (line 61) and `\"typescript\": \"^6.0.3\"` (line 64), and `.yarnrc.yml:10-13` extends `@commitlint/load` with `typescript`/`@types/node`. The repo contains no TypeScript, never runs `tsc`, and `@commitlint/load` is no longer in the tree.\n\n## Evidence\n\nManifest: `package.json:61`, `package.json:64` (devDependencies). Lockfile: both resolved (`typescript@patch:typescript@npm%3A6.0.3`), while `grep -c '^\"@commitlint/load@' yarn.lock` → `0`. Usage scan: `git ls-files | grep -cE '\\.(ts|mts|cts|tsx)$'` → `0`; no `tsc`/`typescript` reference in `package.json` scripts, workflows or `eslint.config.mjs`. The only remaining consumer, `eslint-plugin-n`'s `lib/util/get-full-type-name.js`, loads `typescript` inside `try { … } catch { return null }`, and linting works with it unresolvable (verified in `base-configs-eslint`).\n\n## Impact\n\nRoughly 20 MB of compiler plus type definitions installed on every clone and CI run for no effect, and a `packageExtensions` entry that reads as if commitlint needed patching here.\n\n## Fix\n\nRemove both devDependencies and the `'@commitlint/load@*'` block from `.yarnrc.yml` (keep the `eslint-plugin-n` block only if you want type-aware `n/no-sync`, which nothing here uses — otherwise drop it too), then `yarn install`. Removal is behavior-affecting in principle: confirm `yarn eslint:report` still passes before committing.\n\n## Resolution\nOwner-approved removal: typescript and @types/node devDependencies and both packageExtensions blocks (@commitlint/load, eslint-plugin-n) removed from package.json/.yarnrc.yml. `yarn eslint:report`, `yarn prettier:report` pass; immutable install passes. typescript still resolves transitively (6.0.3 via @ivuorinen/eslint-config 1.2.3), not as a declared dependency.\n", "category": "maintainability", "found": "2026-09-25", "id": "deps-689fef99", "resolved": "2026-09-25", "severity": "low", "status": "fixed", "title": "typescript and @types/node devDependencies and the @commitlint/load packageExtension serve nothing"}
{"area": "CLAUDE.md", "auditor": "audit", "body": "## Problem\n\nThe repo's own `markdownlint:report` script exits 1 on a clean `main`: `CLAUDE.md` violates MD060 (table pipe spacing), MD013 (lines over 200) and blank-line rules under the repo's `.markdownlint.json`. Found during the fix pass, not the audit pass — the conventions sweep ran `pre-commit --all-files` in the package repos, and this repo has no pre-commit config.\n\n## Evidence\n\n`git show HEAD:CLAUDE.md | markdownlint --stdin -c .markdownlint.json` → 26 errors (e.g. `14:1 MD060/table-column-style`, `28:201 MD013/line-length`); README.md, CHANGELOG.md and SECURITY.md report 0.\n\n## Impact\n\n`yarn fix` / `markdownlint:report`, which CLAUDE.md itself lists as the lint commands, fail on every run, so a real Markdown regression is indistinguishable from the standing failures.\n\n## Fix\n\nRun `markdownlint --fix CLAUDE.md` (fixes table spacing and blank lines) and wrap the remaining lines over 200 characters.\n\n## Resolution\nmarkdownlint --fix CLAUDE.md applied table spacing and blank-line fixes; four lines over 200 characters wrapped by hand. yarn markdownlint:report and yarn prettier:report exit 0. docs/audit/ added to .markdownlintignore (generated findings store), as base-configs-commitlint does.\n", "category": "conventions", "found": "2026-09-25", "id": "audit-fb16212a", "resolved": "2026-09-25", "severity": "low", "status": "fixed", "title": "yarn markdownlint:report fails on main: CLAUDE.md has 26 markdownlint errors"}
{"area": ".github/workflows/pr-lint.yml", "auditor": "ci", "body": "## Problem\n\nNon-gating check. `pr-lint.yml` (PR Lint), `pr.yml` and `codeql.yml` run on `pull_request` and read as merge gates, but `main`'s branch protection lists no required status checks and there are no rulesets. The repository also has `allow_auto_merge: true`.\n\n## Evidence\n\nVerified with the GitHub API (authenticated):\n\n```text\n$ gh api repos/ivuorinen/base-configs/branches/main/protection\nrequired_status_checks: none | enforce_admins: false\n$ gh api repos/ivuorinen/base-configs/rules/branches/main\n[]\n$ gh api repos/ivuorinen/base-configs --jq .allow_auto_merge\ntrue\n```\n\n`main` currently carries failing runs: `gh api repos/ivuorinen/base-configs/commits/main/check-runs` → `Lint=failure`, `Dependabot=failure`.\n\n## Impact\n\nAny PR — including bot PRs with auto-merge enabled — merges regardless of lint, CodeQL or lockfile results. The advisory-laden lockfile that now blocks publishing merged this way.\n\n## Fix\n\nAdd a ruleset (or branch-protection rule) on `main` requiring the check contexts `PR Lint` and `Analyze (javascript)`. Verify afterwards with:\n\n```sh\ngh api repos/ivuorinen/base-configs/rules/branches/main --jq '.[] | select(.type==\"required_status_checks\")'\n```\n\n## Resolution\nruleset 'main checks' (24025505) added on 2026-09-26: PR Lint, Analyze (javascript); verified via rules/branches/main\n", "category": "reliability", "found": "2026-09-25", "id": "ci-7d119495", "resolved": "2026-09-26", "severity": "medium", "status": "fixed", "title": "No PR check is required and auto-merge is on, so red PRs merge (verified)"}
{"area": ".github/renovate.json", "auditor": "audit", "body": "## Problem\n\nThe non-releasing rule in `.github/renovate.json` matches only `\"matchManagers\": [\"github-actions\", \"pre-commit\"]`, and the major-prefix rule only `[\"pre-commit\"]`. Renovate's `nvm` manager, which updates `.nvmrc`, matches neither. `.nvmrc` is not in `package.json` `files`, and the workflows read it only through `node-version-file`, so a Node bump there never reaches consumers. Renovate still titles it with the preset default scope `deps`.\n\n## Evidence\n\n- Shared preset `github>ivuorinen/renovate-config` sets `semanticCommitScope: \"actions\"` only for `matchManagers: [\"github-actions\"]`, and sets `commitMessagePrefix: \"chore(deps)!: \"` on every `major` update. Its `chore(actions): ` override for majors covers github-actions only.\n- Shared release rules (`@ivuorinen/semantic-release-config` 3.0.0 `index.cjs:11-13`): `chore(deps)` gives a patch, breaking `chore(deps)` gives a major, and `chore(actions)` gives no release.\n- Observed: ivuorinen/base-configs#577 and ivuorinen/base-configs-commitlint#196 (branch `renovate/node-26.x`, which changes only `.nvmrc`). Renovate titled both `chore(deps): update node.js (…)`, including force-pushes made after the actions/devDependencies rule landed (#578 / #201). The maintainer renamed both to `chore(actions): …` by hand before merging (PR timeline `renamed` events by the maintainer).\n\n## Impact\n\nUnless someone retitles the PR, every Node minor or patch bump merges as `chore(deps)` and publishes a needless patch release. In config-checker, each release then fans out into Renovate bumps across every @ivuorinen package. A Node major (for example 26 → 28) arrives as `chore(deps)!:` and publishes a **major** release of the package, even though consumers see no change.\n\n## Fix\n\nAdd `\"nvm\"` to both manager lists, and update the first rule's description so it names `.nvmrc` as well:\n\n```json\n\"matchManagers\": [\"github-actions\", \"nvm\", \"pre-commit\"],\n…\n\"matchManagers\": [\"nvm\", \"pre-commit\"],\n\"matchUpdateTypes\": [\"major\"],\n\"commitMessagePrefix\": \"chore(actions): \"\n```\n\nThe better fix is to make the same change once in the shared `ivuorinen/renovate-config` preset, next to its github-actions major rule, so all nine repos pick it up.\n\n## Resolution\nFixed in the shared preset, ivuorinen/renovate-config#114 (merged as 503bbff): nvm (.nvmrc) and pre-commit updates get the literal prefix \"chore(tooling): \" and devDependencies/require-dev get \"chore(dev-deps): \" for every update type, so none carries the global \"chore(deps)!: \" major marker; \"actions\" stays reserved for github-actions. @ivuorinen/semantic-release-config releases on the deps scope only, so tooling and dev-deps commits cut no release (checked through commit-analyzer). ivuorinen/base-configs#586 then removed every local scope, prefix and group rule from this repo's .github/renovate.json; only the conventional-changelog-conventionalcommits <10 hold remains.\n", "category": "correctness", "extra": {"location": ".github/renovate.json:11-22", "location_sha": "e17ab349a99bc5bb"}, "found": "2026-09-28", "id": "audit-314d998c", "resolved": "2026-09-29", "severity": "medium", "status": "fixed", "title": "Renovate .nvmrc (nvm manager) updates are titled chore(deps) and release the package"}
{"area": ".github/renovate.json", "auditor": "audit", "body": "## Problem\n\nThe third packageRule (`matchManagers: [\"pre-commit\"]`, `matchUpdateTypes: [\"major\"]`) and the `pre-commit` entry in the first rule's `matchManagers` target a manager with no input in this repo. base-configs has no `.pre-commit-config.yaml`; it runs its hooks through `simple-git-hooks` in `package.json`. The rules came over from the sibling repos, where pre-commit does exist.\n\n## Evidence\n\n- `.github/renovate.json:34-35`: `\"matchManagers\": [\"pre-commit\"], \"matchUpdateTypes\": [\"major\"]`\n- The repo root has no `.pre-commit-config.yaml` (the other eight repos all have one). `package.json` declares `simple-git-hooks`.\n\n## Impact\n\nThe rules never fire, so their descriptions (\"so without this a hook major still arrives as 'chore(deps)!: '…\") describe a risk this repo does not have. The result is dead configuration that someone has to keep in sync whenever the rule set changes, as the fix for the nvm-manager finding will require.\n\n## Fix\n\nDrop the third packageRule and remove `\"pre-commit\"` from the first rule's `matchManagers`, adjusting its description. If the rules stay deliberately for parity with the sibling repos, add a description line that says so.\n\n## Resolution\nThe pre-commit rules are gone: ivuorinen/base-configs#586 reduced .github/renovate.json to the conventional-changelog-conventionalcommits <10 hold. Scope and prefix handling for every manager, pre-commit included, now lives in the shared preset (ivuorinen/renovate-config#114).\n", "category": "maintainability", "extra": {"location": ".github/renovate.json:29-37", "location_sha": "9a072c44294010ea"}, "found": "2026-09-28", "id": "audit-f4d8da4c", "resolved": "2026-09-29", "severity": "advisory", "status": "fixed", "title": "renovate.json pre-commit rules match nothing: base-configs has no .pre-commit-config.yaml"}
{"area": "`.mega-linter.yml`", "auditor": "audit", "body": "## Problem\n\nExposing `GITHUB_TOKEN` lets zizmor's online `impostor-commit` audit authenticate instead of returning HTTP 401.\n\n## Evidence\n\n`ACTION_ZIZMOR_UNSECURED_ENV_VARIABLES` is in the MegaLinter v9.5.0 schema and is the documented remedy.\n\nOnline audits run only inside MegaLinter's CI; local validation cannot exercise the token hand-off.\n\n## Impact\n\nInformational. If MegaLinter does not surface `GITHUB_TOKEN` to the zizmor step in CI, the 401 may persist.\n\n## Fix\n\nNo action now. If CI still 401s, set `ACTION_ZIZMOR_ARGUMENTS: --no-online-audits` to skip only online audits.\n\nMigrated from v1 `nitpicker-findings.md` as `N1`.\n\n## Resolution\nConfirmed in CI: PR #596 PR Lint run 38243715185 ran MegaLinter's ACTION_ZIZMOR (zizmor v1.25.0) on pr.yml and publish.yml with 0 errors and no HTTP 401; ACTION_ZIZMOR_UNSECURED_ENV_VARIABLES: [GITHUB_TOKEN] in .mega-linter.yml delivers the token. Re-validated 2026-10-27.\n", "category": "reliability", "found": "2026-06-25", "id": "audit-695458e8", "resolved": "2026-10-26", "severity": "advisory", "status": "fixed", "title": "zizmor online-audit re-enable cannot be confirmed without a CI run"}
{"area": ".yarnrc.yml", "auditor": "security", "body": "## Problem\n\nsupply-chain. `.yarnrc.yml` sets no `npmMinimalAgeGate`. The one-day gate that applies today is Yarn 4's built-in default, while the file's own comment describes `npmPreapprovedPackages` as an exemption from that gate.\n\n## Evidence\n\n`yarn config --why npmMinimalAgeGate` → `Source: <default>, Value: 1440`. opengrep 1.31.0 `--config=auto` rule `yarn-missing-minimal-age-gate` fires on `.yarnrc.yml:1`. The shared Renovate preset waits `minimumReleaseAge: \"3 days\"` before proposing a version.\n\n## Impact\n\nA future Yarn default change silently removes the only install-time guard against a freshly published malicious version; nothing in the repo would show it.\n\n## Fix\n\nDeclare it: `npmMinimalAgeGate: 3d` (matches Renovate's 3-day `minimumReleaseAge`, so Renovate PRs still install). The same pattern as base-configs-stylelint's explicit `enableScripts: false`.\n\n## Resolution\nfix/audit-2026-10 85cb561: npmMinimalAgeGate: 3d declared next to npmPreapprovedPackages in .yarnrc.yml.\n", "category": "security", "extra": {"location": ".yarnrc.yml:1", "location_sha": "d794c2962910a2a5"}, "found": "2026-10-27", "id": "security-a375de32", "resolved": "2026-10-27", "severity": "low", "status": "fixed", "title": "The npm age gate is inherited from Yarn's default instead of being declared"}
{"area": "package.json", "auditor": "deps", "body": "## Problem\n\nheavyweight-dependency. `npm-run-all2` is used by exactly one script, `fix`, to run three scripts in sequence (`run-s prettier:fix eslint:fix markdownlint:fix`).\n\n## Evidence\n\nManifest: `package.json` devDependencies `\"npm-run-all2\": \"^9.0.2\"`. Lockfile: `npm-run-all2@npm:9.0.3`, which depends on `shell-quote@npm:1.10.0` (GHSA-pqg4-j6r4-53mv). Usage scan over every tracked file and `scripts`: the only reference is the `run-s` binary in `scripts.fix`; no workflow, hook or doc invokes `run-p`, `run-s` or `npm-run-all`.\n\n## Impact\n\nA dev dependency and its transitive tree (shell-quote, cross-spawn, memorystream, pidtree, read-package-json-fast, which) exist to chain three commands.\n\n## Fix\n\nReplace with `\"fix\": \"yarn prettier:fix && yarn eslint:fix && yarn markdownlint:fix\"` and remove the devDependency (`yarn remove npm-run-all2`). Same sequential, stop-on-first-failure behaviour as `run-s`.\n\n## Resolution\nfix/audit-2026-10 79cfaa3 (user-approved removal): fix script is yarn prettier:fix && yarn eslint:fix && yarn markdownlint:fix; npm-run-all2 and shell-quote are gone from yarn.lock; yarn fix runs all three.\n", "category": "maintainability", "extra": {"location": "package.json:45", "location_sha": "50b6d81c1b4cfe92"}, "found": "2026-10-27", "id": "deps-9dbb2db4", "resolved": "2026-10-27", "severity": "low", "status": "fixed", "title": "npm-run-all2 is used by one script and pulls in shell-quote with a critical advisory"}
{"area": "package.json", "auditor": "contract", "body": "## Problem\n\n`@ivuorinen/base-configs` depends on `@ivuorinen/stylelint-config`, whose `peerDependencies` require `stylelint ^17.0.0`, but neither declares nor documents `stylelint`. Every other bundled tool (eslint, prettier, markdownlint-cli, semantic-release, browserslist) arrives as a runtime dependency of its config package.\n\n## Evidence\n\n`yarn install` in this repo: `YN0002: @ivuorinen/base-configs@workspace:. doesn't provide stylelint (p1de5e6), requested by @ivuorinen/stylelint-config`. `node_modules/stylelint` does not exist after the install. README.md lists stylelint among the bundled configs and says nothing about installing it.\n\n## Impact\n\nA Yarn or pnpm consumer gets the stylelint config (and, under npm, a written `.stylelintrc.json`) but no `stylelint` binary, contradicting the package's purpose of installing all required tooling in one step. npm 7+ installs the peer automatically, so the gap only shows outside npm.\n\n## Fix\n\nAdd `\"stylelint\": \"^17.0.0\"` to `dependencies` (the peer range of @ivuorinen/stylelint-config), so the meta-package provides it like every other tool. If the consumer's own stylelint version should win instead, declare it as a `peerDependencies` entry and add it to the README install line; either way the YN0002 warning must disappear.\n\n## Resolution\nfix/audit-2026-10 731895d: stylelint ^17.0.0 is a runtime dependency (user chose dependency over peer); yarn install no longer reports YN0002; node_modules/.bin/stylelint runs the bundled .stylelintrc.json.\n", "category": "correctness", "extra": {"location": "package.json:58", "location_sha": "195ff32137e72ab3"}, "found": "2026-10-27", "id": "contract-a7a824cd", "resolved": "2026-10-27", "severity": "medium", "status": "fixed", "title": "The meta-package does not provide stylelint, the required peer of its stylelint config"}
{"area": ".yamlignore", "auditor": "audit", "body": "## Problem\n\ndead config. `.yamlignore` holds a single newline and no tool reads it; base-configs has no `.yamllint.yml` and no workflow references the file.\n\n## Evidence\n\n`wc -c .yamlignore` → 1. No tracked file mentions `yamlignore`. base-configs-commitlint's `.yamllint.yml:4`: `yamllint reads this key, not a .yamlignore file`.\n\n## Impact\n\nA file that looks like configuration and does nothing.\n\n## Fix\n\n`git rm .yamlignore`.\n\n## Resolution\nfix/audit-2026-10 b767293: .yamlignore removed; nothing referenced it.\n", "category": "maintainability", "extra": {"location": ".yamlignore"}, "found": "2026-10-27", "id": "audit-2ac29db3", "resolved": "2026-10-27", "severity": "low", "status": "fixed", "title": "The .yamlignore file is read by nothing"}
{"area": ".github/workflows/pr.yml", "auditor": "audit", "body": "## Problem\n\n`Install Dependencies` is gated on `cache-hit != 'true'` and runs plain `yarn install`.\n\n## Evidence\n\npr.yml: `if: steps.cache.outputs.cache-hit != 'true'` / `run: yarn install`. Plain `yarn install` rewrites a drifted lockfile instead of failing.\n\n## Impact\n\nA PR whose package.json and yarn.lock disagree passes this workflow.\n\n## Fix\n\nRun `yarn install --immutable --mode=skip-build` unconditionally.\n\n## Resolution\nfix/audit-2026-10 3037944: pr.yml installs with --immutable --mode=skip-build on every run; actionlint and zizmor clean.\n", "category": "reliability", "extra": {"location": ".github/workflows/pr.yml:48-49", "location_sha": "35e5c62f08f5787f"}, "found": "2026-10-27", "id": "audit-8a62e935", "resolved": "2026-10-27", "severity": "low", "status": "fixed", "title": "pr.yml skips the install on a cache hit and never runs it with --immutable"}
{"area": ".github/dependabot.yml", "auditor": "commits", "body": "## Problem\n\nThe Dependabot npm entry is `versioning-strategy: lockfile-only` with `commit-message: prefix: \"chore(deps)\"`, which @ivuorinen/semantic-release-config releases as a patch. The comment above it still describes the non-releasing `actions` scope.\n\n## Evidence\n\n#589 changed the prefix from chore(actions) to chore(deps) after audit-909a30de had set it. Consumers resolve the ranges in package.json and never this lockfile, so a lockfile bump changes nothing for them. The four open lockfile-only @ivuorinen bumps (#591-#594) would each have published a release. The same prefix in base-configs-semantic-release turned config-checker's lockfile-only releases into semantic-release-config v3.0.5-v3.0.7, which arrived here as more of these bumps.\n\n## Impact\n\nReleases with no consumer-visible change, each one a reviewed PR here and a version consumers' bots then offer.\n\n## Fix\n\nSet the prefix to chore(lockfile): no release rule matches it, and it is not `actions`, which the shared Renovate preset reserves for GitHub Actions.\n\n## Resolution\n7425274: prefix chore(lockfile), comment corrected; no release rule matches it.\n", "category": "reliability", "extra": {"location": ".github/dependabot.yml:20", "location_sha": "8584767c212c6763"}, "found": "2026-10-27", "id": "commits-d3eb75e5", "resolved": "2026-10-27", "severity": "low", "status": "fixed", "title": "Dependabot lockfile-only bumps are titled chore(deps) again and cut patch releases"}
{"area": "yarn.lock", "auditor": "security", "body": "## Problem\n\ndependency-vulnerability. The committed lockfile resolves ten packages that osv-scanner, trivy and `yarn npm audit` all report as vulnerable. MegaLinter's grype, osv-scanner and trivy linters fail on this lockfile, so the required `PR Lint` check is red on all 11 open PRs (#585, #591-#600) regardless of what each PR changes.\n\n## Evidence\n\nScanned 2026-10-27 on origin/main c3b0e6d. osv-scanner 2.7.0, trivy 0.76.0 (`--include-dev-deps`) and `yarn npm audit --recursive` report the same set (grype 0.120.1 cannot parse a Yarn Berry lockfile: `unable to parse yarn v2 package key`, so it is not a source here).\n\n| Package | Advisories | First fixed |\n| --- | --- | --- |\n| handlebars 4.7.9 | GHSA-8r5x-fm3f-whwj (CVSS 9.8), GHSA-p8wg-vrv2-v86f (9.2), GHSA-xw65-4hp5-5hc7 | 4.7.10 |\n| shell-quote 1.10.0 | GHSA-pqg4-j6r4-53mv (trivy: CRITICAL) | 1.11.0 |\n| undici 8.11.2 | GHSA-4qv8-2p9j-2685, GHSA-f72v-6w45-q472, GHSA-xjj6-f33q-929c | 8.12.2 |\n| http-cache-semantics 4.2.0 | GHSA-ch52-4w7c-c8xp (affects <= 4.2.0) | 4.3.0 |\n| markdown-it 14.3.2 | GHSA-gfr2-rv44-r23c, GHSA-vfgh-jcmw-g37q | 14.3.3 |\n| source-map-js 1.2.1 | GHSA-68fv-2mgg-jv7q | 1.2.2 |\n| fast-uri 3.1.8 | GHSA-333x-w4r6-6crp, GHSA-w74x-v6f9-j3xg, GHSA-wjr9-j795-jxcr, GHSA-xhq7-xmqj-769h | 3.1.9 |\n| ip-address 10.7.2 | GHSA-rxp7-6hxx-x8q5 | 10.7.4 |\n| smol-toml 1.7.2 | GHSA-r4xh-jqrq-34v2 | 1.9.0 |\n| katex 0.16.47 | GHSA-238p-pmpm-9mq7 | 0.18.2 |\n\nPR #600's `PR Lint` log: grype 14 errors, osv-scanner 15 errors, trivy 1 error.\n\n## Impact\n\nEvery dependency PR is blocked by a failure it did not cause, so security updates queue up instead of merging. The vulnerable code runs in this repo's own tooling (release notes rendering via handlebars, npm's fetch stack via @semantic-release/npm, markdownlint-cli).\n\n## Fix\n\nRefresh the lockfile after the config packages are re-released: bump the seven `@ivuorinen/*` ranges to their current releases, `yarn up -R` each listed package, and re-run `osv-scanner scan source --lockfile=yarn.lock` until it exits 0. katex 0.18.2 and smol-toml 1.9.0 sit outside the ranges markdownlint-cli 0.49.1 allows (`^0.16.0`, `~1.7.0`), so they need a markdownlint-cli release that widens them, not a resolution forced across a major.\n\n## Status — partially applied (2026-10-27)\n\n`fix/audit-2026-10` raised the seven config ranges to their published\nreleases (7e3266b), removed npm-run-all2 and with it shell-quote (79cfaa3),\nre-resolved the lockfile and narrowed `js-yaml: ^5.0.0` to\n`markdownlint-cli/js-yaml: ^5.4.1` (abadaf4), and recorded a dated\nosv-scanner ignore for the unreachable katex advisory (78e3db7). handlebars,\nundici, fast-uri, http-cache-semantics, ip-address, markdown-it, source-map-js\nand js-yaml are fixed.\n\nRemaining: smol-toml 1.7.2 (GHSA-r4xh-jqrq-34v2). It arrives through the\npublished @ivuorinen/markdownlint-config 2.0.6, whose markdownlint-cli ^0.49.1\npins `~1.7.0`. base-configs-markdownlint `fix/audit-2026-10` raises\nmarkdownlint-cli to ^0.50.0 (smol-toml ~1.9.1); once that is released, raise\nthe range here, drop the `markdownlint-cli/js-yaml` resolution, confirm\nosv-scanner exits 0 and resolve this finding.\n\n## Resolution\nAll ten advisories are cleared on fix/audit-2026-10. Eight were fixed by raising the config ranges and re-resolving (7e3266b, 79cfaa3, abadaf4). smol-toml resolves to 1.9.1 through a markdownlint-cli/smol-toml resolution (838ef8b). katex GHSA-238p-pmpm-9mq7 (trivy: CVE-2026-103923) is unreachable and ignored until 2027-01-27 in osv-scanner.toml (78e3db7) and .trivyignore (0ed15fb). osv-scanner 2.7.0 and trivy 0.76.0, run with MegaLinter's commands, exit 0. Dropping the two markdownlint-cli resolutions is tracked in deps-83deab14.\n", "category": "security", "extra": {"location": "yarn.lock:2989", "location_sha": "e5dfefd07ada30c9"}, "found": "2026-10-27", "id": "security-44eab753", "resolved": "2026-10-27", "severity": "critical", "status": "fixed", "title": "yarn.lock resolves 10 packages with known advisories, which fails PR Lint on every open PR"}
{"area": "package.json", "auditor": "deps", "body": "## Problem\n\nThe conventional-changelog-conventionalcommits ^9.3.1 devDependency, and the Renovate hold that keeps it below 10, exist so that 9.x is hoisted to the root of this repository's tree, where the published @ivuorinen/semantic-release-config's plugins resolve their preset by name.\n\n## Evidence\n\nThis repository releases with @ivuorinen/semantic-release-config 3.0.9 from its lockfile, which still passes `preset: \"conventionalcommits\"`; without the devDependency the root copy is conventional-changelog-conventionalcommits 10.4.1, the version @commitlint/config-conventional depends on, and generateNotes fails. base-configs-semantic-release f9b6c8b loads the preset by path, which makes the root copy irrelevant, but it is not published yet.\n\n## Impact\n\nA workaround to maintain until removed, and consumers of the 3.0.9 range still run the by-name config.\n\n## Fix\n\nAfter semantic-release-config publishes f9b6c8b: raise the `@ivuorinen/semantic-release-config` dependency to that version (`fix(deps)`, so consumers who upgrade base-configs get it), then drop the devDependency and its Renovate hold and confirm release notes still render.\n\n## Resolution\n9359d1b raises @ivuorinen/semantic-release-config to ^3.0.10, which loads the preset by path; 9163a9b drops the conventionalcommits ^9.3.1 devDependency and the <10 Renovate hold. With 10.4.1 hoisted to the root, commit analysis and release notes render through the installed config.\n", "category": "maintainability", "extra": {"location": "package.json:63", "location_sha": "af7afc8930c4099f"}, "found": "2026-10-27", "id": "deps-5f3eafa2", "resolved": "2026-10-28", "severity": "low", "status": "fixed", "title": "conventionalcommits devDependency and <10 hold only hoist 9.x"}
{"area": "package.json", "auditor": "deps", "body": "## Problem\n\npackage.json resolves `markdownlint-cli/js-yaml` to ^5.4.1 and `markdownlint-cli/smol-toml` to ^1.9.1, outside the ranges markdownlint-cli 0.49.1 declares. Both exist only because the published @ivuorinen/markdownlint-config 2.0.6 depends on markdownlint-cli ^0.49.1, whose pinned versions carry advisories.\n\n## Evidence\n\nmarkdownlint-cli 0.49.1 declares js-yaml ~5.2.1 and smol-toml ~1.7.0; smol-toml 1.7.2 carries GHSA-r4xh-jqrq-34v2 (fixed in 1.9.0). markdownlint-cli 0.50.0 declares js-yaml ~5.5.0 and smol-toml ~1.9.1, so both overrides are redundant once a markdownlint-config release depends on it. base-configs-markdownlint `fix/audit-2026-10` raises markdownlint-cli to ^0.50.0 but is not released yet. The resolutions were added in abadaf4 (js-yaml) and 838ef8b (smol-toml).\n\n## Impact\n\nA resolution keeps applying after its reason is gone: once markdownlint-cli 0.50 is in the tree, `markdownlint-cli/js-yaml: ^5.4.1` can resolve a js-yaml outside 0.50.0's ~5.5.0. Consumers are unaffected, since resolutions apply only to this repository's install.\n\n## Fix\n\nAfter @ivuorinen/markdownlint-config releases with markdownlint-cli ^0.50.0: raise its range here to that release, drop both `markdownlint-cli/*` resolutions, re-resolve, and confirm osv-scanner and trivy (as MegaLinter runs them) still exit 0.\n\n## Resolution\n42378f3 raises @ivuorinen/markdownlint-config to ^2.0.7 (markdownlint-cli ^0.50.0); 49337f0 drops both markdownlint-cli resolutions. js-yaml 5.5.0 and smol-toml 1.9.1 resolve without them; osv-scanner (with osv-scanner.toml) and trivy (vuln,misconfig) exit 0.\n", "category": "maintainability", "extra": {"location": "package.json:66-69", "location_sha": "47a776213c3e3185"}, "found": "2026-10-27", "id": "deps-83deab14", "resolved": "2026-10-28", "severity": "low", "status": "fixed", "title": "markdownlint-cli js-yaml and smol-toml resolutions outlive markdownlint-cli 0.49"}
