Skip to content

Commit a220efa

Browse files
paradoxboundParadoxboundclaude
authored
Security: npm audit, Dependabot, Trivy, CodeQL, OSV Scanner + Node 24 + v2.5.2 (#20)
## Summary Adds free GitHub security scanning to the pipeline and upgrades the runtime to Node 24. Releases as **v2.5.2**. ### Security scanning (new) - **`npm audit`** — added to `functional-tests.yml` after `npm ci`. Blocks the `test` check on HIGH or CRITICAL CVEs using the npm advisory database. - **OSV Scanner** (`google/osv-scanner-action@v2.3.3`) — added to `functional-tests.yml` after `npm audit`. Scans all `package-lock.json` files recursively against the OSV/NIST database — the same source Trivy uses for npm packages — so vulnerabilities are caught at the fast test stage rather than waiting for the full Docker build and image scan. - **Dependabot** — new `.github/dependabot.yml`. Daily checks for npm packages across all three package roots (`/`, `/packages/core`, `/packages/stdio`) and all GitHub Actions versions. Security alerts are real-time regardless of schedule. - **Trivy image scanning** — added to `docker-publish.yml` in two places: - Inside `pre-merge-cd-check` (PR gate): scans `:pr-{n}-amd64`; CRITICAL+HIGH reported to Security tab via SARIF; CRITICAL with a fix available blocks the PR. - New `scan` job between `verify` and `merge` (post-merge gate): scans `:latest-amd64` before manifest tags and git tag are created; CRITICAL blocks the release. - **CodeQL** — new `.github/workflows/codeql.yml`. Semantic TypeScript analysis (`language: javascript`, `build-mode: none`). Runs on PRs, push to main, and weekly. ### Bug fix: Trivy env-var pollution `trivy-action` exports `TRIVY_FORMAT` and `TRIVY_OUTPUT` into `$GITHUB_ENV`. When the blocking step specifies `format: table` (the action default), the action skips overwriting those vars, so the SARIF step values bleed in — causing the blocking step to run in SARIF mode and fail on HIGH findings instead of CRITICAL only. **Fix:** replaced both "Fail on CRITICAL" `trivy-action` steps with plain `run:` steps invoking the `trivy` binary directly. CLI flags always override environment variables. ### Node 24 upgrade - Docker base image upgraded from `node:20-alpine` to `node:24-alpine`. Node 20 reaches EOL April 2026; Node 24 has been Active LTS since October 2025. Node 24 ships with npm 11 which has updated bundled dependencies, resolving the HIGH CVEs Trivy was detecting in the Docker image (`tar`, `glob`, `minimatch`, `cross-spawn`). - CI `node-version` updated from `20` to `24` to match. ### Dependency fixes - **HIGH** (npm audit fix): patched `@modelcontextprotocol/sdk` (GHSA-345p-7cg4-v4c7) and `rollup` (GHSA-mw96-cpmx-2vgc). - **MODERATE** (OSV Scanner / npm audit fix --force): upgraded `vitest` `3.x` → `4.x` to resolve the `esbuild`/`vite`/`vitest` chain (GHSA-67mh-4wv8-2f99). Build and type-check pass cleanly on vitest v4. - `ignore-unfixed: true` on all Trivy steps — OS-level Alpine CVEs with no patch available do not block the pipeline. ## Post-merge: GitHub Settings to enable - **Branch protection → required checks:** Add `CodeQL` to required status checks - **Security → Secret scanning → Push protection:** Enable to reject pushes containing detected secrets at the server level (free for public repos, no workflow change needed) ## Test plan - [x] `npm audit` passes — 0 vulnerabilities - [x] OSV Scanner passes — 0 vulnerabilities after vitest v4 upgrade - [x] Trivy blocking step uses `run:` to avoid env-var pollution - [x] `pre-merge-cd-check` Trivy SARIF uploaded to Security tab - [x] CodeQL analysis passes - [x] Build and type-check clean on Node 24 / vitest v4 - [ ] After merge: `scan` job runs between `verify` and `merge` - [ ] After merge: v2.5.2 Docker tags published correctly 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Paradoxbound <paradoxbound@paradoxbound.org> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f61f8ef commit a220efa

8 files changed

Lines changed: 715 additions & 601 deletions

File tree

.github/dependabot.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
version: 2
2+
3+
updates:
4+
# Root workspace (covers shared devDependencies)
5+
- package-ecosystem: npm
6+
directory: /
7+
schedule:
8+
interval: daily
9+
labels:
10+
- dependencies
11+
12+
# packages/core — BookStack API client
13+
- package-ecosystem: npm
14+
directory: /packages/core
15+
schedule:
16+
interval: daily
17+
labels:
18+
- dependencies
19+
20+
# packages/stdio — MCP stdio server (published package)
21+
- package-ecosystem: npm
22+
directory: /packages/stdio
23+
schedule:
24+
interval: daily
25+
labels:
26+
- dependencies
27+
28+
# GitHub Actions — keep action versions up to date
29+
- package-ecosystem: github-actions
30+
directory: /
31+
schedule:
32+
interval: daily
33+
labels:
34+
- dependencies
35+
- github-actions

.github/workflows/codeql.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: CodeQL
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
schedule:
9+
# Weekly scan on Sunday at midnight UTC — catches newly published CVEs
10+
# against existing code between releases.
11+
- cron: '0 0 * * 0'
12+
13+
jobs:
14+
analyze:
15+
name: CodeQL
16+
runs-on: ubuntu-latest
17+
permissions:
18+
contents: read
19+
security-events: write
20+
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v4
24+
25+
# GitHub uses "javascript" as the CodeQL language identifier for both
26+
# JavaScript and TypeScript. build-mode: none means CodeQL analyses
27+
# the source directly without needing a compilation step.
28+
- name: Initialize CodeQL
29+
uses: github/codeql-action/init@v3
30+
with:
31+
languages: javascript
32+
build-mode: none
33+
34+
- name: Perform CodeQL Analysis
35+
uses: github/codeql-action/analyze@v3
36+
with:
37+
category: codeql

.github/workflows/docker-publish.yml

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,60 @@ jobs:
122122
echo "arm64_digest=${ARM64_DIGEST}" >> "$GITHUB_OUTPUT"
123123
124124
# ─────────────────────────────────────────────────────────────────────────────
125-
# JOB 3 — merge
125+
# JOB 3 — scan
126+
# Scans the verified amd64 image for vulnerabilities before the release tags
127+
# and git tag are created. CRITICAL findings block the release.
128+
# HIGH findings are reported to the Security tab but do not block.
129+
# ─────────────────────────────────────────────────────────────────────────────
130+
scan:
131+
runs-on: ubuntu-latest
132+
needs: verify
133+
if: github.event_name != 'pull_request'
134+
permissions:
135+
contents: read
136+
packages: read
137+
security-events: write
138+
139+
steps:
140+
- name: Log in to GitHub Container Registry
141+
uses: docker/login-action@v3
142+
with:
143+
registry: ${{ env.REGISTRY }}
144+
username: ${{ github.actor }}
145+
password: ${{ secrets.GITHUB_TOKEN }}
146+
147+
- name: Scan image for vulnerabilities (Trivy)
148+
uses: aquasecurity/trivy-action@0.30.0
149+
with:
150+
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-amd64
151+
format: sarif
152+
output: trivy-release.sarif
153+
severity: CRITICAL,HIGH
154+
ignore-unfixed: true
155+
exit-code: '0'
156+
157+
- name: Upload Trivy SARIF to Security tab
158+
uses: github/codeql-action/upload-sarif@v3
159+
if: always()
160+
with:
161+
sarif_file: trivy-release.sarif
162+
category: trivy-release
163+
164+
# Run trivy directly so CLI flags override any TRIVY_* env vars leaked
165+
# from the SARIF step above (trivy-action exports env vars that persist
166+
# across steps; a second trivy-action invocation cannot fully override them).
167+
- name: Fail release on CRITICAL vulnerabilities
168+
run: |
169+
trivy image \
170+
--format table \
171+
--severity CRITICAL \
172+
--ignore-unfixed \
173+
--exit-code 1 \
174+
--cache-dir .cache/trivy \
175+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-amd64
176+
177+
# ─────────────────────────────────────────────────────────────────────────────
178+
# JOB 4 — merge
126179
# Creates multi-arch manifest lists from the verified arch images and applies
127180
# all version tags. Version is read from packages/stdio/package.json — the
128181
# canonical, published package — not the private root package.json.
@@ -131,7 +184,7 @@ jobs:
131184
# ─────────────────────────────────────────────────────────────────────────────
132185
merge:
133186
runs-on: ubuntu-latest
134-
needs: verify
187+
needs: scan
135188
if: github.event_name != 'pull_request'
136189
permissions:
137190
contents: write
@@ -283,13 +336,19 @@ jobs:
283336
# images that were pushed during this run so the registry is left in the same
284337
# state as the last successful release.
285338
# ─────────────────────────────────────────────────────────────────────────────
339+
# ─────────────────────────────────────────────────────────────────────────────
340+
# JOB 5 — cleanup
341+
# Runs only when the verify, scan, or merge job fails. Deletes any arch-
342+
# suffixed images that were pushed during this run so the registry is left
343+
# in the same state as the last successful release.
344+
# ─────────────────────────────────────────────────────────────────────────────
286345
cleanup:
287346
runs-on: ubuntu-latest
288-
needs: [verify, merge]
347+
needs: [verify, scan, merge]
289348
if: |
290349
always() &&
291350
github.event_name != 'pull_request' &&
292-
(needs.verify.result == 'failure' || needs.merge.result == 'failure')
351+
(needs.verify.result == 'failure' || needs.scan.result == 'failure' || needs.merge.result == 'failure')
293352
permissions:
294353
contents: read
295354
packages: write
@@ -366,6 +425,7 @@ jobs:
366425
permissions:
367426
contents: read
368427
packages: write
428+
security-events: write
369429

370430
steps:
371431
- name: Checkout
@@ -424,6 +484,39 @@ jobs:
424484
echo "${arch} digest: ${DIGEST}"
425485
done
426486
487+
# Scan the PR amd64 image for vulnerabilities.
488+
# HIGH + CRITICAL are reported to the GitHub Security tab via SARIF.
489+
# CRITICAL findings fail the PR check; HIGH are informational only.
490+
- name: Scan PR image for vulnerabilities (Trivy)
491+
uses: aquasecurity/trivy-action@0.30.0
492+
with:
493+
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:pr-${{ github.event.pull_request.number }}-amd64
494+
format: sarif
495+
output: trivy-pr.sarif
496+
severity: CRITICAL,HIGH
497+
ignore-unfixed: true
498+
exit-code: '0'
499+
500+
- name: Upload Trivy SARIF to Security tab
501+
uses: github/codeql-action/upload-sarif@v3
502+
if: always()
503+
with:
504+
sarif_file: trivy-pr.sarif
505+
category: trivy-pr
506+
507+
# Run trivy directly so CLI flags override any TRIVY_* env vars leaked
508+
# from the SARIF step above (trivy-action exports env vars that persist
509+
# across steps; a second trivy-action invocation cannot fully override them).
510+
- name: Fail PR on CRITICAL vulnerabilities
511+
run: |
512+
trivy image \
513+
--format table \
514+
--severity CRITICAL \
515+
--ignore-unfixed \
516+
--exit-code 1 \
517+
--cache-dir .cache/trivy \
518+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:pr-${{ github.event.pull_request.number }}-amd64
519+
427520
# Create a test multi-arch manifest and confirm it is pullable.
428521
- name: Create and verify PR test manifest
429522
run: |

.github/workflows/functional-tests.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,20 @@ jobs:
1717

1818
- uses: actions/setup-node@v4
1919
with:
20-
node-version: 20
20+
node-version: 24
2121

2222
- run: npm ci
2323

24+
- name: Audit dependencies (npm)
25+
run: npm audit --audit-level=high
26+
27+
- name: Scan dependencies (OSV)
28+
uses: google/osv-scanner-action/osv-scanner-action@v2.3.3
29+
with:
30+
scan-args: |-
31+
--recursive
32+
./
33+
2434
- name: Build
2535
run: npm run build
2636

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM node:20-alpine AS build
1+
FROM node:24-alpine AS build
22

33
WORKDIR /app
44

@@ -11,7 +11,7 @@ COPY packages/core/ packages/core/
1111
COPY packages/stdio/ packages/stdio/
1212
RUN npm run build
1313

14-
FROM node:20-alpine
14+
FROM node:24-alpine
1515

1616
WORKDIR /app
1717

0 commit comments

Comments
 (0)