Supply Chain Audit
Standalone skill for analyzing the supply chain threat landscape of a project's direct dependencies.
Usage
/rune:supply-chain-audit # Auto-detect package manager, analyze all
/rune:supply-chain-audit --max 20 # Limit to 20 dependencies
/rune:supply-chain-audit --manager npm # Force specific package manager
Flags
| Flag |
Effect |
--max N |
Maximum dependencies to analyze (default: 50) |
--manager TYPE |
Force package manager: npm, pip, cargo, go, composer |
Workflow
const args = "$ARGUMENTS".trim()
const maxFlag = args.match(/--max\s+(\d+)/)
const managerFlag = args.match(/--manager\s+(\w+)/)
// v3.x: defaults baked-in; see references/v3-defaults.md
const maxDeps = maxFlag ? parseInt(maxFlag[1]) : 50
const riskThreshold = "medium"
// Step 1: Auto-detect package manager
const manifests = {
npm: "package.json",
pip: ["requirements.txt", "pyproject.toml"],
cargo: "Cargo.toml",
go: "go.mod",
composer: "composer.json"
}
let detectedManagers = []
if (managerFlag) {
detectedManagers = [managerFlag[1]]
} else {
// Scan for manifest files
for (const [manager, files] of Object.entries(manifests)) {
const fileList = Array.isArray(files) ? files : [files]
for (const f of fileList) {
if (Glob(f).length > 0) {
detectedManagers.push(manager)
break
}
}
}
}
if (detectedManagers.length === 0) {
log("No package manifest files found. Supply chain audit requires package.json, requirements.txt, Cargo.toml, go.mod, or composer.json.")
return
}
log(`Detected package managers: ${detectedManagers.join(", ")}`)
// Step 2: Extract dependencies per manager
let allDeps = []
for (const manager of detectedManagers) {
let deps = []
switch (manager) {
case "npm":
// Read package.json, extract .dependencies keys
const pkg = JSON.parse(Read("package.json"))
deps = Object.keys(pkg.dependencies || {}).map(name => ({
name, version: pkg.dependencies[name], manager: "npm"
}))
break
case "pip":
// Read requirements.txt, strip version specifiers
const reqFile = Glob("requirements.txt").length > 0 ? "requirements.txt" : null
if (reqFile) {
const lines = Read(reqFile).split("\n")
.filter(l => l.trim() && !l.startsWith("#") && !l.startsWith("-"))
.map(l => ({ name: l.replace(/[>=<!\[].*$/, "").trim(), version: "*", manager: "pip" }))
deps = lines
}
break
case "cargo":
// Parse Cargo.toml [dependencies]
const cargoContent = Read("Cargo.toml")
const depSection = cargoContent.match(/\[dependencies\]([\s\S]*?)(?:\[|$)/)?.[1] || ""
deps = depSection.split("\n")
.filter(l => l.includes("="))
.map(l => ({ name: l.split("=")[0].trim().replace(/"/g, ""), version: l.split("=")[1]?.trim(), manager: "cargo" }))
break
case "go":
// Parse go.mod require block
const goContent = Read("go.mod")
const requireBlock = goContent.match(/require \(([\s\S]*?)\)/)?.[1] || ""
deps = requireBlock.split("\n")
.filter(l => l.trim())
.map(l => {
const parts = l.trim().split(/\s+/)
return { name: parts[0], version: parts[1], manager: "go" }
})
break
case "composer":
const composer = JSON.parse(Read("composer.json"))
deps = Object.keys(composer.require || {})
.filter(n => n !== "php" && !n.startsWith("ext-"))
.map(name => ({ name, version: composer.require[name], manager: "composer" }))
break
}
allDeps = allDeps.concat(deps)
}
// Cap at maxDeps
if (allDeps.length > maxDeps) {
log(`Found ${allDeps.length} dependencies, capping at ${maxDeps}`)
allDeps = allDeps.slice(0, maxDeps)
}
log(`Analyzing ${allDeps.length} dependencies...`)
// Step 3: For each dependency, query registry + GitHub for risk signals
// Uses gh api for GitHub data, npm view / curl for registry data
// Scores across 6 risk dimensions per the supply-chain-sentinel agent protocol
// Step 4: Generate structured risk report
// Output format matches supply-chain-sentinel output with risk summary table
// Step 5: For P1/P2 findings, suggest alternatives via WebSearch (if available)
// Present final report to user
Risk Dimensions
| Dimension |
Weight |
P1 Threshold |
P2 Threshold |
P3 Threshold |
| Maintainer count |
25% |
0-1 maintainers |
2-3 maintainers |
— |
| Last commit date |
25% |
>24 months |
12-24 months |
6-12 months |
| CVE history |
20% |
Unpatched CVEs |
3+ CVEs/2yr |
1-2 CVEs/2yr |
| Download trajectory |
10% |
— |
>50% decline |
>25% decline |
| Bus factor |
10% |
>90% single |
>70% single |
>50% single |
| Security policy |
10% |
— |
— |
Missing SECURITY.md |
Severity Mapping
- P1 (Critical): Abandoned package with known CVEs, or composite score >= 0.7
- P2 (High): Single maintainer, or abandoned (>12mo), or composite score >= 0.4
- P3 (Medium): Weak signals only, composite score >= 0.2
Output
The skill produces a formatted risk report directly in the conversation with:
- Risk summary table (all dependencies)
- Detailed findings for P1/P2/P3 dependencies
- Alternative package suggestions for high-risk dependencies
- Packages that could not be analyzed (API failures)
Configuration (v3.x baked-in defaults)
In v3.x there is no talisman.yml user config layer — these values are inlined at the consumer call sites above:
| Key |
Value |
enabled |
true (always on) |
max_dependencies |
50 |
risk_threshold |
"medium" |
registries.npm |
"https://registry.npmjs.org" |
registries.pypi |
"https://pypi.org/pypi" |
See references/v3-defaults.md for the canonical source-of-truth.
Error Handling
| Error |
Recovery |
gh CLI not available |
Fall back to unauthenticated API calls (60 req/hr limit) |
| Registry API failure |
Mark dependency as UNCERTAIN, continue with others |
| GitHub API rate limit |
Stop GitHub queries, report partial results |
| No manifest files found |
Report and exit gracefully |
| Private/scoped packages |
Skip with note (cannot query public registries) |
1---2name: supply-chain-audit3description: Analyze project dependencies for supply chain risks. Checks maintainer count, commit frequency, CVE history, abandonment signals, bus factor, and security policy presence for each direct dependency. Supports npm, pip, cargo, go mod, and composer. Use when: "supply chain audit", "dependency risk", "check dependencies", "maintainer risk", "abandoned packages", "dependency health", "package security", "supply chain risk".4---56<!-- v3.x: defaults baked from former talisman.misc; see references/v3-defaults.md -->78# Supply Chain Audit910Standalone skill for analyzing the supply chain threat landscape of a project's direct dependencies.1112## Usage1314```bash15/rune:supply-chain-audit # Auto-detect package manager, analyze all16/rune:supply-chain-audit --max 20 # Limit to 20 dependencies17/rune:supply-chain-audit --manager npm # Force specific package manager18```1920## Flags2122| Flag | Effect |23|------|--------|24| `--max N` | Maximum dependencies to analyze (default: 50) |25| `--manager TYPE` | Force package manager: npm, pip, cargo, go, composer |2627## Workflow2829```javascript30const args = "$ARGUMENTS".trim()31const maxFlag = args.match(/--max\s+(\d+)/)32const managerFlag = args.match(/--manager\s+(\w+)/)3334// v3.x: defaults baked-in; see references/v3-defaults.md35const maxDeps = maxFlag ? parseInt(maxFlag[1]) : 5036const riskThreshold = "medium"3738// Step 1: Auto-detect package manager39const manifests = {40 npm: "package.json",41 pip: ["requirements.txt", "pyproject.toml"],42 cargo: "Cargo.toml",43 go: "go.mod",44 composer: "composer.json"45}4647let detectedManagers = []48if (managerFlag) {49 detectedManagers = [managerFlag[1]]50} else {51 // Scan for manifest files52 for (const [manager, files] of Object.entries(manifests)) {53 const fileList = Array.isArray(files) ? files : [files]54 for (const f of fileList) {55 if (Glob(f).length > 0) {56 detectedManagers.push(manager)57 break58 }59 }60 }61}6263if (detectedManagers.length === 0) {64 log("No package manifest files found. Supply chain audit requires package.json, requirements.txt, Cargo.toml, go.mod, or composer.json.")65 return66}6768log(`Detected package managers: ${detectedManagers.join(", ")}`)6970// Step 2: Extract dependencies per manager71let allDeps = []7273for (const manager of detectedManagers) {74 let deps = []75 switch (manager) {76 case "npm":77 // Read package.json, extract .dependencies keys78 const pkg = JSON.parse(Read("package.json"))79 deps = Object.keys(pkg.dependencies || {}).map(name => ({80 name, version: pkg.dependencies[name], manager: "npm"81 }))82 break83 case "pip":84 // Read requirements.txt, strip version specifiers85 const reqFile = Glob("requirements.txt").length > 0 ? "requirements.txt" : null86 if (reqFile) {87 const lines = Read(reqFile).split("\n")88 .filter(l => l.trim() && !l.startsWith("#") && !l.startsWith("-"))89 .map(l => ({ name: l.replace(/[>=<!\[].*$/, "").trim(), version: "*", manager: "pip" }))90 deps = lines91 }92 break93 case "cargo":94 // Parse Cargo.toml [dependencies]95 const cargoContent = Read("Cargo.toml")96 const depSection = cargoContent.match(/\[dependencies\]([\s\S]*?)(?:\[|$)/)?.[1] || ""97 deps = depSection.split("\n")98 .filter(l => l.includes("="))99 .map(l => ({ name: l.split("=")[0].trim().replace(/"/g, ""), version: l.split("=")[1]?.trim(), manager: "cargo" }))100 break101 case "go":102 // Parse go.mod require block103 const goContent = Read("go.mod")104 const requireBlock = goContent.match(/require \(([\s\S]*?)\)/)?.[1] || ""105 deps = requireBlock.split("\n")106 .filter(l => l.trim())107 .map(l => {108 const parts = l.trim().split(/\s+/)109 return { name: parts[0], version: parts[1], manager: "go" }110 })111 break112 case "composer":113 const composer = JSON.parse(Read("composer.json"))114 deps = Object.keys(composer.require || {})115 .filter(n => n !== "php" && !n.startsWith("ext-"))116 .map(name => ({ name, version: composer.require[name], manager: "composer" }))117 break118 }119 allDeps = allDeps.concat(deps)120}121122// Cap at maxDeps123if (allDeps.length > maxDeps) {124 log(`Found ${allDeps.length} dependencies, capping at ${maxDeps}`)125 allDeps = allDeps.slice(0, maxDeps)126}127128log(`Analyzing ${allDeps.length} dependencies...`)129130// Step 3: For each dependency, query registry + GitHub for risk signals131// Uses gh api for GitHub data, npm view / curl for registry data132// Scores across 6 risk dimensions per the supply-chain-sentinel agent protocol133134// Step 4: Generate structured risk report135// Output format matches supply-chain-sentinel output with risk summary table136137// Step 5: For P1/P2 findings, suggest alternatives via WebSearch (if available)138139// Present final report to user140```141142## Risk Dimensions143144| Dimension | Weight | P1 Threshold | P2 Threshold | P3 Threshold |145|-----------|--------|-------------|-------------|-------------|146| Maintainer count | 25% | 0-1 maintainers | 2-3 maintainers | — |147| Last commit date | 25% | >24 months | 12-24 months | 6-12 months |148| CVE history | 20% | Unpatched CVEs | 3+ CVEs/2yr | 1-2 CVEs/2yr |149| Download trajectory | 10% | — | >50% decline | >25% decline |150| Bus factor | 10% | >90% single | >70% single | >50% single |151| Security policy | 10% | — | — | Missing SECURITY.md |152153## Severity Mapping154155- **P1 (Critical)**: Abandoned package with known CVEs, or composite score >= 0.7156- **P2 (High)**: Single maintainer, or abandoned (>12mo), or composite score >= 0.4157- **P3 (Medium)**: Weak signals only, composite score >= 0.2158159## Output160161The skill produces a formatted risk report directly in the conversation with:162- Risk summary table (all dependencies)163- Detailed findings for P1/P2/P3 dependencies164- Alternative package suggestions for high-risk dependencies165- Packages that could not be analyzed (API failures)166167## Configuration (v3.x baked-in defaults)168169In v3.x there is no `talisman.yml` user config layer — these values are inlined at the consumer call sites above:170171| Key | Value |172|---|---|173| `enabled` | `true` (always on) |174| `max_dependencies` | `50` |175| `risk_threshold` | `"medium"` |176| `registries.npm` | `"https://registry.npmjs.org"` |177| `registries.pypi` | `"https://pypi.org/pypi"` |178179See [references/v3-defaults.md](../../references/v3-defaults.md) for the canonical source-of-truth.180181## Error Handling182183| Error | Recovery |184|-------|----------|185| `gh` CLI not available | Fall back to unauthenticated API calls (60 req/hr limit) |186| Registry API failure | Mark dependency as UNCERTAIN, continue with others |187| GitHub API rate limit | Stop GitHub queries, report partial results |188| No manifest files found | Report and exit gracefully |189| Private/scoped packages | Skip with note (cannot query public registries) |