security/scanning/supply-chain-attack-response/SKILL.md
Detect, respond to, and prevent software supply chain attacks on package registries, container images, and CI/CD pipelines with lockfile auditing, provenance verification, and emergency response playbooks.
npx skillsauth add bagelhole/devops-security-agent-skills supply-chain-attack-responseInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Software supply chain attacks target the dependencies, build systems, and distribution channels that developers trust implicitly. When a package on PyPI, npm, or crates.io is compromised, every downstream consumer inherits the malicious payload. This skill provides detection techniques, emergency response playbooks, and hardening strategies to protect your software supply chain end to end.
Invoke this skill when any of the following apply:
# Full audit of installed packages
npm audit
# JSON output for programmatic processing
npm audit --json | jq '.vulnerabilities | to_entries[] | select(.value.severity == "critical")'
# Fix automatically where possible
npm audit fix
# Check for known malicious packages via Socket.dev CLI
npx socket scan --package-lock package-lock.json
# Install pip-audit (maintained by Google/OSSF)
pip install pip-audit
# Audit current environment against OSV.dev
pip-audit
# Audit a requirements file directly
pip-audit -r requirements.txt --output json
# Check for typosquatting with bandersnatch or custom script
pip-audit --strict --desc on
# Install cargo-audit
cargo install cargo-audit
# Run audit against RustSec Advisory Database
cargo audit
# JSON output for CI integration
cargo audit --json
# Check for yanked crates
cargo audit --deny yanked
# Verify a container image signature with cosign
cosign verify \
--certificate-identity "https://github.com/myorg/myrepo/.github/workflows/build.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/myorg/myimage:latest
# Verify an artifact with sigstore-python
pip install sigstore
python -m sigstore verify identity \
--cert-identity "[email protected]" \
--cert-oidc-issuer "https://accounts.google.com" \
artifact.tar.gz
# Install slsa-verifier
go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest
# Verify provenance of a binary
slsa-verifier verify-artifact my-binary \
--provenance-path my-binary.intoto.jsonl \
--source-uri github.com/myorg/myrepo \
--source-tag v1.2.3
When a dependency is confirmed compromised, execute these steps in order.
# Pin the last known-good version immediately in package.json
npm install <package>@<safe-version> --save-exact
# For pip, pin with hash verification
pip download <package>==<safe-version> --require-hashes -d ./vendor/
# For cargo, pin in Cargo.toml
# Replace: some_crate = "^1.2" with:
# some_crate = "=1.2.3"
cargo update -p some_crate --precise 1.2.3
# Determine which versions you pulled and when
# npm
npm ls <compromised-package>
cat package-lock.json | jq '.packages | to_entries[] | select(.key | contains("<compromised-package>"))'
# pip
pip show <compromised-package>
pip cache list <compromised-package>
# Check git history for when the dependency version changed
git log --all -p -- package-lock.json | grep -A2 -B2 "<compromised-package>"
# Search for known IOCs from the advisory
grep -r "suspicious-domain.com" ./node_modules/<compromised-package>/
grep -r "eval(atob" ./node_modules/<compromised-package>/
# Check for unexpected post-install scripts
cat node_modules/<compromised-package>/package.json | jq '.scripts'
# For Python packages, inspect setup.py and __init__.py
find ~/.local/lib/python*/site-packages/<compromised-package>/ -name "*.py" \
| xargs grep -l "subprocess\|os.system\|exec(\|eval("
SUBJECT: [SECURITY INCIDENT] Compromised dependency: <package-name>
SEVERITY: Critical
IMPACT: <package-name> versions <affected-range> contain malicious code.
AFFECTED SYSTEMS: <list of repos/services consuming this dependency>
STATUS: Contained -- pinned to safe version <safe-version>
ACTIONS TAKEN:
1. Pinned all repositories to last known-good version
2. Initiated audit of all systems that pulled affected versions
3. Scanning for indicators of compromise
RECOMMENDED ACTIONS:
- Do NOT deploy any build that consumed affected versions
- Review CI/CD logs for the timeframe <start> to <end>
- Rotate any secrets that were accessible to the build environment
# If the package maintainer account was compromised, fork the last safe version
git clone https://github.com/original-author/<package>.git
cd <package>
git checkout v<safe-version>
# Publish to your private registry or vendor directly
# For npm, point to your fork via package.json
# "dependencies": { "<package>": "git+https://github.com/yourorg/<package>.git#v1.2.3" }
Lockfiles are your first line of defense. Tampered or inconsistent lockfiles indicate something is wrong.
# npm: ensure lockfile matches package.json (fails CI if out of sync)
npm ci
# Yarn: check lockfile integrity
yarn install --frozen-lockfile
# pip: generate a hash-locked requirements file
pip-compile --generate-hashes requirements.in -o requirements.txt
# Verify no unexpected changes in lockfile during PR
git diff --name-only origin/main...HEAD | grep -E "(package-lock|yarn.lock|Cargo.lock|requirements.txt)"
# Use the socket CLI to check for typosquatting risk
npx socket scan --package-lock package-lock.json
# Python: check package names against popular packages
pip-audit -r requirements.txt 2>&1 | grep -i "typosquat"
# Custom check: compare package names to known popular packages
# Flag anything with edit distance <= 2 from a top-1000 package
python3 -c "
import json, sys
from difflib import SequenceMatcher
with open('package-lock.json') as f:
lock = json.load(f)
popular = ['express','lodash','react','axios','chalk','debug','commander','inquirer']
for pkg in lock.get('packages', {}):
name = pkg.split('node_modules/')[-1] if 'node_modules/' in pkg else pkg
for p in popular:
ratio = SequenceMatcher(None, name, p).ratio()
if 0.75 < ratio < 1.0 and name != p:
print(f'WARNING: {name} is suspiciously similar to {p} (similarity: {ratio:.2f})')
"
# .github/workflows/lockfile-check.yml
name: Lockfile Audit
on: pull_request
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for lockfile changes
run: |
LOCKFILES="package-lock.json yarn.lock pnpm-lock.yaml Cargo.lock requirements.txt poetry.lock"
for f in $LOCKFILES; do
if git diff --name-only origin/main...HEAD | grep -q "$f"; then
echo "::warning::Lockfile $f was modified -- review dependency changes carefully"
git diff origin/main...HEAD -- "$f" | head -100
fi
done
- name: Run npm audit
if: hashFiles('package-lock.json') != ''
run: npm audit --audit-level=high
# requirements.txt with hashes (generated by pip-compile --generate-hashes)
requests==2.31.0 \
--hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003eb \
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7f0edf3fcb0fce8afe0f44546e1
# Install with mandatory hash verification
pip install --require-hashes -r requirements.txt
# Generate hashes for existing requirements
pip-compile --generate-hashes requirements.in
# npm automatically verifies integrity hashes in package-lock.json
# Ensure your lockfile contains integrity fields:
cat package-lock.json | jq '.packages | to_entries[] | select(.value.integrity == null) | .key'
# Enable strict engine and audit checks in .npmrc
cat >> .npmrc << 'EOF'
engine-strict=true
audit=true
audit-level=high
EOF
# Install cargo-vet
cargo install cargo-vet
# Initialize in your project
cargo vet init
# Certify a crate after review
cargo vet certify serde 1.0.193
# Import audit results from trusted organizations
cargo vet trust --all mozilla
cargo vet trust --all google
# Run verification in CI
cargo vet check
# Sign an image (keyless via Sigstore/Fulcio in CI)
cosign sign ghcr.io/myorg/myimage@sha256:abc123...
# Verify with expected identity
cosign verify \
--certificate-identity-regexp "https://github.com/myorg/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/myorg/myimage:latest
# Verify and extract attestations
cosign verify-attestation \
--type slsaprovenance \
--certificate-identity-regexp "https://github.com/myorg/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/myorg/myimage:latest | jq '.payload' | base64 -d | jq .
# kyverno-require-signed-images.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-signed-images
spec:
validationFailureAction: Enforce
background: false
rules:
- name: verify-image-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/myorg/*"
attestors:
- entries:
- keyless:
subject: "https://github.com/myorg/*"
issuer: "https://token.actions.githubusercontent.com"
rekor:
url: https://rekor.sigstore.dev
# Apply the policy
kubectl apply -f kyverno-require-signed-images.yaml
# Test: this unsigned image should be rejected
kubectl run test --image=ghcr.io/myorg/unsigned-image:latest
# Expected: admission webhook denies the request
# BAD: mutable tag, can be hijacked
- uses: actions/checkout@v4
# GOOD: pinned to exact commit SHA
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
# Use pin-github-action to automate pinning
npm install -g pin-github-action
pin-github-action .github/workflows/*.yml
# Use ephemeral self-hosted runners that are destroyed after each job
jobs:
build:
runs-on: self-hosted
container:
image: ghcr.io/myorg/build-env:latest@sha256:abc123...
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- name: Build in isolated container
run: |
# No access to host filesystem or network beyond what's needed
make build
# GitHub Actions OIDC with AWS -- no static credentials stored
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
aws-region: us-east-1
- run: aws s3 cp build/ s3://my-bucket/ --recursive
# At the top of every workflow, use least-privilege permissions
permissions:
contents: read
packages: read
# Never grant write permissions globally; scope them per job
jobs:
publish:
permissions:
contents: read
packages: write
| Level | Requirement | |-------|-------------| | SLSA 1 | Build process is documented and generates provenance | | SLSA 2 | Provenance is generated by a hosted build service and is authenticated | | SLSA 3 | Build platform is hardened, provenance is non-falsifiable |
# .github/workflows/slsa-build.yml
name: SLSA Build
on:
push:
tags: ["v*"]
jobs:
build:
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.hash.outputs.digest }}
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- name: Build artifact
run: |
make build
cp dist/my-binary ./my-binary
- name: Generate digest
id: hash
run: |
DIGEST=$(sha256sum my-binary | cut -d ' ' -f1)
echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@v4
with:
name: my-binary
path: my-binary
provenance:
needs: build
permissions:
actions: read
id-token: write
contents: write
uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]
with:
base64-subjects: |
${{ needs.build.outputs.digest }} my-binary
upload-assets: true
# Download the provenance and binary from the release
gh release download v1.2.3 -p "my-binary" -p "my-binary.intoto.jsonl"
# Verify
slsa-verifier verify-artifact my-binary \
--provenance-path my-binary.intoto.jsonl \
--source-uri github.com/myorg/myrepo \
--source-tag v1.2.3
echo $? # 0 = verified successfully
# artifactory-remote-npm.yaml
apiVersion: v1
kind: RemoteRepository
metadata:
name: npm-remote
spec:
packageType: npm
url: https://registry.npmjs.org
includesPattern: |
express/**
lodash/**
react/**
@types/**
excludesPattern: |
*malicious*
*typosquat*
xrayIndex: true
blockMismatchingMimeTypes: true
enableTokenAuthentication: true
# Enable Nexus Firewall audit on a proxy repository
curl -u admin:$NEXUS_PASSWORD -X PUT \
"https://nexus.internal/service/rest/v1/security/content-selectors" \
-H "Content-Type: application/json" \
-d '{
"name": "block-suspicious-pypi",
"description": "Block packages with no maintainer history",
"expression": "format == \"pypi\" and coordinate.age < 7"
}'
# verdaccio config.yaml
storage: /verdaccio/storage
uplinks:
npmjs:
url: https://registry.npmjs.org/
cache: true
maxage: 30m
packages:
'@myorg/*':
access: $authenticated
publish: $authenticated
proxy: [] # never proxy internal packages
'**':
access: $authenticated
publish: $deny # block publishing public package names
proxy: npmjs
# Block known malicious packages
'event-stream':
access: $deny
publish: $deny
# .github/workflows/dependency-review.yml
name: Dependency Review
on: pull_request
permissions:
contents: read
pull-requests: write
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- uses: actions/dependency-review-action@4901385134134e04cec5fbe5ddfe3b2c5bd5d976 # v4
with:
fail-on-severity: high
deny-licenses: GPL-3.0, AGPL-3.0
comment-summary-in-pr: always
warn-only: false
# Install osv-scanner
go install github.com/google/osv-scanner/cmd/osv-scanner@latest
# Scan a project directory (auto-detects lockfiles)
osv-scanner -r /path/to/project
# Scan a specific lockfile
osv-scanner --lockfile=package-lock.json
# Scan a Docker image
osv-scanner --docker myimage:latest
# Output as JSON for CI processing
osv-scanner -r /path/to/project --format json | jq '.results[].packages[].vulnerabilities[] | .id'
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 10
reviewers:
- "security-team"
labels:
- "dependencies"
- "security"
# Group minor/patch updates but keep major separate for review
groups:
production-dependencies:
dependency-type: "production"
update-types: ["minor", "patch"]
dev-dependencies:
dependency-type: "development"
update-types: ["minor", "patch"]
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
#!/usr/bin/env bash
# alert-new-deps.sh -- run in CI on PRs to detect newly added dependencies
set -euo pipefail
BASE_BRANCH="${1:-origin/main}"
LOCKFILE="package-lock.json"
NEW_DEPS=$(diff <(git show "$BASE_BRANCH:$LOCKFILE" 2>/dev/null | jq -r '.packages | keys[]' | sort) \
<(jq -r '.packages | keys[]' "$LOCKFILE" | sort) \
| grep "^>" | sed 's/^> //' || true)
if [ -n "$NEW_DEPS" ]; then
echo "New dependencies detected:"
echo "$NEW_DEPS"
# Send to Slack
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{
\"text\": \"New dependencies added in PR #${PR_NUMBER}:\n\`\`\`${NEW_DEPS}\`\`\`\",
\"channel\": \"#security-alerts\"
}"
fi
[ ] Identify the exact compromised package version(s)
[ ] Determine the time window of exposure (first install to detection)
[ ] List all repositories and services that consumed the package
[ ] Check CI/CD build logs for the exposure window
[ ] Inspect runtime logs for outbound connections to unknown hosts
[ ] Review process execution logs for unexpected child processes
[ ] Check for modifications to other files in node_modules/site-packages
[ ] Verify no additional packages were installed as transitive deps
[ ] Dump and analyze DNS query logs for the exposure period
[ ] Check for new cron jobs, systemd services, or scheduled tasks
[ ] Audit all secrets/tokens that were accessible to the build environment
#!/usr/bin/env bash
# blast-radius.sh -- assess how widely a compromised package spread
set -euo pipefail
COMPROMISED_PKG="$1"
COMPROMISED_VERSIONS="$2" # comma-separated, e.g., "1.2.3,1.2.4"
echo "=== Blast Radius Assessment for $COMPROMISED_PKG ==="
# Check all repos in the org
for repo in $(gh repo list myorg --json name -q '.[].name'); do
echo "--- Checking $repo ---"
# Check package-lock.json
LOCK=$(gh api "repos/myorg/$repo/contents/package-lock.json" \
--jq '.content' 2>/dev/null | base64 -d 2>/dev/null || true)
if echo "$LOCK" | grep -q "\"$COMPROMISED_PKG\""; then
VERSION=$(echo "$LOCK" | jq -r ".packages[\"node_modules/$COMPROMISED_PKG\"].version // empty")
if echo "$COMPROMISED_VERSIONS" | grep -q "$VERSION"; then
echo "AFFECTED: $repo uses $COMPROMISED_PKG@$VERSION"
fi
fi
done
# Rotate all secrets that were accessible during the exposure window
# 1. Rotate cloud provider credentials
aws iam create-access-key --user-name ci-deploy
aws iam delete-access-key --user-name ci-deploy --access-key-id OLD_KEY_ID
# 2. Rotate GitHub tokens
gh auth refresh
# 3. Rotate database credentials
kubectl create secret generic db-credentials \
--from-literal=password="$(openssl rand -base64 32)" \
--dry-run=client -o yaml | kubectl apply -f -
# 4. Rotate npm/PyPI publish tokens
npm token revoke <old-token>
npm token create --read-only
# 5. Invalidate all active sessions/JWTs
# Application-specific -- trigger a key rotation in your auth service
--- INTERNAL INCIDENT REPORT ---
Incident ID: SC-YYYY-NNN
Date Detected: YYYY-MM-DD HH:MM UTC
Package: <name>@<version>
Registry: npm / PyPI / crates.io
Advisory: <link to CVE or advisory>
Timeline:
- YYYY-MM-DD HH:MM: Compromised version published to registry
- YYYY-MM-DD HH:MM: First installation in our environment (from CI logs)
- YYYY-MM-DD HH:MM: Compromise detected via <audit tool / advisory / manual review>
- YYYY-MM-DD HH:MM: Pinned to safe version across all repos
- YYYY-MM-DD HH:MM: Completed IOC scan -- no evidence of exploitation
- YYYY-MM-DD HH:MM: All exposed secrets rotated
Blast Radius:
- Repositories affected: N
- Production deployments with compromised version: N
- Secrets potentially exposed: <list>
Root Cause:
<Maintainer account takeover / malicious maintainer / build system compromise>
Remediation:
1. Pinned to safe version
2. Rotated all potentially exposed secrets
3. Deployed clean builds to production
4. Added package to monitoring watch list
Preventive Measures:
1. Enabled hash-pinning for all dependencies
2. Added dependency-review-action to all repos
3. Configured Artifactory proxy with allowlist
4. Scheduled quarterly supply chain audits
| Task | Command |
|------|---------|
| Audit npm | npm audit --json |
| Audit pip | pip-audit -r requirements.txt |
| Audit cargo | cargo audit |
| Scan with OSV | osv-scanner -r . |
| Verify cosign signature | cosign verify --certificate-identity-regexp ... <image> |
| Verify SLSA provenance | slsa-verifier verify-artifact ... |
| Pin GitHub Actions | pin-github-action .github/workflows/*.yml |
| Check lockfile drift | npm ci (fails if lockfile is out of sync) |
| Generate pip hashes | pip-compile --generate-hashes requirements.in |
| Cargo vet check | cargo vet check |
development
Design and operationalize SRE dashboards that surface reliability, latency, error, saturation, and capacity signals across services. Use when building observability views for SLOs, incident response, and executive reliability reporting.
testing
Harden OpenClaw self-hosted environments with baseline host controls, auth tightening, secret handling, network segmentation, and safe update/rollback workflows. Use when deploying OpenClaw in home labs, startups, or production-like local AI infrastructure.
devops
Deploy, manage, and optimize vector databases for AI applications. Covers Qdrant, Weaviate, pgvector, and Pinecone — collection management, indexing strategies, backup, and performance tuning for production RAG and semantic search workloads.
testing
Deploy ML models on Kubernetes with KServe (formerly KFServing) and NVIDIA Triton Inference Server. Includes canary deployments, autoscaling, model versioning, A/B testing, and GPU resource management for production model serving.