mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-14 02:10:07 +08:00
docs: add supply-chain incident response playbook
Add a repo-level supply-chain incident response playbook for npm/GitHub Actions package-registry incidents, anchored on the May 2026 TanStack compromise and prior Shai-Hulud-style npm incidents. - add `docs/security/supply-chain-incident-response.md` with exposure checks, immediate response steps, workflow rules, publication rules, and escalation triggers - link the playbook from `SECURITY.md` - reject `pull_request_target` workflows that restore or save shared dependency caches - add a regression test for the new `pull_request_target + actions/cache` guardrail Validation: - node tests/ci/validate-workflow-security.test.js (12 passed, 0 failed) - node scripts/ci/validate-workflow-security.js (validated 7 workflow files) - npx markdownlint-cli 'SECURITY.md' 'docs/security/supply-chain-incident-response.md' - npx markdownlint-cli '**/*.md' --ignore node_modules - git diff --check - node tests/run-all.js (2377 passed, 0 failed) - GitHub CI for #1848 green across Ubuntu, Windows, and macOS No release, tag, npm publish, plugin tag, marketplace submission, or announcement was performed.
This commit is contained in:
parent
da04a6e344
commit
cbecf5689d
@ -96,5 +96,6 @@ Do not sanitize repo files in response to ephemeral reminders; they are not the
|
|||||||
|
|
||||||
- **AgentShield**: Scan your agent config for vulnerabilities — `npx ecc-agentshield scan`
|
- **AgentShield**: Scan your agent config for vulnerabilities — `npx ecc-agentshield scan`
|
||||||
- **Security Guide**: [The Shorthand Guide to Everything Agentic Security](./the-security-guide.md)
|
- **Security Guide**: [The Shorthand Guide to Everything Agentic Security](./the-security-guide.md)
|
||||||
|
- **Supply-chain incident response**: [npm/GitHub Actions package-registry playbook](./docs/security/supply-chain-incident-response.md)
|
||||||
- **OWASP MCP Top 10**: [owasp.org/www-project-mcp-top-10](https://owasp.org/www-project-mcp-top-10/)
|
- **OWASP MCP Top 10**: [owasp.org/www-project-mcp-top-10](https://owasp.org/www-project-mcp-top-10/)
|
||||||
- **OWASP Agentic Applications Top 10**: [genai.owasp.org](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/)
|
- **OWASP Agentic Applications Top 10**: [genai.owasp.org](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/)
|
||||||
|
|||||||
114
docs/security/supply-chain-incident-response.md
Normal file
114
docs/security/supply-chain-incident-response.md
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
# Supply-Chain Incident Response
|
||||||
|
|
||||||
|
This playbook is the ECC operator runbook for npm, GitHub Actions, and
|
||||||
|
cross-ecosystem package-registry incidents. It is intentionally conservative:
|
||||||
|
registry signatures, provenance, and trusted publishing are useful signals, but
|
||||||
|
they do not prove that the workflow executed the intended code path.
|
||||||
|
|
||||||
|
## Current External Trigger
|
||||||
|
|
||||||
|
As of 2026-05-13, the active incident class is the May 2026 TanStack npm
|
||||||
|
supply-chain compromise:
|
||||||
|
|
||||||
|
- TanStack reported 84 malicious versions across 42 `@tanstack/*` packages,
|
||||||
|
published on 2026-05-11 between 19:20 and 19:26 UTC.
|
||||||
|
- GitHub advisory `GHSA-g7cv-rxg3-hmpx` / `CVE-2026-45321` describes
|
||||||
|
install-time malware that harvests cloud credentials, GitHub tokens, npm
|
||||||
|
credentials, Vault tokens, Kubernetes tokens, and SSH private keys.
|
||||||
|
- The attack chain combined `pull_request_target`, GitHub Actions cache
|
||||||
|
poisoning across a fork/base trust boundary, and OIDC token extraction from a
|
||||||
|
GitHub Actions runner.
|
||||||
|
- npm trusted publishing/provenance can confirm a package came from a bound CI
|
||||||
|
identity. It cannot by itself prove that the CI cache, lifecycle scripts, or
|
||||||
|
publish path were safe.
|
||||||
|
|
||||||
|
Primary references:
|
||||||
|
|
||||||
|
- <https://tanstack.com/blog/npm-supply-chain-compromise-postmortem>
|
||||||
|
- <https://github.com/advisories/GHSA-g7cv-rxg3-hmpx>
|
||||||
|
- <https://tanstack.com/blog/incident-followup>
|
||||||
|
- <https://docs.npmjs.com/trusted-publishers/>
|
||||||
|
- <https://www.cisa.gov/news-events/alerts/2025/09/23/widespread-supply-chain-compromise-impacting-npm-ecosystem>
|
||||||
|
|
||||||
|
## ECC Exposure Check
|
||||||
|
|
||||||
|
Run this before a release candidate, after a broad dependency bump, and after
|
||||||
|
any package-registry incident.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n '(@tanstack|mistralai|uipath|opensearch|guardrails|axios)' \
|
||||||
|
package.json package-lock.json .opencode/package.json .opencode/package-lock.json
|
||||||
|
npm ci --ignore-scripts
|
||||||
|
npm audit signatures
|
||||||
|
npm audit --audit-level=high
|
||||||
|
node scripts/ci/validate-workflow-security.js
|
||||||
|
node tests/scripts/npm-publish-surface.test.js
|
||||||
|
node tests/run-all.js
|
||||||
|
```
|
||||||
|
|
||||||
|
If a search hit appears only in documentation examples, note it in the release
|
||||||
|
evidence but do not rotate credentials for a docs-only reference.
|
||||||
|
|
||||||
|
## Immediate Response
|
||||||
|
|
||||||
|
If ECC or a maintainer machine installed a known-bad package version:
|
||||||
|
|
||||||
|
1. Stop the host from publishing or deploying.
|
||||||
|
2. Preserve evidence before cleanup:
|
||||||
|
- package manager command history;
|
||||||
|
- `package-lock.json`, `pnpm-lock.yaml`, or `yarn.lock`;
|
||||||
|
- CI run URLs and runner logs;
|
||||||
|
- npm package versions and tarball integrity hashes;
|
||||||
|
- outbound network logs where available.
|
||||||
|
3. Treat the install host as compromised if lifecycle scripts may have run.
|
||||||
|
4. Rotate every credential reachable by the process:
|
||||||
|
- npm automation tokens and maintainer tokens;
|
||||||
|
- GitHub PATs, fine-grained tokens, deploy keys, and Actions secrets;
|
||||||
|
- cloud credentials, Vault tokens, Kubernetes service-account tokens, SSH
|
||||||
|
keys, and local `.npmrc` tokens;
|
||||||
|
- any MCP, plugin, or harness credentials available in environment variables
|
||||||
|
or user-scope config.
|
||||||
|
5. Purge GitHub Actions caches for affected repositories.
|
||||||
|
6. Reinstall from a clean environment with `npm ci --ignore-scripts` first.
|
||||||
|
7. Re-enable lifecycle scripts only after the dependency tree and package
|
||||||
|
versions are pinned to known-clean releases.
|
||||||
|
|
||||||
|
## GitHub Actions Rules
|
||||||
|
|
||||||
|
ECC enforces these rules through `scripts/ci/validate-workflow-security.js`:
|
||||||
|
|
||||||
|
- privileged workflows must not checkout untrusted PR refs;
|
||||||
|
- workflows with write permissions must use `npm ci --ignore-scripts`;
|
||||||
|
- workflows with `id-token: write` must not restore or save shared dependency
|
||||||
|
caches;
|
||||||
|
- workflows that run `npm audit` must also run `npm audit signatures`;
|
||||||
|
- `pull_request_target` workflows must not restore or save shared dependency
|
||||||
|
caches.
|
||||||
|
|
||||||
|
Treat any violation as a release blocker.
|
||||||
|
|
||||||
|
## Publication Rules
|
||||||
|
|
||||||
|
Before tagging or publishing ECC:
|
||||||
|
|
||||||
|
1. Verify there is no unexpected dependency on packages in the active advisory.
|
||||||
|
2. Use a clean checkout or throwaway worktree for release commands.
|
||||||
|
3. Do not mix PR/test caches with publish jobs.
|
||||||
|
4. Keep `id-token: write` limited to release workflows that do not use shared
|
||||||
|
dependency caches.
|
||||||
|
5. Prefer trusted publishing/provenance where supported, while still requiring
|
||||||
|
local package-surface tests and registry-signature verification.
|
||||||
|
6. Confirm npm dist-tag, GitHub release, Claude plugin, Codex plugin, and
|
||||||
|
OpenCode package state in the publication-readiness evidence document.
|
||||||
|
|
||||||
|
## When To Escalate
|
||||||
|
|
||||||
|
Escalate to a maintainer security review before any release or merge if:
|
||||||
|
|
||||||
|
- a dependency lockfile references a package named in an active advisory;
|
||||||
|
- a workflow combines `pull_request_target` with dependency installation,
|
||||||
|
cache restore/save, PR-head checkout, or write permissions;
|
||||||
|
- a release workflow combines `id-token: write` with shared cache usage;
|
||||||
|
- a publish workflow uses a long-lived npm token without a documented reason;
|
||||||
|
- AgentShield, GitGuardian, Dependabot, npm audit, or registry-signature checks
|
||||||
|
disagree.
|
||||||
@ -129,6 +129,16 @@ function findViolations(filePath, source) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (/\bpull_request_target\s*:/m.test(source) && ACTIONS_CACHE_PATTERN.test(source)) {
|
||||||
|
violations.push({
|
||||||
|
filePath,
|
||||||
|
event: 'pull_request_target cache',
|
||||||
|
description: 'pull_request_target workflows must not restore or save shared dependency caches',
|
||||||
|
expression: 'pull_request_target + actions/cache',
|
||||||
|
line: getLineNumber(source, source.search(/\bpull_request_target\s*:/m)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (NPM_AUDIT_PATTERN.test(source) && !NPM_AUDIT_SIGNATURES_PATTERN.test(source)) {
|
if (NPM_AUDIT_PATTERN.test(source) && !NPM_AUDIT_SIGNATURES_PATTERN.test(source)) {
|
||||||
violations.push({
|
violations.push({
|
||||||
filePath,
|
filePath,
|
||||||
|
|||||||
@ -99,6 +99,14 @@ function run() {
|
|||||||
assert.match(result.stderr, /pull_request\.head\.sha/);
|
assert.match(result.stderr, /pull_request\.head\.sha/);
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('rejects shared cache use in pull_request_target workflows', () => {
|
||||||
|
const result = runValidator({
|
||||||
|
'unsafe-pr-target-cache.yml': `name: Unsafe\non:\n pull_request_target:\n branches: [main]\njobs:\n inspect:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/cache@v5\n with:\n path: ~/.npm\n key: cache\n - run: echo inspect\n`,
|
||||||
|
});
|
||||||
|
assert.notStrictEqual(result.status, 0, 'Expected validator to fail on pull_request_target cache use');
|
||||||
|
assert.match(result.stderr, /pull_request_target workflows must not restore or save shared dependency caches/);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
if (test('rejects npm ci without ignore-scripts in workflows with write permissions', () => {
|
if (test('rejects npm ci without ignore-scripts in workflows with write permissions', () => {
|
||||||
const result = runValidator({
|
const result = runValidator({
|
||||||
'unsafe-write-install.yml': `name: Unsafe\non:\n workflow_dispatch:\npermissions:\n contents: read\n issues: write\njobs:\n audit:\n runs-on: ubuntu-latest\n steps:\n - run: npm ci\n`,
|
'unsafe-write-install.yml': `name: Unsafe\non:\n workflow_dispatch:\npermissions:\n contents: read\n issues: write\njobs:\n audit:\n runs-on: ubuntu-latest\n steps:\n - run: npm ci\n`,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user