Files
msd-core/SECURITY.md
Tom Boucher 89886d90b4 feat(114): npm dependency integrity gate (npm ls invalid/extraneous) (#135)
* test(114): add failing regression tests for npm dependency integrity gate

Adds tests/npm-integrity-gate.test.cjs and four fixture directories under
tests/fixtures/npm-integrity/ covering:
  - clean: matching lockfile and node_modules (expects exit 0)
  - drift: declared vs installed version mismatch (expects exit 1)
    Reproduces the ws 8.20.1 declared / 8.20.0 installed incident shape
    using stable-dep@8.20.1 (package.json) vs stable-dep@8.20.0 (node_modules).
  - extraneous: unlisted package in node_modules (exits 1; exits 0 with --ignore-extraneous)
  - missing: declared package absent from node_modules (exits 1 regardless of flags)

Each test spawns scripts/check-npm-integrity.sh as a subprocess and asserts
on exit code first, then stderr content. Tests are RED at this commit because
the script does not yet exist.

Sources:
  npm ls docs: https://docs.npmjs.com/cli/v10/commands/npm-ls
  NIST SSDF PW.4.1: https://csrc.nist.gov/publications/detail/sp/800-218/final

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(114): add check-npm-integrity.sh + workspace-aware drift detection

Adds scripts/check-npm-integrity.sh, a Bash script that:
  1. Runs `npm ls --all --json` at the invocation directory
  2. Parses JSON output for invalid, missing, and extraneous package flags
  3. Exits 1 on any finding; emits a structured report to stderr listing offenders
     with both declared and installed versions for invalid packages
  4. Exits 2 on tool error (npm/node not found, JSON parse failure)
  5. Accepts --ignore-extraneous to suppress extraneous-only failures
  6. Documents behaviour in --help output including remediation path

Workspace behaviour: the root package.json in this repo has no "workspaces"
field. npm ls runs at the invocation root and covers that tree only. The sdk/
sub-package is a separate, non-workspace package and is out of scope for a
single invocation. If workspaces are added in future, npm ls will traverse
them automatically (npm >=7).

The drift scenario (ws 8.20.1 declared vs 8.20.0 installed) is reproduced by
using an exact version pin in package.json combined with a mismatched
node_modules/package.json -- npm ls marks this as "invalid" and exits 1.

npm exits 0 for extraneous packages even though they appear in the JSON
"problems" array; this script detects them via JSON parsing regardless of
the npm exit code.

Sources:
  npm ls docs: https://docs.npmjs.com/cli/v10/commands/npm-ls
  NIST SSDF PW.4.1: https://csrc.nist.gov/publications/detail/sp/800-218/final
  OpenSSF Scorecard Pinned-Dependencies:
    https://github.com/ossf/scorecard/blob/main/docs/checks.md#pinned-dependencies

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci(114): wire dependency integrity gate into CI/release/security workflows

Adds a "Dependency integrity gate" step invoking
scripts/check-npm-integrity.sh to three workflows, always after `npm ci`
and before any test or build step:

  .github/workflows/test.yml
    - matrix job: after "Install dependencies" / before "Build SDK dist"
    - coverage job: after "Install dependencies" / before "Build SDK dist"

  .github/workflows/release.yml
    - rc job "Install and test": after npm ci, before npm run test:coverage
    - finalize job "Install and test": after npm ci, before npm run test:coverage

  .github/workflows/security-scan.yml
    - Added setup-node + npm ci + gate before existing source-scan steps
    - Bumped timeout-minutes from 5 to 10 to accommodate the install step

Also adds "check:integrity": "./scripts/check-npm-integrity.sh" to root
package.json scripts for local contributor invocation.

No new workflow files created. All edits extend existing workflows.

Sources:
  npm ls docs: https://docs.npmjs.com/cli/v10/commands/npm-ls
  NIST SSDF PW.4.1: https://csrc.nist.gov/publications/detail/sp/800-218/final

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(114): document dependency integrity gate in audit runbook

Appends a "Dependency Integrity Verification" section to SECURITY.md
(no docs/runbooks/ directory exists in this repo). Covers:
  - The three detection classes: invalid, missing, extraneous
  - Local invocation: ./scripts/check-npm-integrity.sh + npm run check:integrity
  - Remediation: rm -rf node_modules && npm ci
  - Bypass policy: no flag; commit-message documentation required if skipped
  - Scope: root package only (sdk/ is a non-workspace package, out of scope)
  - CI coverage listing

Sources cited:
  NIST SSDF PW.4.1: https://csrc.nist.gov/publications/detail/sp/800-218/final
  OpenSSF Scorecard Pinned-Dependencies:
    https://github.com/ossf/scorecard/blob/main/docs/checks.md#pinned-dependencies

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#114): npm integrity gate satisfies its own clean/drift/extraneous fixtures

Replace npm-ls-based analysis with pure package-lock.json parsing so the
script runs correctly in CI and test environments where node_modules is not
installed. Key changes:

- Rewrite check-npm-integrity.sh parser to read package-lock.json directly
  instead of spawning `npm ls --all --json`, which required node_modules on
  disk and incorrectly flagged clean/drift fixtures as MISSING.
- Implement a self-contained semver satisfies() covering exact, caret, tilde,
  comparison-operator, and compound ranges — no external semver package needed.
- Update extraneous fixture package-lock.json to include ghost-pkg with
  "extraneous: true" so the lockfile-based detector can identify it.
- Update missing fixture package-lock.json to omit the node_modules/absent-dep
  entry, making the absent-dep MISSING condition derivable from lockfile alone.

All 13 tests (clean ×2, drift ×3, extraneous ×3, missing ×3, help ×2) pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#114): treat transitive deps as non-extraneous in integrity gate

The extraneous check was comparing all lockfile packages against root
package.json declarations only. This caused every transitive dependency
(e.g. hono, ajv, @anthropic-ai/claude-agent-sdk-darwin-arm64) to be
flagged as EXTRANEOUS, producing false-positive failures in CI.

Only packages that npm itself marks with "extraneous: true" in the
lockfile represent genuinely unwanted packages. Transitive dependencies
installed by parent packages are valid and should be skipped.

All 13 existing tests continue to pass; the extraneous fixture still
works because it uses "extraneous: true" explicitly (npm's own marker).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci: retrigger checks after transient git-auth runner failure

The original run for this PR had a single CI job fail with:
"fatal: could not read Username for 'https://github.com': terminal prompts disabled"
That is a hosted-runner infrastructure flake — no code defect. The run
cannot be retried via gh CLI (too old). This empty commit kicks a fresh
full CI cycle.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 15:50:17 -04:00

5.1 KiB

Security Policy

Reporting a Vulnerability

Please do not report security vulnerabilities through public GitHub issues.

Instead, please report them via email to: security@gsd.build (or DM @glittercowboy on Discord/Twitter if email bounces)

Include:

  • Description of the vulnerability
  • Steps to reproduce
  • Potential impact
  • Any suggested fixes (optional)

Response Timeline

  • Acknowledgment: Within 48 hours
  • Initial assessment: Within 1 week
  • Fix timeline: Depends on severity, but we aim for:
    • Critical: 24-48 hours
    • High: 1 week
    • Medium/Low: Next release

Scope

Security issues in the GSD codebase that could:

  • Execute arbitrary code on user machines
  • Expose sensitive data (API keys, credentials)
  • Compromise the integrity of generated plans/code

Recognition

We appreciate responsible disclosure and will credit reporters in release notes (unless you prefer to remain anonymous).

Org-level security baseline

This file covers how to report individual vulnerabilities. For the broader org-wide security posture — scanner controls, incident-audit checklists, ownership model, and rollout plan — see:

docs/security/baseline.md

Secret-Scan Exclusion Governance

Secret-scanning exclusions (.secretscanignore) require structured annotations. Bare paths are accepted in default mode with a deprecation warning but are rejected in strict mode. The lint runs on every PR.

Annotation format

# allow: <pattern>  reason="..."  owner="..."  expires="YYYY-MM-DD"  [rule-id="..."]
<pattern>

Required keys: reason, owner, expires. Wildcard patterns (**, *.ext) also require rule-id.

Lint locally: scripts/secret-scan-lint.sh --file .secretscanignore

Periodic reduced-exclusion scan (release and security-review lanes)

Run this during every release and scheduled security review:

scripts/secret-scan.sh --diff origin/main --strict

The --strict flag:

  • Does not honour grandfathered (un-annotated) exclusions — those files are scanned.
  • Skips any exclusion whose expires date is in the past — those files are scanned.
  • Is intended to surface accumulated exclusion debt that default mode masks.

If --strict finds findings that default mode does not, those findings represent either (a) an entry that should have been annotated and renewed, or (b) an actual secret that was only hidden by a stale exclusion. In both cases: investigate, remediate, and update the exclusion annotation.

References:


Dependency Integrity Verification

Purpose

The scripts/check-npm-integrity.sh gate detects three classes of dependency drift that can silently introduce security or reliability risk:

  • Invalid — an installed package version does not satisfy the declared semver range (e.g., ws@8.20.0 installed when 8.20.1 is declared). This was the original incident that prompted this gate.
  • Missing — a declared dependency is absent from node_modules/.
  • Extraneous — a package is present in node_modules/ but not declared as a dependency.

This aligns with NIST SSDF PW.4.1 (use components from well-governed, secure sources: https://csrc.nist.gov/publications/detail/sp/800-218/final) and the OpenSSF Scorecard "Pinned-Dependencies" check (https://github.com/ossf/scorecard/blob/main/docs/checks.md#pinned-dependencies).

Invoking locally

./scripts/check-npm-integrity.sh
# or via npm script:
npm run check:integrity

The script exits 0 on a clean install and 1 on any finding, with a structured report to stderr listing every offender and both the declared and installed versions for invalid packages.

Options:

  • --ignore-extraneous — suppress extraneous-only failures (useful when intentionally adding packages before updating the lockfile)
  • --help — print usage and exit 0

Remediation

The canonical fix for any drift is:

rm -rf node_modules && npm ci

Then verify with npm run check:integrity before committing.

Bypass policy

There is no bypass flag. If the gate must be skipped for a specific commit (e.g., during a lockfile migration), document the reason in the commit message. CI workflow steps can be skipped via if: false with a comment explaining why and a follow-up issue number. Any such skip must be reversed in a subsequent commit before the PR is merged.

Scope

The gate runs npm ls --all --json at the repository root. The sdk/ sub-directory is a separate, non-workspace package and is out of scope for this single invocation. If sdk/ is ever declared as a workspace in root package.json, it will be covered automatically (npm >=7 traverses workspaces by default).

CI coverage

The gate runs in:

  • test.yml — all matrix jobs and the coverage job, after npm ci
  • release.yml — rc and finalize jobs, after npm ci
  • security-scan.yml — before all diff-based source scans