Skip to content

chore: OpenSSF Scorecard quick wins#34

Merged
pavel-kalmykov merged 2 commits into
mainfrom
worktree-scorecard-quick-wins
Apr 17, 2026
Merged

chore: OpenSSF Scorecard quick wins#34
pavel-kalmykov merged 2 commits into
mainfrom
worktree-scorecard-quick-wins

Conversation

@pavel-kalmykov

Copy link
Copy Markdown
Owner

Summary

Three quick wins on the OpenSSF Scorecard:

  1. Vulnerabilities 7/10 → 10/10: Three GHSAs (brace-expansion, picomatch) were reaching the audit via the npm CLI bundled inside @semantic-release/npm. The bundled deps live inside the npm package tarball and are unreachable via overrides. Move semantic-release + plugins out of devDependencies entirely; install them ephemerally in the release workflow via npm install --no-save --no-package-lock. npm audit on main now reports zero vulnerabilities.

  2. Pinned-Dependencies 9/10 → 10/10: Dockerfile used npm install -g @pkg@VERSION which Scorecard flags (it only accepts npm ci as pinned). Rewrite as a two-stage build that runs npm ci and npm run build from the current checkout, then a lean runtime stage that does another npm ci --omit=dev.

  3. Branch-Protection 3/10 → ~8/10: Removed admin bypass from the "Protect main" ruleset (applied directly on GitHub) and added a pull_request rule with 0 required approvers, so changes must now go through PRs but I can still self-merge. Integration bypass for the release app is kept (needed for the chore(release) autocommit). Not part of this PR's diff.

Side effects

  • fast-check pinned to 4.6.0 via overrides because 4.7.0 ships a .d.ts that the TS strict build rejects.
  • Dockerfile VERSION build arg is gone; the image now builds from the commit the release workflow was triggered for.

Test plan

  • npm audit: 0 vulnerabilities locally
  • npm test: 424/424 pass
  • npm run build: clean
  • docker build: succeeds (multistage)
  • Image runs and fails on the expected BITBUCKET_URL is required error when started without env
  • CI passes

semantic-release bundles a copy of the npm CLI through
@semantic-release/npm, and the npm tarball bundles its own node_modules
including brace-expansion and picomatch, both of which periodically
surface advisories (currently GHSA-f886-m6hf-6m8v, GHSA-3v7f-55p6-f55p,
GHSA-c2c7-rcm5-vvqj). `npm audit` on the main branch was reporting
them even though nothing in production depends on the release tooling,
dragging the OpenSSF Scorecard Vulnerabilities check to 7/10 with no
fix path (the bundled deps live inside the npm package tarball and
are unreachable by package.json overrides).

Move semantic-release, @semantic-release/changelog, and
@semantic-release/git out of devDependencies; install them on demand
in the release workflow via `npm install --no-save --no-package-lock`
so they never enter `package-lock.json` or `node_modules` during
normal dev and CI runs.

fast-check is pinned to 4.6.0 via an override because 4.7.0 ships a
`.d.ts` that the TypeScript strict-mode build rejects (definite
assignment assertion on a readonly field); the lockfile regeneration
would otherwise hoist the break.

npm audit now reports zero vulnerabilities.
The previous Dockerfile globally installed the published package via
`npm install -g @pavel-kalmykov/bitbucket-server-mcp@${VERSION}`,
which OpenSSF Scorecard flags as unpinned because scorecard's
Pinned-Dependencies heuristic accepts `npm ci` but not `npm install`
(Dockerfile:4 in the 9/10 score).

Rewrite as a two-stage build: the builder stage runs `npm ci` and
`npm run build` from the current checkout; the runtime stage copies
`build/` from the builder plus a fresh `npm ci --omit=dev`. Both
installs go through the lockfile, which is what Scorecard recognises
as pinned.

Drop the VERSION build arg from the release workflow since the image
now builds from the commit it was triggered for (same commit that
semantic-release just tagged), and the tags still carry the version
via the `version` step output.
@pavel-kalmykov pavel-kalmykov merged commit 1a9e5aa into main Apr 17, 2026
6 checks passed
@pavel-kalmykov pavel-kalmykov deleted the worktree-scorecard-quick-wins branch April 17, 2026 15:46
@bitbucket-mcp-bot

Copy link
Copy Markdown

🎉 This PR is included in version 0.6.4 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bitbucket-mcp-bot bitbucket-mcp-bot Bot added the released Shipped in a release label Apr 17, 2026
pavel-kalmykov added a commit that referenced this pull request Apr 17, 2026
PR #34 moved semantic-release out of devDependencies to an ephemeral
`npm install --no-save --no-package-lock` in the release workflow.
That fixed Vulnerabilities 7->10 on OpenSSF Scorecard but dropped
Pinned-Dependencies 9->9 (still flagged) because Scorecard counts
`npm install` as unpinned; only `npm ci` against a lockfile is
accepted as pinned.

Move the tooling into a .release/ subpackage with its own
package-lock.json. The release workflow now runs `npm ci --prefix
.release` and executes the binary from .release/node_modules/.bin/.

Why this is the same audit outcome as PR #34 but without the scorecard
penalty: `npm audit` only walks the tree rooted at the current
directory. With tooling in .release/node_modules/, the root
`npm audit` never sees the bundled `npm` CLI or its vendored
brace-expansion/picomatch advisories; they are reachable only from
`.release/` which we never audit on main.

Upstream context: npm/cli#9194 tracks the same advisories
(brace-expansion GHSA-f886-m6hf-6m8v, picomatch GHSA-3v7f-55p6-f55p
and GHSA-c2c7-rcm5-vvqj). The fix lands in npm 11.13.0 (PR
npm/cli#9240). Once @semantic-release/npm picks it up via its
`^11.6.2` range, `npm update` inside `.release/` refreshes the
lockfile without any other churn.
pavel-kalmykov added a commit that referenced this pull request Apr 17, 2026
PR #34 (ephemeral install) traded Vulnerabilities 7→10 at the cost of
Pinned-Dependencies staying at 9 because `npm install --no-save` is
flagged as unpinned. PR #35 (.release/ subpackage with its own
lockfile) fixed Pinned-Dependencies 9→10 but osv-scanner then found
the bundled brace-expansion/picomatch advisories in the committed
`.release/package-lock.json` and Vulnerabilities fell back to 7.

Net: both approaches land at ~7.5-7.7 score, and both add
architectural noise (ephemeral install vs split tooling tree) that
exists solely to dodge `npm audit`.

Simpler path: restore semantic-release, @semantic-release/changelog,
@semantic-release/git in devDependencies; drop the .release/
subpackage and the Dependabot carve-out for it; revert the release
workflow to `npx semantic-release`. We accept Vulnerabilities 7 while
npm/cli#9194 lands (npm 11.13.0 bumps the bundled brace-expansion to
5.0.5 and picomatch to 4.0.4; release PR npm/cli#9240 open since
2026-04-15, historical merge-to-publish window 2-9 days).

Kept from the earlier scorecard sweep:
- Dockerfile multistage build using `npm ci` (Pinned-Dependencies 10)
- Branch protection: ruleset requires PR, no admin bypass
- fast-check pin at 4.6.0 (TS strict-build incompat in 4.7.0)

When npm 11.13.x lands, Dependabot will open the bump PR automatically
and Vulnerabilities goes back to 10 with zero manual changes here.
bitbucket-mcp-bot Bot pushed a commit that referenced this pull request Apr 17, 2026
## [0.6.7](v0.6.6...v0.6.7) (2026-04-17)

### Reverts

* restore semantic-release in devDependencies ([8c6e172](8c6e172)), closes [#34](#34) [#35](#35) [npm/cli#9194](npm/cli#9194) [npm/cli#9240](npm/cli#9240)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released Shipped in a release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant