Vibecosystem supply-chain-security
Typosquatting detection, install script analysis, dependency confusion prevention, and phantom dependency detection for npm/pip.
install
source · Clone the upstream repo
git clone https://github.com/vibeeval/vibecosystem
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/vibeeval/vibecosystem "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/supply-chain-security" ~/.claude/skills/vibeeval-vibecosystem-supply-chain-security && rm -rf "$T"
manifest:
skills/supply-chain-security/SKILL.mdsource content
Supply Chain Security
Patterns for auditing third-party dependencies before they own your production environment.
Typosquatting Detection
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:
- Character transposition:
→lodashlodahs - Missing character:
→expressexpres - Extra character:
→chalkcchalk - Hyphen/underscore swap:
→my-libmy_lib - Homoglyph:
instead ofrn
in package namem
Install Script Audit
# 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')
Dependency Confusion Risk Assessment
# 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
Phantom Dependency Detection
# 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`) } } }
Lock File Integrity Verification
# 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 audit / pip-audit Integration
# 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
SBOM Generation
# 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
Known Malicious Package Indicators
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)
Dependency Pinning Strategy
// 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"] }
Dependency Risk Scoring
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 |
Risk Assessment Template
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>
Automated Risk Check
# 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.