Skip to content

Add supply-chain controls, audit policy, and release checklist - #701

Merged
ritik4ever merged 1 commit into
ritik4ever:mainfrom
floxxih:fix/devops-supply-chain-ops
May 30, 2026
Merged

Add supply-chain controls, audit policy, and release checklist#701
ritik4ever merged 1 commit into
ritik4ever:mainfrom
floxxih:fix/devops-supply-chain-ops

Conversation

@floxxih

@floxxih floxxih commented May 30, 2026

Copy link
Copy Markdown

Summary:

  • Attest frontend/backend build bundles and upload SBOM artifacts in CI.
  • Add a repo-wide npm audit baseline with a CI gate and maintainer update command.
  • Add a reusable release checklist template and a contract Makefile helper.

Verification:

  • npm run audit:policy

Closes #545
Closes #546
Closes #544
Closes #547

Summary by CodeRabbit

  • New Features

    • Added automated supply-chain security artifacts including SBOMs and provenance attestations
    • Implemented npm audit policy validation with configurable baseline thresholds to track dependency vulnerabilities
  • Documentation

    • Added structured release checklist template with security and deployment verification steps
    • Enhanced operations and contribution guides with supply-chain artifact guidance and vulnerability baseline procedures

Review Change Stack

@vercel

vercel Bot commented May 30, 2026

Copy link
Copy Markdown

@ChaoLing140 is attempting to deploy a commit to the ritik4ever's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented May 30, 2026

Copy link
Copy Markdown

@floxxih Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This pull request implements four interconnected operational improvements: GitHub Actions supply-chain artifact generation with SBOMs and attestations, npm audit baseline enforcement with CI gating, a release checklist template, and supporting documentation for repeatable maintainer workflows across the repository.

Changes

Supply-chain operations and release automation

Layer / File(s) Summary
Build workflow: supply-chain artifacts and SBOMs
.github/workflows/build.yml, docs/OPERATIONS.md
Build workflow gains explicit OIDC permissions and a full artifact pipeline: builds frontend/backend distributions, generates CycloneDX SBOMs for all components, creates GitHub attestations for bundle and SBOM provenance, and uploads artifacts with strict validation. Operations handbook documents artifact outputs and maintainer verification procedures.
NPM audit baseline enforcement script
scripts/check-npm-audit-baseline.mjs
New CLI script validates npm audit vulnerability counts per configured scope against a baseline file, logs detailed deltas when severities exceed limits, conditionally updates baseline on pass, persists refreshed state with timestamp, and exits with failure code if enforcement fails.
NPM audit baseline CI and documentation
.github/workflows/npm-audit-policy.yml, package.json, backend/package.json, docs/CONTRIBUTING.md, security/npm-audit-baseline.json
New GitHub Actions workflow enforces npm audit policy on pull requests when package files or baseline change. Package scripts register audit:policy and audit:policy:update commands. CONTRIBUTING.md documents the policy, exception handling, and baseline update rules. Baseline file records current vulnerability counts per scope with generation timestamp. Section numbering is updated throughout CONTRIBUTING.md to accommodate the new audit policy section.
Release checklist template and contract build helper
docs/RELEASE_CHECKLIST.md, contracts/Makefile
New release checklist markdown defines sequential sections for metadata, supply-chain verification, contract validation, backend deployment, frontend rollout, and post-release verification. Contracts Makefile registers a release-checklist target that outputs the checklist path and numbered process steps.
Changelog documentation
CHANGELOG.md
Unreleased section documents three new operational features: build attestations and SBOM artifacts, npm audit baseline with CI policy, and release checklist with contract Makefile helper.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐰 New workflows bloom in spring,
SBOMs and audits align,
Checklists keep us whole—
Release safe, ship clean.
Ops automation takes the stage! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the three main changes: supply-chain controls (attestations and SBOMs), audit policy (npm baseline), and release checklist, matching the changeset content.
Linked Issues check ✅ Passed All four linked issues (#545, #546, #544, #547) are comprehensively addressed: artifact attestations with provenance (build.yml), SBOM generation for frontend/backend/contracts, release checklist template (RELEASE_CHECKLIST.md, Makefile), and npm audit baseline with CI gate and automation (scripts, workflows, docs).
Out of Scope Changes check ✅ Passed All changes are directly scoped to the four linked issues: workflow updates, SBOM/attestation tooling, audit baseline automation, release checklists, and documentation are all within the supply-chain controls and audit policy objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/build.yml:
- Around line 47-51: The workflow step named "Install Rust SBOM tooling"
currently installs the floating `stable` toolchain and installs `cargo-sbom`
without a fixed version; change that step to pin Rust to a specific toolchain
release (e.g., use a rust-toolchain.toml or replace `rustup toolchain install
stable`/`rustup default stable` with a concrete toolchain like `1.72.1`), and
install cargo-sbom with an explicit version by adding `--version <x.y.z>` to the
`cargo install cargo-sbom --locked` command so SBOM builds are reproducible.
- Around line 64-90: The workflow currently references actions/attest@v4 and
actions/upload-artifact@v4 by tag; update each uses: entry (all occurrences of
"uses: actions/attest@v4" and "uses: actions/upload-artifact@v4") to the
corresponding full commit SHA (e.g., actions/attest@<sha> and
actions/upload-artifact@<sha>) to pin to an immutable revision; fetch the
canonical commit SHAs from the respective GitHub action repos/releases, replace
the tag references for the four attest steps and the upload step, and keep the
existing with: inputs unchanged.

In @.github/workflows/npm-audit-policy.yml:
- Around line 34-38: Replace floating action tags actions/checkout@v4 and
actions/setup-node@v4 with their respective pinned commit SHAs or digests (e.g.,
actions/checkout@<full-sha> and actions/setup-node@<full-sha>) and add
persist-credentials: false to the checkout step (the actions/checkout
invocation) to disable credential persistence; keep the node-version: 20 input
on the setup-node step when pinning.

In `@docs/OPERATIONS.md`:
- Around line 117-120: Update the "Verification" section to include an exact,
maintainer-ready command and the identity checks to run; add a sample invocation
using GitHub CLI's attestation verify (e.g., "gh attestation verify <artifact>
--attestation <attestation-file> --source <repo>@<workflow>@<commit>") and list
the specific checks to perform: confirm artifact name and version match, verify
the attestation subject matches the artifact digest, and validate the
attestation provenance (repository, workflow name, and commit SHA) against the
expected repo/workflow/commit values produced by CI so a maintainer can
copy-paste and run the verification reproducibly.

In `@scripts/check-npm-audit-baseline.mjs`:
- Around line 91-95: The current writeBaseline block unconditionally updates
security/npm-audit-baseline.json (using writeFileSync with baselinePath and
nextBaseline.generatedAt) even when some scope exceeded the allowed baseline,
producing a partial/incorrect baseline; change the logic so the file is written
only when the overall run succeeded (no scopes exceeded or no failures) by
gating the write behind the success condition (e.g., check the existing
boolean/flag that indicates "all scopes within baseline" or introduce one like
allScopesWithinBaseline) before calling writeFileSync and setting
nextBaseline.generatedAt; apply the same guarded-write change to the other write
site referenced (the lines around 97-99) so the baseline is never updated on
failed runs.
- Around line 34-57: The current audit parsing treats missing vulnerability
metadata as zeros; change this to fail-fast by validating the spawn result and
parsed report before computing counts: after calling spawnSync('npm'...) check
result.error and that result.status === 0 (or result.signal) and throw if the
subprocess failed or timed out; after JSON.parse, if report.error exists or
report.metadata?.vulnerabilities is falsy, throw an informative Error for
scopeName including the raw output; only then iterate severities to build
counts. Reference symbols: spawnSync call, result, output, report,
report.metadata?.vulnerabilities, severities, counts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 461e2272-8cd7-422e-a329-564e86e67ece

📥 Commits

Reviewing files that changed from the base of the PR and between 23b93a5 and 2fe3e61.

📒 Files selected for processing (11)
  • .github/workflows/build.yml
  • .github/workflows/npm-audit-policy.yml
  • CHANGELOG.md
  • backend/package.json
  • contracts/Makefile
  • docs/CONTRIBUTING.md
  • docs/OPERATIONS.md
  • docs/RELEASE_CHECKLIST.md
  • package.json
  • scripts/check-npm-audit-baseline.mjs
  • security/npm-audit-baseline.json

Comment on lines +47 to +51
- name: Install Rust SBOM tooling
run: |
rustup toolchain install stable --profile minimal
rustup default stable
cargo install cargo-sbom --locked

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the workflow section around the referenced lines
echo "=== build.yml lines 1-120 (for context) ==="
nl -ba .github/workflows/build.yml | sed -n '1,120p'

# Confirm exactly how cargo-sbom is installed
echo "=== Search for cargo-sbom install / usage ==="
rg -n --hidden --glob '!.git/*' "cargo-sbom|rustup toolchain install|rust-toolchain" .

# Check for any checked-in Rust toolchain files
echo "=== Look for rust-toolchain files ==="
ls -la rust-toolchain rust-toolchain.toml .tool-versions 2>/dev/null || true

Repository: ritik4ever/stellar-portfolio-rebalancer

Length of output: 188


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the workflow section around the referenced lines (no nl available)
echo "=== build.yml lines 1-120 (for context) ==="
awk 'NR<=120{printf "%6d  %s\n", NR, $0}' .github/workflows/build.yml

echo "=== Search for cargo-sbom install / usage ==="
rg -n --hidden --glob '!.git/*' "cargo-sbom|rustup toolchain install|rust-toolchain|toolchain" .

echo "=== Look for rust-toolchain / tool-versions files ==="
ls -la rust-toolchain rust-toolchain.toml .tool-versions 2>/dev/null || true

Repository: ritik4ever/stellar-portfolio-rebalancer

Length of output: 4341


Pin Rust SBOM tooling versions for reproducible builds.

Lines 47-51 install the moving stable Rust toolchain and run cargo install cargo-sbom --locked without pinning a specific cargo-sbom version, so SBOM output can drift across runs. Pin Rust to an exact release (e.g., rust-toolchain.toml or a fixed toolchain in the workflow) and install cargo-sbom with --version <...>.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build.yml around lines 47 - 51, The workflow step named
"Install Rust SBOM tooling" currently installs the floating `stable` toolchain
and installs `cargo-sbom` without a fixed version; change that step to pin Rust
to a specific toolchain release (e.g., use a rust-toolchain.toml or replace
`rustup toolchain install stable`/`rustup default stable` with a concrete
toolchain like `1.72.1`), and install cargo-sbom with an explicit version by
adding `--version <x.y.z>` to the `cargo install cargo-sbom --locked` command so
SBOM builds are reproducible.

Comment on lines +64 to +90
- name: Attest frontend bundle provenance
uses: actions/attest@v4
with:
subject-path: artifacts/frontend-dist.tar.gz

- name: Attest backend bundle provenance
uses: actions/attest@v4
with:
subject-path: artifacts/backend-dist.tar.gz

- name: Attest frontend SBOM
uses: actions/attest@v4
with:
sbom-path: artifacts/sbom/frontend.cdx.json

- name: Attest backend SBOM
uses: actions/attest@v4
with:
sbom-path: artifacts/sbom/backend.cdx.json

- name: Attest contracts SBOM
uses: actions/attest@v4
with:
sbom-path: artifacts/sbom/contracts.cdx.json

- name: Upload build and supply-chain artifacts
uses: actions/upload-artifact@v4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin actions/attest and actions/upload-artifact to full commit SHAs in this workflow.

In .github/workflows/build.yml (lines 64-90), the uses: steps for actions/attest@v4 and actions/upload-artifact@v4 are only tag-pinned; pin them to the exact commit SHA(s) to prevent supply-chain trust drift.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 65-65: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 70-70: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 75-75: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 80-80: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 85-85: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 90-90: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build.yml around lines 64 - 90, The workflow currently
references actions/attest@v4 and actions/upload-artifact@v4 by tag; update each
uses: entry (all occurrences of "uses: actions/attest@v4" and "uses:
actions/upload-artifact@v4") to the corresponding full commit SHA (e.g.,
actions/attest@<sha> and actions/upload-artifact@<sha>) to pin to an immutable
revision; fetch the canonical commit SHAs from the respective GitHub action
repos/releases, replace the tag references for the four attest steps and the
upload step, and keep the existing with: inputs unchanged.

Comment on lines +34 to +38
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '33,39p' .github/workflows/npm-audit-policy.yml

Repository: ritik4ever/stellar-portfolio-rebalancer

Length of output: 207


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/npm-audit-policy.yml"
nl -ba "$FILE" | sed -n '1,200p'
echo "---- grep persist-credentials ----"
rg -n "persist-credentials|checkout@|setup-node@" "$FILE" || true

Repository: ritik4ever/stellar-portfolio-rebalancer

Length of output: 126


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/npm-audit-policy.yml"

echo "---- file (numbered, first 160 lines) ----"
cat -n "$FILE" | sed -n '1,160p'

echo "---- grep patterns ----"
rg -n "persist-credentials|checkout@|setup-node@" "$FILE" || true

Repository: ritik4ever/stellar-portfolio-rebalancer

Length of output: 1485


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "^\\s*- uses:" .github/workflows/npm-audit-policy.yml

Repository: ritik4ever/stellar-portfolio-rebalancer

Length of output: 159


Pin and harden the workflow actions (checkout).
This workflow uses floating tags (actions/checkout@v4, actions/setup-node@v4) and the checkout step does not set persist-credentials: false. Pin both actions to full commit SHAs/digests and disable credential persistence on checkout.

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 34-34: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 34-34: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 36-36: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/npm-audit-policy.yml around lines 34 - 38, Replace
floating action tags actions/checkout@v4 and actions/setup-node@v4 with their
respective pinned commit SHAs or digests (e.g., actions/checkout@<full-sha> and
actions/setup-node@<full-sha>) and add persist-credentials: false to the
checkout step (the actions/checkout invocation) to disable credential
persistence; keep the node-version: 20 input on the setup-node step when
pinning.

Comment thread docs/OPERATIONS.md
Comment on lines +117 to +120
### Verification

Use GitHub's attestation tooling to verify a downloaded artifact against the repository's published attestations. The workflow stores the attestation on the run; verification is a maintainer task, not a manual build step.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== File preview: docs/OPERATIONS.md (around lines 90-150) ==="
sed -n '90,150p' docs/OPERATIONS.md | cat -n

echo
echo "=== Search: attestation/gh attestation/verify in docs ==="
rg -n --hidden --no-ignore -S "attestation|gh attestation|attestations|verify" docs || true

Repository: ritik4ever/stellar-portfolio-rebalancer

Length of output: 9416


Document the exact artifact attestation verification command and checks

In docs/OPERATIONS.md (Verification section), add the specific maintainer-ready invocation (e.g., gh attestation verify ..., matching what the CI generates) plus the required identity/subject checks to validate (artifact name/version, and the expected repo/workflow/commit identity). [This] keeps the verification step executable and repeatable from the handbook.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/OPERATIONS.md` around lines 117 - 120, Update the "Verification" section
to include an exact, maintainer-ready command and the identity checks to run;
add a sample invocation using GitHub CLI's attestation verify (e.g., "gh
attestation verify <artifact> --attestation <attestation-file> --source
<repo>@<workflow>@<commit>") and list the specific checks to perform: confirm
artifact name and version match, verify the attestation subject matches the
artifact digest, and validate the attestation provenance (repository, workflow
name, and commit SHA) against the expected repo/workflow/commit values produced
by CI so a maintainer can copy-paste and run the verification reproducibly.

Comment on lines +34 to +57
const result = spawnSync('npm', ['audit', '--json', '--omit=dev'], {
cwd: resolve(repoRoot, scope.path),
encoding: 'utf8',
shell: process.platform === 'win32',
})

const output = (result.stdout || result.stderr || '').trim()
if (!output) {
throw new Error(`npm audit produced no output for scope "${scopeName}"`)
}

let report
try {
report = JSON.parse(output)
} catch (error) {
throw new Error(`Failed to parse npm audit output for scope "${scopeName}": ${error.message}\n${output}`)
}

const counts = {}
for (const severity of severities) {
counts[severity] = report.metadata?.vulnerabilities?.[severity] ?? 0
}

return counts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail closed when npm audit itself fails.

Lines 34-54 treat missing vulnerability metadata as zero counts. That lets registry/auth/lockfile failures pass the policy gate if npm audit returns an error payload or the subprocess fails before producing the expected JSON shape. This check should hard-fail on result.error, report.error, missing metadata.vulnerabilities, and ideally on a bounded timeout as well.

Suggested fix
 function runAudit(scopeName) {
   const scope = baseline.scopes[scopeName]
   if (!scope) {
     throw new Error(`Unknown audit scope "${scopeName}"`)
   }
 
   const result = spawnSync('npm', ['audit', '--json', '--omit=dev'], {
     cwd: resolve(repoRoot, scope.path),
     encoding: 'utf8',
     shell: process.platform === 'win32',
+    timeout: 120_000,
   })
+
+  if (result.error) {
+    throw new Error(
+      `Failed to run npm audit for scope "${scopeName}": ${result.error.message}`
+    )
+  }
 
   const output = (result.stdout || result.stderr || '').trim()
   if (!output) {
     throw new Error(`npm audit produced no output for scope "${scopeName}"`)
   }
@@
   try {
     report = JSON.parse(output)
   } catch (error) {
     throw new Error(`Failed to parse npm audit output for scope "${scopeName}": ${error.message}\n${output}`)
   }
+
+  if (report.error) {
+    throw new Error(
+      `npm audit failed for scope "${scopeName}": ${report.error.summary || report.error.code || 'unknown error'}`
+    )
+  }
+
+  if (!report.metadata?.vulnerabilities) {
+    throw new Error(`npm audit did not return vulnerability metadata for scope "${scopeName}"`)
+  }
 
   const counts = {}
   for (const severity of severities) {
-    counts[severity] = report.metadata?.vulnerabilities?.[severity] ?? 0
+    counts[severity] = report.metadata.vulnerabilities[severity] ?? 0
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const result = spawnSync('npm', ['audit', '--json', '--omit=dev'], {
cwd: resolve(repoRoot, scope.path),
encoding: 'utf8',
shell: process.platform === 'win32',
})
const output = (result.stdout || result.stderr || '').trim()
if (!output) {
throw new Error(`npm audit produced no output for scope "${scopeName}"`)
}
let report
try {
report = JSON.parse(output)
} catch (error) {
throw new Error(`Failed to parse npm audit output for scope "${scopeName}": ${error.message}\n${output}`)
}
const counts = {}
for (const severity of severities) {
counts[severity] = report.metadata?.vulnerabilities?.[severity] ?? 0
}
return counts
const result = spawnSync('npm', ['audit', '--json', '--omit=dev'], {
cwd: resolve(repoRoot, scope.path),
encoding: 'utf8',
shell: process.platform === 'win32',
timeout: 120_000,
})
if (result.error) {
throw new Error(
`Failed to run npm audit for scope "${scopeName}": ${result.error.message}`
)
}
const output = (result.stdout || result.stderr || '').trim()
if (!output) {
throw new Error(`npm audit produced no output for scope "${scopeName}"`)
}
let report
try {
report = JSON.parse(output)
} catch (error) {
throw new Error(`Failed to parse npm audit output for scope "${scopeName}": ${error.message}\n${output}`)
}
if (report.error) {
throw new Error(
`npm audit failed for scope "${scopeName}": ${report.error.summary || report.error.code || 'unknown error'}`
)
}
if (!report.metadata?.vulnerabilities) {
throw new Error(`npm audit did not return vulnerability metadata for scope "${scopeName}"`)
}
const counts = {}
for (const severity of severities) {
counts[severity] = report.metadata.vulnerabilities[severity] ?? 0
}
return counts
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-npm-audit-baseline.mjs` around lines 34 - 57, The current audit
parsing treats missing vulnerability metadata as zeros; change this to fail-fast
by validating the spawn result and parsed report before computing counts: after
calling spawnSync('npm'...) check result.error and that result.status === 0 (or
result.signal) and throw if the subprocess failed or timed out; after
JSON.parse, if report.error exists or report.metadata?.vulnerabilities is falsy,
throw an informative Error for scopeName including the raw output; only then
iterate severities to build counts. Reference symbols: spawnSync call, result,
output, report, report.metadata?.vulnerabilities, severities, counts.

Comment on lines +91 to +95
if (writeBaseline) {
nextBaseline.generatedAt = new Date().toISOString()
writeFileSync(baselinePath, `${JSON.stringify(nextBaseline, null, 2)}\n`)
console.log(`[audit-policy] Baseline refreshed at ${baselinePath}`)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not persist a partial baseline on failed runs.

With --write-baseline, Lines 91-95 still rewrite the JSON even when another scope exceeded the baseline. A failed update run should leave security/npm-audit-baseline.json untouched; otherwise maintainers can accidentally commit partial baseline drift with a fresh generatedAt.

Suggested fix
-if (writeBaseline) {
-  nextBaseline.generatedAt = new Date().toISOString()
-  writeFileSync(baselinePath, `${JSON.stringify(nextBaseline, null, 2)}\n`)
-  console.log(`[audit-policy] Baseline refreshed at ${baselinePath}`)
-}
-
 if (hasFailure) {
   console.error('[audit-policy] Update the baseline only after the findings are reviewed and accepted.')
   process.exit(1)
 }
+
+if (writeBaseline) {
+  nextBaseline.generatedAt = new Date().toISOString()
+  writeFileSync(baselinePath, `${JSON.stringify(nextBaseline, null, 2)}\n`)
+  console.log(`[audit-policy] Baseline refreshed at ${baselinePath}`)
+}

Also applies to: 97-99

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-npm-audit-baseline.mjs` around lines 91 - 95, The current
writeBaseline block unconditionally updates security/npm-audit-baseline.json
(using writeFileSync with baselinePath and nextBaseline.generatedAt) even when
some scope exceeded the allowed baseline, producing a partial/incorrect
baseline; change the logic so the file is written only when the overall run
succeeded (no scopes exceeded or no failures) by gating the write behind the
success condition (e.g., check the existing boolean/flag that indicates "all
scopes within baseline" or introduce one like allScopesWithinBaseline) before
calling writeFileSync and setting nextBaseline.generatedAt; apply the same
guarded-write change to the other write site referenced (the lines around 97-99)
so the baseline is never updated on failed runs.

@ritik4ever
ritik4ever merged commit 9a41ff6 into ritik4ever:main May 30, 2026
7 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants