Supply Chain Audit
Standalone skill for analyzing the supply chain threat landscape of a project's direct dependencies.
Load skills: zsh-compat
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, from talisman) |
--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+)/)
// Read talisman config
// readTalismanSection: "misc"
const talismanMisc = readTalismanSection("misc") ?? {}
const supplyChainConfig = talismanMisc.supply_chain ?? {}
if (supplyChainConfig.enabled === false) {
log("Supply chain audit is disabled in talisman. Set supply_chain.enabled: true to enable.")
return
}
const maxDeps = maxFlag ? parseInt(maxFlag[1]) : (supplyChainConfig.max_dependencies ?? 50)
const riskThreshold = supplyChainConfig.risk_threshold ?? "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)
Talisman Configuration
# .rune/talisman.yml
supply_chain:
enabled: true # Enable supply chain analysis
max_dependencies: 50 # Cap on dependencies to analyze
risk_threshold: "medium" # Minimum risk level to report: low|medium|high
registries: # Override registry API endpoints (optional)
npm: "https://registry.npmjs.org"
pypi: "https://pypi.org/pypi"
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---5
6# Supply Chain Audit
7
8Standalone skill for analyzing the supply chain threat landscape of a project's direct dependencies.
9
10**Load skills**: `zsh-compat`
11
12## Usage
13
14```bash
15/rune:supply-chain-audit # Auto-detect package manager, analyze all
16/rune:supply-chain-audit --max 20 # Limit to 20 dependencies
17/rune:supply-chain-audit --manager npm # Force specific package manager
18```
19
20## Flags
21
22| Flag | Effect |
23|------|--------|
24| `--max N` | Maximum dependencies to analyze (default: 50, from talisman) |
25| `--manager TYPE` | Force package manager: npm, pip, cargo, go, composer |
26
27## Workflow
28
29```javascript
30const args = "$ARGUMENTS".trim()
31const maxFlag = args.match(/--max\s+(\d+)/)
32const managerFlag = args.match(/--manager\s+(\w+)/)
33
34// Read talisman config
35// readTalismanSection: "misc"
36const talismanMisc = readTalismanSection("misc") ?? {}
37const supplyChainConfig = talismanMisc.supply_chain ?? {}
38
39if (supplyChainConfig.enabled === false) {
40 log("Supply chain audit is disabled in talisman. Set supply_chain.enabled: true to enable.")
41 return
42}
43
44const maxDeps = maxFlag ? parseInt(maxFlag[1]) : (supplyChainConfig.max_dependencies ?? 50)
45const riskThreshold = supplyChainConfig.risk_threshold ?? "medium"
46
47// Step 1: Auto-detect package manager
48const manifests = {
49 npm: "package.json",
50 pip: ["requirements.txt", "pyproject.toml"],
51 cargo: "Cargo.toml",
52 go: "go.mod",
53 composer: "composer.json"
54}
55
56let detectedManagers = []
57if (managerFlag) {
58 detectedManagers = [managerFlag[1]]
59} else {
60 // Scan for manifest files
61 for (const [manager, files] of Object.entries(manifests)) {
62 const fileList = Array.isArray(files) ? files : [files]
63 for (const f of fileList) {
64 if (Glob(f).length > 0) {
65 detectedManagers.push(manager)
66 break
67 }
68 }
69 }
70}
71
72if (detectedManagers.length === 0) {
73 log("No package manifest files found. Supply chain audit requires package.json, requirements.txt, Cargo.toml, go.mod, or composer.json.")
74 return
75}
76
77log(`Detected package managers: ${detectedManagers.join(", ")}`)
78
79// Step 2: Extract dependencies per manager
80let allDeps = []
81
82for (const manager of detectedManagers) {
83 let deps = []
84 switch (manager) {
85 case "npm":
86 // Read package.json, extract .dependencies keys
87 const pkg = JSON.parse(Read("package.json"))
88 deps = Object.keys(pkg.dependencies || {}).map(name => ({
89 name, version: pkg.dependencies[name], manager: "npm"
90 }))
91 break
92 case "pip":
93 // Read requirements.txt, strip version specifiers
94 const reqFile = Glob("requirements.txt").length > 0 ? "requirements.txt" : null
95 if (reqFile) {
96 const lines = Read(reqFile).split("\n")
97 .filter(l => l.trim() && !l.startsWith("#") && !l.startsWith("-"))
98 .map(l => ({ name: l.replace(/[>=<!\[].*$/, "").trim(), version: "*", manager: "pip" }))
99 deps = lines
100 }
101 break
102 case "cargo":
103 // Parse Cargo.toml [dependencies]
104 const cargoContent = Read("Cargo.toml")
105 const depSection = cargoContent.match(/\[dependencies\]([\s\S]*?)(?:\[|$)/)?.[1] || ""
106 deps = depSection.split("\n")
107 .filter(l => l.includes("="))
108 .map(l => ({ name: l.split("=")[0].trim().replace(/"/g, ""), version: l.split("=")[1]?.trim(), manager: "cargo" }))
109 break
110 case "go":
111 // Parse go.mod require block
112 const goContent = Read("go.mod")
113 const requireBlock = goContent.match(/require \(([\s\S]*?)\)/)?.[1] || ""
114 deps = requireBlock.split("\n")
115 .filter(l => l.trim())
116 .map(l => {
117 const parts = l.trim().split(/\s+/)
118 return { name: parts[0], version: parts[1], manager: "go" }
119 })
120 break
121 case "composer":
122 const composer = JSON.parse(Read("composer.json"))
123 deps = Object.keys(composer.require || {})
124 .filter(n => n !== "php" && !n.startsWith("ext-"))
125 .map(name => ({ name, version: composer.require[name], manager: "composer" }))
126 break
127 }
128 allDeps = allDeps.concat(deps)
129}
130
131// Cap at maxDeps
132if (allDeps.length > maxDeps) {
133 log(`Found ${allDeps.length} dependencies, capping at ${maxDeps}`)
134 allDeps = allDeps.slice(0, maxDeps)
135}
136
137log(`Analyzing ${allDeps.length} dependencies...`)
138
139// Step 3: For each dependency, query registry + GitHub for risk signals
140// Uses gh api for GitHub data, npm view / curl for registry data
141// Scores across 6 risk dimensions per the supply-chain-sentinel agent protocol
142
143// Step 4: Generate structured risk report
144// Output format matches supply-chain-sentinel output with risk summary table
145
146// Step 5: For P1/P2 findings, suggest alternatives via WebSearch (if available)
147
148// Present final report to user
149```
150
151## Risk Dimensions
152
153| Dimension | Weight | P1 Threshold | P2 Threshold | P3 Threshold |
154|-----------|--------|-------------|-------------|-------------|
155| Maintainer count | 25% | 0-1 maintainers | 2-3 maintainers | — |
156| Last commit date | 25% | >24 months | 12-24 months | 6-12 months |
157| CVE history | 20% | Unpatched CVEs | 3+ CVEs/2yr | 1-2 CVEs/2yr |
158| Download trajectory | 10% | — | >50% decline | >25% decline |
159| Bus factor | 10% | >90% single | >70% single | >50% single |
160| Security policy | 10% | — | — | Missing SECURITY.md |
161
162## Severity Mapping
163
164- **P1 (Critical)**: Abandoned package with known CVEs, or composite score >= 0.7
165- **P2 (High)**: Single maintainer, or abandoned (>12mo), or composite score >= 0.4
166- **P3 (Medium)**: Weak signals only, composite score >= 0.2
167
168## Output
169
170The skill produces a formatted risk report directly in the conversation with:
171- Risk summary table (all dependencies)
172- Detailed findings for P1/P2/P3 dependencies
173- Alternative package suggestions for high-risk dependencies
174- Packages that could not be analyzed (API failures)
175
176## Talisman Configuration
177
178```yaml
179# .rune/talisman.yml
180supply_chain:
181 enabled: true # Enable supply chain analysis
182 max_dependencies: 50 # Cap on dependencies to analyze
183 risk_threshold: "medium" # Minimum risk level to report: low|medium|high
184 registries: # Override registry API endpoints (optional)
185 npm: "https://registry.npmjs.org"
186 pypi: "https://pypi.org/pypi"
187```
188
189## Error Handling
190
191| Error | Recovery |
192|-------|----------|
193| `gh` CLI not available | Fall back to unauthenticated API calls (60 req/hr limit) |
194| Registry API failure | Mark dependency as UNCERTAIN, continue with others |
195| GitHub API rate limit | Stop GitHub queries, report partial results |
196| No manifest files found | Report and exit gracefully |
197| Private/scoped packages | Skip with note (cannot query public registries) |