skills/supply-chain-security/SKILL.md
Typosquatting detection, install script analysis, dependency confusion prevention, and phantom dependency detection for npm/pip.
npx skillsauth add rubicanjr/FinCognis supply-chain-securityInstall 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.
Patterns for auditing third-party dependencies before they own your production environment.
Common substitution patterns attackers use against popular packages:
# Levenshtein distance check against known-good package list
def levenshtein(a: str, b: str) -> int:
if len(a) < len(b):
return levenshtein(b, a)
if not b:
return len(a)
prev = list(range(len(b) + 1))
for i, ca in enumerate(a):
curr = [i + 1]
for j, cb in enumerate(b):
curr.append(min(prev[j + 1] + 1, curr[j] + 1, prev[j] + (ca != cb)))
prev = curr
return prev[-1]
POPULAR_PACKAGES = ['express', 'lodash', 'react', 'axios', 'moment', 'chalk']
def is_typosquat(pkg: str, threshold: int = 2) -> list[str]:
return [p for p in POPULAR_PACKAGES if 0 < levenshtein(pkg, p) <= threshold]
# Examples of known typosquats
typosquats = {
'lodahs': 'lodash', # character swap
'expres': 'express', # missing char
'reakt': 'react', # phonetic substitution
'axois': 'axios', # transposition
'momnet': 'moment', # transposition
}
Common substitution patterns to check manually:
lodash → lodahsexpress → expreschalk → cchalkmy-lib → my_librn instead of m in package name# List all packages with install scripts (npm)
npm query ":root > *" | \
node -e "const d=require('/dev/stdin');
Object.entries(d.dependencies||{}).forEach(([k,v])=>{
const pkg = require(\`./node_modules/\${k}/package.json\`);
if(pkg.scripts?.preinstall||pkg.scripts?.postinstall||pkg.scripts?.install)
console.log(k, Object.keys(pkg.scripts))
})"
# Safer: use npm pack --dry-run to see what a package would install
npm pack lodash --dry-run
# Check a specific package's install scripts before installing
npm show <package> scripts
// Automated install script audit
import { execSync } from 'child_process'
import fs from 'fs'
import path from 'path'
function auditInstallScripts(nodeModulesDir: string): void {
const packages = fs.readdirSync(nodeModulesDir)
for (const pkg of packages) {
const pkgJsonPath = path.join(nodeModulesDir, pkg, 'package.json')
if (!fs.existsSync(pkgJsonPath)) continue
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'))
const scripts = pkgJson.scripts ?? {}
const suspicious = ['preinstall', 'postinstall', 'install']
.filter(s => s in scripts)
if (suspicious.length > 0) {
console.warn(`[WARN] ${pkg} has install scripts: ${suspicious.join(', ')}`)
console.warn(` ${scripts[suspicious[0]]}`)
}
}
}
auditInstallScripts('./node_modules')
# Check if your internal package names exist on the public registry
# If they do AND the public version is newer, npm may pull the public one
INTERNAL_PACKAGES=("@mycompany/auth" "@mycompany/utils" "internal-logger")
for pkg in "${INTERNAL_PACKAGES[@]}"; do
result=$(npm show "$pkg" version 2>/dev/null)
if [ -n "$result" ]; then
echo "RISK: $pkg exists on public registry at version $result"
else
echo "OK: $pkg not on public registry"
fi
done
Prevention in .npmrc:
# Force scoped packages to your private registry
@mycompany:registry=https://npm.mycompany.com
@internal:registry=https://npm.mycompany.com
# Block public fallback for scoped packages
//npm.mycompany.com/:always-auth=true
# Find imports/requires that aren't in package.json
# Node.js (using depcheck)
npx depcheck --ignores="@types/*" .
# Python (using pipreqs)
pip install pipreqs
pipreqs . --print | sort > required.txt
pip freeze | sort > installed.txt
diff required.txt installed.txt
// Custom phantom dep detector for TypeScript
import { execSync } from 'child_process'
import fs from 'fs'
function detectPhantomDeps(srcDir: string): void {
const pkgJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'))
const declared = new Set([
...Object.keys(pkgJson.dependencies ?? {}),
...Object.keys(pkgJson.devDependencies ?? {}),
])
// Extract all import/require statements
const imports = execSync(
`rg --no-heading -oh "from ['\"]([^./][^'\"]+)['\"]" ${srcDir} | sed "s/from ['\"]//;s/['\"]//;s/\\/.*//"`
).toString().split('\n').filter(Boolean)
const unique = new Set(imports)
for (const imp of unique) {
const root = imp.startsWith('@') ? imp.split('/').slice(0, 2).join('/') : imp.split('/')[0]
if (!declared.has(root)) {
console.warn(`[PHANTOM] "${root}" is imported but not in package.json`)
}
}
}
# Verify package-lock.json integrity (npm)
npm ci --dry-run
# Detect if lock file is out of sync with package.json
npm install --package-lock-only
git diff --exit-code package-lock.json
# pip: verify requirements are pinned with hashes
pip install --require-hashes -r requirements.txt
# Generate hash-pinned requirements
pip-compile --generate-hashes requirements.in -o requirements.txt
# npm - break build on high severity
npm audit --audit-level=high
# pip-audit
pip install pip-audit
pip-audit --requirement requirements.txt --fail-on CRITICAL,HIGH
# GitHub Actions integration
- name: Security audit
run: |
npm audit --audit-level=moderate
npx better-npm-audit audit --level moderate
# CycloneDX for Node.js
npm install -g @cyclonedx/cyclonedx-npm
cyclonedx-npm --output-format JSON --output-file sbom.json
# CycloneDX for Python
pip install cyclonedx-bom
cyclonedx-bom -r -o sbom.xml
# SPDX via syft
syft . -o spdx-json > sbom.spdx.json
Red flags when reviewing a new dependency:
- Install scripts that curl | bash external URLs
- Package published < 48 hours ago with thousands of weekly downloads
- Zero issues/stars on GitHub but high download count
- package.json "main" points to an obfuscated file
- Dependencies on packages with random hex names
- Author email is a free provider with a new account
- Version bump from 1.x to 99.x (dependency confusion attack)
// package.json: exact pinning for production deps
{
"dependencies": {
"express": "4.18.2", // exact - no caret/tilde
"lodash": "4.17.21"
},
"devDependencies": {
"typescript": "^5.3.0" // range OK for dev tools
}
}
# Convert all ranges to exact versions
npm shrinkwrap
# or use: npm-shrinkwrap.json over package-lock.json for published packages
# Renovate bot for automated safe updates with PR-per-dep
# renovate.json
{
"extends": ["config:base"],
"automerge": false,
"reviewers": ["security-team"]
}
Score each dependency on a 0-100 risk scale before adoption:
| Signal | Low Risk (0-2) | Medium Risk (3-5) | High Risk (6-10) | |--------|---------------|-------------------|------------------| | Age | > 3 years | 1-3 years | < 1 year | | Maintainers | 3+ active | 1-2 active | 1 inactive | | Downloads | > 1M/week | 100K-1M/week | < 100K/week | | GitHub stars | > 5K | 500-5K | < 500 | | Last commit | < 3 months | 3-12 months | > 12 months | | Open issues | < 50 | 50-200 | > 200 unresolved | | Install scripts | None | postinstall (build) | preinstall + network | | Dependencies | < 5 | 5-20 | > 20 transitive | | CVE history | 0 unfixed | Fixed promptly | Unfixed > 30 days | | License | MIT/Apache/BSD | LGPL/MPL | GPL/AGPL/Unknown |
Package: <name>@<version>
Purpose: <why we need it>
Alternatives: <what else we considered>
Signals:
Age: X years (score: N)
Maintainers: X active (score: N)
Downloads: X/week (score: N)
Last commit: X days ago (score: N)
Dependencies: X transitive (score: N)
CVEs: X unfixed (score: N)
Total Risk Score: XX/100
Decision: [ADOPT | REVIEW | REJECT]
Reviewed by: <name>
Date: <date>
# npm: check package metadata
npm show <package> time modified maintainers repository
# Check download stats
curl -s "https://api.npmjs.org/downloads/point/last-week/<package>" | jq .downloads
# Check for known vulnerabilities
npm audit --json | jq '.vulnerabilities["<package>"]'
# Python: check PyPI metadata
pip show <package>
pip-audit -r requirements.txt --format json
Rule: Never npm install <unknown-package> in production without running npm audit, checking install scripts, and verifying the package name against known typosquats.
Dependency risk scoring adapted from Trail of Bits supply-chain-risk-auditor plugin.
development
Goal-based workflow orchestration - routes tasks to specialist agents based on user goals
tools
Wiring Verification
development
Connection management, room patterns, reconnection strategies, message buffering, and binary protocol design.
development
Screenshot comparison QA for frontend development. Takes a screenshot of the current implementation, scores it across multiple visual dimensions, and returns a structured PASS/REVISE/FAIL verdict with concrete fixes. Use when implementing UI from a design reference or verifying visual correctness.