Skip to content

Commit 205c02d

Browse files
Chore/deps audit fix and claude md (#378)
* fix(deps): resolve high-severity npm audit findings brace-expansion, form-data, and js-yaml carried fixable HIGH-severity advisories (DoS via {} expansion, CRLF injection in multipart fields, and quadratic-complexity YAML merge-key DoS). The devsecops-pipeline Trivy FS scan is fail-closed on fixable CRITICAL/HIGH, so these would have broken the gate. Also pin uuid to ^11.1.1 via an override to clear the remaining moderate finding (missing buffer bounds check), pulled in transitively through mochawesome. mochawesome only calls uuid.v4() with no buffer argument, so the vulnerable code path was never reachable, and v4() is stable from uuid v8 through v11. npm audit now reports zero vulnerabilities; full test suite (28 passing) and coverage verified green after the bump. * docs: add CLAUDE.md with repo conventions and fail-closed gate rules Captures what isn't derivable from a quick skim: c8 (not nyc.config.json or test/mocha.opts) is the authoritative coverage config, ESM-only with required OPENWEATHER_API_KEY, the mandatory security headers in startServer, and the fail-closed security gates (npm audit, Trivy FS/image scans, Snyk + license allowlist) that block the pipeline on fixable high/critical findings. Also documents the SHA-pinning + version-comment convention for GitHub Actions and the build-once/scan/push-by-digest/ cosign-sign provenance chain, so future changes don't silently break them. * fix(docker): bump chainguard/node base images to patch node-tar DoS CVEs The Trivy image-scan gate blocked the pipeline on 3 HIGH-severity CVEs (CVE-2026-59871, CVE-2026-59873, CVE-2026-59874) in node-tar, bundled inside the base image's npm package (11.18.0-r0). Fixed upstream in npm 12.0.0-r1. Re-pin both stages to the current patched Chainguard digests, verified by inspecting each image's SBOM layer (var/lib/db/sbom/npm-12.0.1-r1.spdx.json): latest-dev: 7137fadb -> f29c0060 latest: 30bac16e -> c002402b * ci: pin Trivy scanner to v0.72.0 across all trivy-action steps trivy-action v0.36.0 (already our latest pin) defaults its bundled Trivy CLI to v0.70.0 regardless, so every scan step surfaced an "update available" notice. Explicitly set version: v0.72.0 on all 4 usages (FS gate, image SARIF, image gate, SBOM) via the action's own `version` input rather than waiting on a new trivy-action release. Checked v0.72.0's changelog: its one breaking change (migrating Trivy's own release pipeline to GoReleaser's dockers_v2) only affects how the aquasecurity/trivy project publishes its multi-arch Docker images, not the CLI flags or scan behavior this workflow relies on. * ci: audit and refresh all workflow action pins Systematically verified every third-party action pin across all 8 workflows (SHA resolves to the exact tag claimed in its trailing comment, cross-checked against each project's tags/releases via git ls-remote and the GitHub API). Found and fixed: - step-security/harden-runner: v2.19.4 -> v2.20.0 (14 occurrences, all 8 files). Additive-only release (new runner/monitoring support), no behavior change for our egress-policy: audit usage. - github/codeql-action: re-pin the "v4" floating tag from a June commit to the current tip (4 occurrences: init + analyze in codeql.yml, upload-sarif in devsecops-pipeline.yaml and scorecard.yml). Our pin was 397 commits / ~7 weeks behind, missing several CodeQL CLI/query-pack releases (up to v4.37.3). - actions/setup-java: re-pin "v5" from a January commit to the current tip. ~6 months / 72 commits behind, including its own npm-audit vulnerability fix (fast-xml-builder/fast-xml-parser, actions/setup-java#1015). - snyk/actions/node: corrected a stale/misleading comment. The pinned SHA was never actually tracking the master branch tip (contra the "# master" comment) -- it resolves to the tagged v1.0.0 release. Fixed the comment to "# v1.0.0"; the SHA itself was already correct. Also confirmed: no pull_request_target usage anywhere, and no untrusted event data (PR title/body, head_ref, etc.) interpolated into shell run: blocks -- the handful of ${{ github.event.* }} usages are all integers/enums, not attacker-controlled free text. --------- Co-authored-by: Antigravity AI <antigravity-ai@users.noreply.github.com>
1 parent f6003ef commit 205c02d

12 files changed

Lines changed: 146 additions & 53 deletions

.github/workflows/actionlint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
steps:
3030
# ✅ Hardened: SHA Pinning untuk checkout
3131
- name: Harden the runner (Audit all outbound calls)
32-
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
32+
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
3333
with:
3434
egress-policy: audit
3535

.github/workflows/codeql.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,22 @@ jobs:
4343
steps:
4444
# ✅ REMEDIATION 2: Upgraded to v5 (Native Node 24 support)
4545
- name: Harden the runner (Audit all outbound calls)
46-
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
46+
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
4747
with:
4848
egress-policy: audit
4949

5050
- name: Checkout repository
5151
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
5252

5353
- name: Initialize CodeQL
54-
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
54+
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4
5555
with:
5656
languages: ${{ matrix.language }}
5757
build-mode: ${{ matrix.build-mode }}
5858
queries: security-extended,security-and-quality
5959

6060
# ✅ REMEDIATION 3: Upgraded to CodeQL Action v4
6161
- name: Perform CodeQL Analysis
62-
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
62+
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4
6363
with:
6464
category: "/language:${{matrix.language}}"

.github/workflows/dependency-review.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
runs-on: ubuntu-latest
1818
steps:
1919
- name: Harden the runner (Audit all outbound calls)
20-
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
20+
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
2121
with:
2222
egress-policy: audit
2323

.github/workflows/devsecops-pipeline.yaml

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
contents: read
6868
steps:
6969
- name: Harden the runner (Audit all outbound calls)
70-
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
70+
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
7171
with:
7272
egress-policy: audit
7373

@@ -83,7 +83,7 @@ jobs:
8383
cache: npm
8484

8585
- name: Setup Java 21
86-
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
86+
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5
8787
with:
8888
distribution: 'temurin'
8989
java-version: '21'
@@ -117,7 +117,7 @@ jobs:
117117
contents: read
118118
steps:
119119
- name: Harden the runner (Audit all outbound calls)
120-
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
120+
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
121121
with:
122122
egress-policy: audit
123123

@@ -141,7 +141,7 @@ jobs:
141141
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
142142

143143
- name: Snyk Scan
144-
uses: snyk/actions/node@9adf32b1121593767fc3c057af55b55db032dc04 # master
144+
uses: snyk/actions/node@9adf32b1121593767fc3c057af55b55db032dc04 # v1.0.0
145145
continue-on-error: true # we capture the report; the gate step below decides pass/fail
146146
env:
147147
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
@@ -185,6 +185,9 @@ jobs:
185185
# Relax to `exit-code: 0` (report-only) if this proves too strict for your repo.
186186
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
187187
with:
188+
# trivy-action v0.36.0 defaults to Trivy CLI v0.70.0; pin the scanner itself
189+
# to the current release so vuln/DB coverage doesn't silently lag.
190+
version: v0.72.0
188191
scan-type: fs
189192
scan-ref: .
190193
format: table
@@ -218,7 +221,7 @@ jobs:
218221
security-events: write
219222
steps:
220223
- name: Harden the runner (Audit all outbound calls)
221-
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
224+
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
222225
with:
223226
egress-policy: audit
224227

@@ -238,14 +241,15 @@ jobs:
238241
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
239242
continue-on-error: true
240243
with:
244+
version: v0.72.0
241245
scan-type: image
242246
image-ref: ${{ env.LOCAL_IMAGE }}
243247
format: sarif
244248
output: trivy-results.sarif
245249
severity: CRITICAL,HIGH
246250

247251
- name: Upload SARIF to Security Tab
248-
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
252+
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4
249253
if: hashFiles('trivy-results.sarif') != ''
250254
with:
251255
sarif_file: trivy-results.sarif
@@ -255,6 +259,7 @@ jobs:
255259
- name: Trivy Image Scan (ENFORCING gate — fail-closed)
256260
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
257261
with:
262+
version: v0.72.0
258263
scan-type: image
259264
image-ref: ${{ env.LOCAL_IMAGE }}
260265
format: table
@@ -267,6 +272,7 @@ jobs:
267272
- name: Generate SBOM (CycloneDX)
268273
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
269274
with:
275+
version: v0.72.0
270276
scan-type: image
271277
image-ref: ${{ env.LOCAL_IMAGE }}
272278
format: cyclonedx
@@ -307,7 +313,7 @@ jobs:
307313
id-token: write # keyless cosign via GitHub OIDC
308314
steps:
309315
- name: Harden the runner (Audit all outbound calls)
310-
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
316+
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
311317
with:
312318
egress-policy: audit
313319

@@ -416,7 +422,7 @@ jobs:
416422
issues: write
417423
steps:
418424
- name: Harden the runner (Audit all outbound calls)
419-
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
425+
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
420426
with:
421427
egress-policy: audit
422428

@@ -436,7 +442,7 @@ jobs:
436442
# Readiness probe instead of a blind sleep.
437443
for i in $(seq 1 30); do
438444
if curl -sf http://localhost:3000 >/dev/null 2>&1; then
439-
echo "App is up."; break
445+
echo "App is up (attempt $i)."; break
440446
fi
441447
sleep 2
442448
done
@@ -468,7 +474,7 @@ jobs:
468474
contents: read
469475
steps:
470476
- name: Harden the runner (Audit all outbound calls)
471-
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
477+
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
472478
with:
473479
egress-policy: audit
474480

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
# 1) SETUP & CHECKOUT
2222
# =============================
2323
- name: Harden the runner (Audit all outbound calls)
24-
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
24+
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
2525
with:
2626
egress-policy: audit
2727

.github/workflows/scorecard.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424

2525
steps:
2626
- name: Harden the runner (Audit all outbound calls)
27-
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
27+
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
2828
with:
2929
egress-policy: audit
3030

@@ -60,7 +60,7 @@ jobs:
6060

6161
steps:
6262
- name: Harden the runner (Audit all outbound calls)
63-
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
63+
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
6464
with:
6565
egress-policy: audit
6666

@@ -82,6 +82,6 @@ jobs:
8282
run: sed -i 's/no file associated with this alert/./g' results.sarif
8383

8484
- name: "Upload to code-scanning"
85-
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
85+
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4
8686
with:
8787
sarif_file: results.sarif

.github/workflows/secret-scanner.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919

2020
steps:
2121
- name: Harden the runner (Audit all outbound calls)
22-
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
22+
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
2323
with:
2424
egress-policy: audit
2525

.github/workflows/security-self-healing.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
if: ${{ github.event.workflow_run.conclusion == 'success' }}
2525
steps:
2626
- name: Harden the runner (Audit all outbound calls)
27-
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
27+
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
2828
with:
2929
egress-policy: audit
3030

CLAUDE.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# CLAUDE.md
2+
3+
Guidance for Claude Code (and humans) working in this repository.
4+
5+
## What this is
6+
7+
A **DevSecOps reference implementation**: a small Node.js weather app whose real product is the *secure software-supply-chain pipeline* around it. When making changes, treat the security pipeline as a first-class part of the codebase — a change that breaks a security gate is a broken change, even if the app still runs.
8+
9+
- **Runtime:** Node.js `>=24`, **ESM only** (`"type": "module"`).
10+
- **App:** a CLI + minimal `http` server (`/`, `/health`, `/weather`, `/robots.txt`) that fetches OpenWeather data and prints "what to prepare for" recommendations.
11+
12+
## Commands
13+
14+
```bash
15+
npm ci # install exactly from package-lock.json (what CI uses)
16+
npm test # mocha → xunit.xml (SonarQube reporter). Sets NODE_ENV + a dummy API key via cross-env
17+
npm run cover # c8 coverage (text/lcov/html) → coverage/ ; runs coverage-makeover.js after
18+
npm start # node app.js (alias: npm run dev)
19+
npm run start:secure # AIKIDO_BLOCK=true node app.js (Aikido/Zen firewall in blocking mode)
20+
npm audit # MUST stay clean of high/critical — see "Fail-closed gates" below
21+
```
22+
23+
There is no lint script wired into npm; ESLint runs via `.pre-commit-config.yaml` (install [pre-commit](https://pre-commit.com) and run `pre-commit run --all-files`). Gitleaks also runs there.
24+
25+
## Layout
26+
27+
Source lives at the repo **root** (there is no `src/`):
28+
29+
| File | Role |
30+
|---|---|
31+
| `app.js` | Entry point. Exports `run`, `startServer`, `shutdown` for testability; only calls `server.listen` when `NODE_ENV !== 'test'`. Sets security headers on every response. |
32+
| `fetch-weather.js` | OpenWeather fetch + pure helpers (`kelvinToCelsius`, `getVolume`, `processResults`). Prefers a global `fetch` stub (tests) and falls back to `node-fetch`. |
33+
| `prepared-for-the-weather.js` | `doINeed.*` recommendation predicates (umbrella, suncream, etc.). |
34+
| `scripts/coverage-makeover.js` | Post-coverage cosmetic step that restyles the HTML report. |
35+
| `test/` | Mocha specs: `*-test.js` and `*.test.js` (Mocha, Chai, Sinon, `chai-as-promised`, plus `fuzz-test.js` using **fast-check** property testing). |
36+
| `test-data/` | JSON/JS fixtures for the OpenWeather response shape. |
37+
38+
## Conventions
39+
40+
- **ESM everywhere.** Use `import`/`export` with explicit `.js` extensions. No `require`, no CommonJS.
41+
- **Env-driven config, never hardcoded secrets.** `OPENWEATHER_API_KEY` is **required**`fetch-weather.js` throws at import time if it is missing. Other vars: `DEFAULT_LOCATION`, `PORT` (default `3000`), `NODE_ENV`, `AIKIDO_BLOCK`. `.env` is gitignored; tests inject a dummy key via `cross-env`.
42+
- **Security headers** are set on every HTTP response in `startServer` (CSP, `X-Content-Type-Options`, HSTS, etc.). Keep them when touching the server.
43+
- `// NOSONAR` comments on `console.*` lines are intentional SonarCloud suppressions — leave them.
44+
- **Coverage is `c8`**, configured in the `c8` block of `package.json` (that block is authoritative). `nyc.config.json` and `test/mocha.opts` are legacy leftovers — don't rely on them.
45+
- Commit messages follow **Conventional Commits** (`ci:`, `fix(docker):`, `fix:` …), matching the existing history.
46+
47+
## Fail-closed security gates — do not break these
48+
49+
The pipeline is designed to **fail closed**. Before committing dependency or Dockerfile changes, know that:
50+
51+
1. **`npm audit` must have zero high/critical.** The Trivy filesystem scan (`.github/workflows/devsecops-pipeline.yaml`) runs with `ignore-unfixed: true` + `exit-code: 1`, so any *fixable* HIGH/CRITICAL in `package-lock.json` fails the build. Prefer `npm audit fix`; use `overrides` in `package.json` for transitive pins that a direct bump can't reach.
52+
2. **Trivy image scan is also enforcing** (same flags) on the built image. Keep base images patched.
53+
3. **Snyk** gates high/critical vulns *and* a license allowlist (`MIT | Apache-2.0 | BSD-2-Clause | BSD-3-Clause | ISC`) on pushes to `master`.
54+
4. **Provenance chain is build-once:** image is built a single time, scanned, pushed **by digest**, then Cosign **keyless (OIDC)** signs the digest, verifies it, and attests the CycloneDX SBOM. Never rebuild an image between scan and ship, and never sign a mutable `:tag`.
55+
56+
## GitHub Actions rules
57+
58+
- **Every third-party action is pinned to a full commit SHA** with the human-readable version in a trailing comment, e.g. `uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3`. When bumping an action, update **both** the SHA and the comment. Dependabot (`.github/dependabot.yml`, `ci-actions` group) does this automatically — match its style in manual edits.
59+
- **Least privilege:** top-level `permissions: contents: read`; grant extra scopes only on the specific job that needs them.
60+
- **`step-security/harden-runner` (egress `audit`) is the first step of every job.** Keep it there.
61+
- Run `actionlint` before pushing workflow edits (CI runs it via `actionlint.yml`).
62+
63+
## Docker
64+
65+
- `Dockerfile` is a multi-stage build on **Chainguard** distroless images pinned **by digest**, runs as non-root (`USER node`), and makes the app read-only (`chmod 550/440`). Preserve these hardening properties.
66+
- `Dockerfile.dev` (node-alpine, nodemon) is for local dev only.
67+
68+
## Gotchas
69+
70+
- **`xunit.xml` and `coverage/` are generated artifacts.** `npm test` rewrites `xunit.xml` on every run (timestamps). `xunit.xml` is tracked, so revert incidental churn (`git checkout -- xunit.xml`) before committing unrelated work; `coverage/` is gitignored.
71+
- The `README.md` "Repository Structure" section is out of date (it lists test filenames and a `fix-mocha-exit.js` that don't exist). Trust the actual tree over the README.
72+
- Never commit `.env`, `*.pem`, `*.key`, `*.sig`, or `security-reports/` — all gitignored by design.

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# =================================================================
22
# STAGE 1: Build & Dependencies
33
# =================================================================
4-
FROM cgr.dev/chainguard/node:latest-dev@sha256:7137fadb43a7cee4a01495749973f3c8c44a1d02319d9beb543bdb21cb6e874e AS builder
4+
FROM cgr.dev/chainguard/node:latest-dev@sha256:f29c00607fd8fa702c91856895fd8819cd62c83bbe3a2195540abe05359fdf54 AS builder
55

66
# Set context ke user node sejak awal
77
WORKDIR /app
@@ -20,7 +20,7 @@ RUN npm prune --production
2020
# =================================================================
2121
# STAGE 2: Hardened Runtime
2222
# =================================================================
23-
FROM cgr.dev/chainguard/node:latest@sha256:30bac16e19a580ed7feb991876e89d45f96eead87518df4bce46e597336c6a18
23+
FROM cgr.dev/chainguard/node:latest@sha256:c002402b355201714dfbeb3b1a7ca99152cccced4b334496db2442a3a9bf4883
2424

2525
WORKDIR /app
2626

0 commit comments

Comments
 (0)