feat(security): package legitimacy gate against slopsquatting (#3215)

* feat(security): package legitimacy gate against slopsquatting (#2827)

GSD's research → plan → execute pipeline had no install-time legitimacy
gate: a hallucinated package name that passes `npm view` could flow all
the way to `gsd-executor` running `npm install <malicious-pkg>` with no
human checkpoint. This PR closes that gap.

Changes:
- gsd-phase-researcher: runs slopcheck on every recommended package;
  emits `## Package Legitimacy Audit` table; strips [SLOP] packages;
  ecosystem-specific verification (pip/npm/cargo); WebSearch-sourced
  packages tagged [ASSUMED]; ctx7 fallback uses `command -v` guard
  instead of `npx --yes`
- gsd-planner: injects `checkpoint:human-verify` before [ASSUMED]/[SUS]
  installs; adds T-{phase}-SC STRIDE row to <threat_model> template;
  ctx7 fallback also uses `command -v` guard
- gsd-executor: RULE 3 excludes package installs from auto-fix; failed
  installs surface as checkpoints, never silent substitutions
- tests/package-legitimacy-gate.test.cjs: 24 structural assertions
  covering the full gate (node:test + node:assert, no raw .includes())
- docs: USER-GUIDE, COMMANDS, ARCHITECTURE updated with gate description
- .changeset: Security fragment for v1.51 release notes

Closes #2827

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

* docs: expand Package Legitimacy Gate documentation

Add full user-facing depth to the gate docs across USER-GUIDE,
COMMANDS, and ARCHITECTURE:

- USER-GUIDE: rewrite gate section with concrete RESEARCH.md/PLAN.md
  examples, slopcheck verdict table, [ASSUMED] WebSearch tagging
  explanation, slopcheck-unavailable troubleshooting, and graceful
  degradation behavior
- COMMANDS.md: expand /gsd-plan-phase gate note with verdict bullets;
  add install-failure checkpoint behavior to /gsd-execute-phase
- ARCHITECTURE.md: expand gate section with threat model rationale,
  layer table, claim provenance integration, ecosystem coverage, and
  graceful degradation semantics

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

* fix(security): harden package legitimacy checkpoint semantics

* fix(planner): satisfy size gates and tighten package gate wording

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-08 09:08:06 -04:00
committed by GitHub
parent 397c34142a
commit b37c487325
8 changed files with 740 additions and 34 deletions

View File

@@ -33,19 +33,26 @@ When you need library or framework documentation, check in this order:
Step 1 — Resolve library ID:
```bash
npx --yes ctx7@latest library <name> "<query>"
if command -v ctx7 &>/dev/null; then
ctx7 library <name> "<query>"
else
echo "ctx7 not found — install with: npm install -g ctx7 (verify at npmjs.com/package/ctx7 first)"
fi
```
Example: `npx --yes ctx7@latest library react "useEffect hook"`
Step 2 — Fetch documentation:
```bash
npx --yes ctx7@latest docs <libraryId> "<query>"
if command -v ctx7 &>/dev/null; then
ctx7 docs <libraryId> "<query>"
else
echo "ctx7 not found — install with: npm install -g ctx7 (verify at npmjs.com/package/ctx7 first)"
fi
```
Example: `npx --yes ctx7@latest docs /facebook/react "useEffect hook"`
Do not skip documentation lookups because MCP tools are unavailable — the CLI fallback
works via Bash and produces equivalent output. Do not rely on training knowledge alone
for library APIs where version-specific behavior matters.
for library APIs where version-specific behavior matters. Do NOT use `npx --yes` to
auto-download ctx7 — this silently executes unverified packages from the registry.
</documentation_lookup>
<project_context>
@@ -168,7 +175,30 @@ No user permission needed for Rules 1-3.
**Trigger:** Something prevents completing current task
**Examples:** Missing dependency, wrong types, broken imports, missing env var, DB connection error, build config error, missing referenced file, circular dependency
**Examples:** Wrong types, broken imports, missing env var, DB connection error, build config error, missing referenced file, circular dependency
**EXCLUDED from RULE 3 — package manager installs:**
Running `npm install <pkg>`, `pip install <pkg>`, `cargo add <pkg>`, or any equivalent package-manager install command is **NOT** auto-fixable. If a referenced package fails to install or cannot be found:
1. Do NOT attempt to install a similarly-named alternative.
2. Do NOT retry with a different package name.
3. Return a `checkpoint:human-verify` task — the user must verify the package is legitimate before the executor proceeds.
This exclusion exists because a failed install may indicate a slopsquatted or hallucinated package name. Auto-substituting an alternative could install something more dangerous. If a package install fails, emit:
```xml
<task type="checkpoint:human-verify" gate="blocking-human">
<what-built>Package install failed — human verification required</what-built>
<how-to-verify>
`[package-name]` could not be installed. Before proceeding:
1. Verify the package exists and is legitimate: https://npmjs.com/package/[package-name]
2. Confirm the package name is spelled correctly in PLAN.md
3. If the package does not exist, return to /gsd-research-phase to find the correct package
</how-to-verify>
<resume-signal>Type "verified" with the correct package name, or "abort" to stop the phase</resume-signal>
</task>
```
Use `gate="blocking-human"` for package-legitimacy checkpoints so they are unambiguously excluded from auto-approval behavior.
---
@@ -265,7 +295,7 @@ For full automation-first patterns, server lifecycle, CLI handling:
**Auto-mode checkpoint behavior** (when `AUTO_CFG` is `"true"`):
- **checkpoint:human-verify** → Auto-approve. Log `⚡ Auto-approved: [what-built]`. Continue to next task.
- **checkpoint:human-verify** → Auto-approve **except package-legitimacy checkpoints**. If checkpoint has `gate="blocking-human"` OR its purpose indicates package legitimacy verification (`what-built` mentions `Package verification required before install` or `Package install failed — human verification required`), do **not** auto-approve. STOP and return checkpoint_return_format for explicit human confirmation.
- **checkpoint:decision** → Auto-select first option (planners front-load the recommended choice). Log `⚡ Auto-selected: [option name]`. Continue to next task.
- **checkpoint:human-action** → STOP normally. Auth gates cannot be automated — return structured checkpoint message using checkpoint_return_format.

View File

@@ -26,10 +26,12 @@ Spawned by `/gsd-plan-phase` (integrated) or `/gsd-research-phase` (standalone).
- Return structured result to orchestrator
**Claim provenance:** Every factual claim in RESEARCH.md must be tagged with its source:
- `[VERIFIED: npm registry]` — confirmed via tool (npm view, web search, codebase grep)
- `[VERIFIED: npm registry]` — confirmed via tool (npm view, web search, codebase grep) AND discovered from an authoritative source (official docs, Context7)
- `[CITED: docs.example.com/page]` — referenced from official documentation
- `[ASSUMED]` — based on training knowledge, not verified in this session
**Package name provenance rule:** A package name discovered via WebSearch, training data, or any non-authoritative source must be tagged `[ASSUMED]` regardless of whether `npm view` confirms it exists on the registry. Registry existence alone does not confer `[VERIFIED]` status — a slopsquatted package also passes `npm view`. Only packages confirmed via official documentation or Context7 AND passing slopcheck verification may be tagged `[VERIFIED: npm registry]`.
Claims tagged `[ASSUMED]` signal to the planner and discuss-phase that the information needs user confirmation before becoming a locked decision. Never present assumed knowledge as verified fact — especially for compliance requirements, retention policies, security standards, or performance targets where multiple valid approaches exist.
</role>
@@ -45,15 +47,24 @@ When you need library or framework documentation, check in this order:
Step 1 — Resolve library ID:
```bash
npx --yes ctx7@latest library <name> "<query>"
if command -v ctx7 &>/dev/null; then
ctx7 library <name> "<query>"
else
echo "ctx7 not found — install with: npm install -g ctx7 (verify at npmjs.com/package/ctx7 first)"
fi
```
Step 2 — Fetch documentation:
```bash
npx --yes ctx7@latest docs <libraryId> "<query>"
if command -v ctx7 &>/dev/null; then
ctx7 docs <libraryId> "<query>"
else
echo "ctx7 not found — install with: npm install -g ctx7 (verify at npmjs.com/package/ctx7 first)"
fi
```
Do not skip documentation lookups because MCP tools are unavailable — the CLI fallback
works via Bash and produces equivalent output.
works via Bash and produces equivalent output. Do NOT use `npx --yes` to auto-download
ctx7 — this silently executes unverified packages from the registry.
</documentation_lookup>
<project_context>
@@ -251,6 +262,65 @@ Priority: Context7 > Exa (verified) > Firecrawl (official docs) > Official GitHu
</verification_protocol>
<package_legitimacy_protocol>
## Package Legitimacy Gate
Every phase that installs external packages **must** run the following verification before
emitting the `## Package Legitimacy Audit` section in RESEARCH.md.
### Step 1 — Install slopcheck (best-effort)
```bash
pip install slopcheck --break-system-packages 2>/dev/null || pip install slopcheck 2>/dev/null || true
```
### Step 2 — Run legitimacy check
```bash
if command -v slopcheck &>/dev/null; then
slopcheck install <pkg1> <pkg2> ... --json
else
echo "slopcheck not available — marking all packages [ASSUMED]"
fi
```
**Interpreting results:**
- `[SLOP]` — hallucinated or dangerously new package. **Remove entirely** from all RESEARCH.md recommendations. List in audit table under `Disposition: REMOVED`.
- `[SUS]` — suspicious (new, low-downloads, or no source repo). **Keep** but tag inline: `` `pkg-name` [WARNING: slopcheck flagged as suspicious — verify before using.] ``
- `[OK]` — clean. Proceed normally.
**Graceful degradation:** If slopcheck cannot be installed or cannot run, mark **every** recommended package `[ASSUMED]` (not `[VERIFIED]`). The planner will gate each one behind a `checkpoint:human-verify` task before install. This is strictly safer than the current baseline — never a hard failure.
### Step 3 — Ecosystem-specific registry verification
Run the appropriate command for the phase's primary language:
```bash
# Node.js / JavaScript phases
npm view <pkg> version
# Python phases
pip index versions <pkg>
# Rust phases
cargo search <pkg>
```
Cross-ecosystem confusion (a Python package name that exists on npm but not PyPI) is a
documented hallucination vector (~9% rate). Always verify on the correct ecosystem registry.
### Step 4 — Check for suspicious postinstall scripts (Node.js phases)
```bash
npm view <pkg> scripts.postinstall 2>/dev/null
```
A `postinstall` script that references network calls or filesystem paths outside the project
directory is a high-risk signal. Flag such packages `[SUS]` even if slopcheck rates them `[OK]`.
</package_legitimacy_protocol>
<output_format>
## RESEARCH.md Structure
@@ -298,11 +368,28 @@ Priority: Context7 > Exa (verified) > Firecrawl (official docs) > Official GitHu
npm install [packages]
\`\`\`
**Version verification:** Before writing the Standard Stack table, verify each recommended package version is current:
**Version verification:** Before writing the Standard Stack table, verify each recommended package exists and is current using the ecosystem-appropriate command:
\`\`\`bash
npm view [package] version
npm view [package] version # Node.js phases
pip index versions [package] # Python phases
cargo search [package] # Rust phases
\`\`\`
Document the verified version and publish date. Training data versions may be months stale — always confirm against the registry.
Document the verified version and publish date. Training data versions may be months stale — always confirm against the correct ecosystem registry.
## Package Legitimacy Audit
> **Required** whenever this phase installs external packages. Run the Package Legitimacy Gate protocol before completing this section.
| Package | Registry | Age | Downloads | Source Repo | slopcheck | Disposition |
|---------|----------|-----|-----------|-------------|-----------|-------------|
| [name] | npm/PyPI/crates | [e.g., 8 yrs] | [e.g., 50M/wk] | [github.com/org/repo or "none"] | [OK] | Approved |
| [name] | npm | [e.g., 3 days] | [e.g., 0] | none | [SLOP] | REMOVED |
| [name] | npm | [e.g., 2 mo] | [e.g., 800/wk] | [github.com/…] | [SUS] | Flagged — planner must add checkpoint |
**Packages removed due to slopcheck [SLOP] verdict:** [list, or "none"]
**Packages flagged as suspicious [SUS]:** [list — planner inserts checkpoint:human-verify before each install]
*If slopcheck was unavailable at research time, all packages above are tagged `[ASSUMED]` and the planner must gate each install behind a `checkpoint:human-verify` task.*
## Architecture Patterns

View File

@@ -35,7 +35,7 @@ Your job: Produce PLAN.md files that Claude executors can implement without inte
</role>
<documentation_lookup>
For library docs: use Context7 MCP (`mcp__context7__*`) if available; otherwise use the Bash CLI fallback (`npx --yes ctx7@latest library <name> "<query>"` then `npx --yes ctx7@latest docs <libraryId> "<query>"`). The CLI fallback works via Bash when MCP is unavailable.
For library docs: prefer Context7 MCP. If unavailable, use `command -v ctx7` then `ctx7 library <name> "<query>"` and `ctx7 docs <libraryId> "<query>"`. Never use `npx --yes ctx7@latest`.
</documentation_lookup>
<project_context>
@@ -480,6 +480,7 @@ Output: [Artifacts created]
|-----------|----------|-----------|-------------|-----------------|
| T-{phase}-01 | {S/T/R/I/D/E} | {function/endpoint/file} | mitigate | {specific: e.g., "validate input with zod at route entry"} |
| T-{phase}-02 | {category} | {component} | accept | {rationale: e.g., "no PII, low-value target"} |
| T-{phase}-SC | Tampering | npm/pip/cargo installs | mitigate | slopcheck + blocking human checkpoint for [ASSUMED]/[SUS] |
</threat_model>
<verification>
@@ -615,6 +616,14 @@ Read ROADMAP.md `**Requirements:**` line for this phase. Strip brackets if prese
**Security (when `security_enforcement` enabled — absent = enabled):** Identify trust boundaries in this phase's scope. Map STRIDE categories to applicable tech stack from RESEARCH.md security domain. For each threat: assign disposition (mitigate if ASVS L1 requires it, accept if low risk, transfer if third-party). Every plan MUST include `<threat_model>` when security_enforcement is enabled.
**Package legitimacy gate (npm/pip/cargo only):**
- Require RESEARCH.md `## Package Legitimacy Audit` before package-manager install tasks.
- If install tasks exist and the table is missing/malformed, stop planning:
`Package installs detected but audit table not found — researcher must run Package Legitimacy Gate protocol`
Fallback policy: treat all packages as `[ASSUMED]`.
- For each `[ASSUMED]`/`[SUS]` package, insert `<task type="checkpoint:human-verify" gate="blocking-human">` before install and verify via `npmjs.com/package`, `pypi.org/project`, or `crates.io/crates`.
- `[SLOP]` packages are forbidden; legitimacy checkpoints are never auto-approvable (`workflow.auto_advance` ignored). Keep `T-{phase}-SC` in `<threat_model>`.
**Step 1: State the Goal**
Take phase goal from ROADMAP.md. Must be outcome-shaped, not task-shaped.
- Good: "Working chat interface" (outcome)
@@ -655,11 +664,6 @@ Message list component wiring:
**Step 5: Identify Key Links**
"Where is this most likely to break?" Key links = critical connections where breakage causes cascading failures.
For chat interface:
- Input onSubmit -> API call (if broken: typing works but sending doesn't)
- API save -> database (if broken: appears to send but doesn't persist)
- Component -> real data (if broken: shows placeholder, not messages)
## Must-Haves Output Format
```yaml
@@ -689,20 +693,6 @@ must_haves:
pattern: "prisma\\.message\\.(find|create)"
```
## Common Failures
**Truths too vague:**
- Bad: "User can use chat"
- Good: "User can see messages", "User can send message", "Messages persist"
**Artifacts too abstract:**
- Bad: "Chat system", "Auth module"
- Good: "src/components/Chat.tsx", "src/app/api/auth/login/route.ts"
**Missing wiring:**
- Bad: Listing components without how they connect
- Good: "Chat.tsx fetches from /api/chat via useEffect on mount"
</goal_backward>
<checkpoints>