@hungpg/skill-audit 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -2
- package/SKILL.md +63 -7
- package/dist/deps.js +238 -40
- package/dist/hooks.js +278 -0
- package/dist/index.js +87 -10
- package/dist/intel.js +208 -1
- package/dist/patterns.js +67 -0
- package/dist/security.js +96 -15
- package/package.json +4 -2
- package/rules/default-patterns.json +99 -0
- package/scripts/postinstall.cjs +190 -0
package/README.md
CHANGED
|
@@ -12,10 +12,109 @@ Security auditing CLI for AI agent skills.
|
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
|
+
### Option 1: Install via npm (Recommended for CLI)
|
|
16
|
+
|
|
15
17
|
```bash
|
|
16
18
|
npm install -g @hungpg/skill-audit
|
|
17
19
|
```
|
|
18
20
|
|
|
21
|
+
This installs the CLI globally and triggers the postinstall hook prompt.
|
|
22
|
+
|
|
23
|
+
### Option 2: Install via bun (Fast Alternative)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
bun install -g @hungpg/skill-audit
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Bun is significantly faster than npm for installation.
|
|
30
|
+
|
|
31
|
+
### Option 3: Install as a Skill (For Claude Code)
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Install from GitHub repo (not npm package name)
|
|
35
|
+
npx skills add harrypham2000/skill-audit -g -y
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> ⚠️ **Important**: The skills CLI expects `owner/repo` format, not npm scoped packages.
|
|
39
|
+
> - ✅ Correct: `harrypham2000/skill-audit`
|
|
40
|
+
> - ❌ Incorrect: `@hungpg/skill-audit`
|
|
41
|
+
|
|
42
|
+
### Option 4: Install for Qwen Code
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Clone to Qwen skills directory
|
|
46
|
+
mkdir -p ~/.qwen/skills
|
|
47
|
+
git clone https://github.com/harrypham2000/skill-audit.git ~/.qwen/skills/skill-audit
|
|
48
|
+
cd ~/.qwen/skills/skill-audit/skill-audit
|
|
49
|
+
npm install && npm run build
|
|
50
|
+
|
|
51
|
+
# Or with bun (faster)
|
|
52
|
+
bun install && bun run build
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Option 5: Install for Gemini CLI
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Clone to Gemini skills directory
|
|
59
|
+
mkdir -p ~/.gemini/skills
|
|
60
|
+
git clone https://github.com/harrypham2000/skill-audit.git ~/.gemini/skills/skill-audit
|
|
61
|
+
cd ~/.gemini/skills/skill-audit/skill-audit
|
|
62
|
+
npm install && npm run build
|
|
63
|
+
|
|
64
|
+
# Or with bun (faster)
|
|
65
|
+
bun install && bun run build
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Automatic Hook Setup
|
|
69
|
+
|
|
70
|
+
During npm installation, you'll be prompted to set up a **PreToolUse hook** that automatically audits skills before installation:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
╔════════════════════════════════════════════════════════════╗
|
|
74
|
+
║ 🛡️ skill-audit hook setup ║
|
|
75
|
+
╠════════════════════════════════════════════════════════════╣
|
|
76
|
+
║ ║
|
|
77
|
+
║ skill-audit can automatically audit skills before ║
|
|
78
|
+
║ installation to protect you from malicious packages. ║
|
|
79
|
+
║ ║
|
|
80
|
+
║ When you run 'npx skills add <package>', the hook will: ║
|
|
81
|
+
║ • Scan the skill for security vulnerabilities ║
|
|
82
|
+
║ • Check for prompt injection, secrets, code execution ║
|
|
83
|
+
║ • Block installation if risk score > 3.0 ║
|
|
84
|
+
║ ║
|
|
85
|
+
╚════════════════════════════════════════════════════════════╝
|
|
86
|
+
|
|
87
|
+
Options:
|
|
88
|
+
[Y] Yes, install the hook (recommended)
|
|
89
|
+
[N] No, skip for now
|
|
90
|
+
[S] Skip forever (don't ask again)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Manual Hook Management
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Install hook manually
|
|
97
|
+
skill-audit --install-hook
|
|
98
|
+
|
|
99
|
+
# Install with custom threshold
|
|
100
|
+
skill-audit --install-hook --hook-threshold 5.0
|
|
101
|
+
|
|
102
|
+
# Check hook status
|
|
103
|
+
skill-audit --hook-status
|
|
104
|
+
|
|
105
|
+
# Remove hook
|
|
106
|
+
skill-audit --uninstall-hook
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### How the Hook Works
|
|
110
|
+
|
|
111
|
+
1. **Trigger**: When you run `npx skills add <package>`
|
|
112
|
+
2. **Scan**: skill-audit analyzes the skill before installation
|
|
113
|
+
3. **Decision**:
|
|
114
|
+
- Risk score ≤ 3.0 → Installation proceeds
|
|
115
|
+
- Risk score > 3.0 → Installation blocked
|
|
116
|
+
4. **Report**: Detailed findings shown in terminal
|
|
117
|
+
|
|
19
118
|
## Usage
|
|
20
119
|
|
|
21
120
|
```bash
|
|
@@ -59,6 +158,11 @@ skill-audit --update-db
|
|
|
59
158
|
| `-o, --output <file>` | Save to file | |
|
|
60
159
|
| `--no-deps` | Skip dependency scan | |
|
|
61
160
|
| `-v, --verbose` | Verbose output | |
|
|
161
|
+
| `--install-hook` | Install PreToolUse hook | |
|
|
162
|
+
| `--uninstall-hook` | Remove PreToolUse hook | |
|
|
163
|
+
| `--hook-threshold <score>` | Hook risk threshold | 3.0 |
|
|
164
|
+
| `--hook-status` | Show hook status | |
|
|
165
|
+
| `--block` | Exit 1 if threshold exceeded | |
|
|
62
166
|
|
|
63
167
|
## Exit Codes
|
|
64
168
|
|
|
@@ -89,9 +193,11 @@ Feeds are cached locally with automatic freshness checks:
|
|
|
89
193
|
|
|
90
194
|
| Source | Update Frequency | Cache Lifetime |
|
|
91
195
|
|--------|------------------|----------------|
|
|
92
|
-
| CISA KEV | Daily |
|
|
93
|
-
|
|
|
196
|
+
| CISA KEV | Daily | 1 day |
|
|
197
|
+
| NIST NVD | Daily | 1 day |
|
|
198
|
+
| FIRST EPSS | Daily | 3 days |
|
|
94
199
|
| OSV.dev | On-query | 7 days |
|
|
200
|
+
| GHSA | On-query | 3 days |
|
|
95
201
|
|
|
96
202
|
**Automatic updates:**
|
|
97
203
|
- Runs on `npm install` via `postinstall` hook
|
|
@@ -100,6 +206,20 @@ Feeds are cached locally with automatic freshness checks:
|
|
|
100
206
|
|
|
101
207
|
**Stale cache warning:** Audit output warns if feeds are >3 days old.
|
|
102
208
|
|
|
209
|
+
### NVD Synchronization
|
|
210
|
+
|
|
211
|
+
The `--update-db` command fetches CVEs modified in the last 24 hours only.
|
|
212
|
+
For initial setup or after extended offline periods, run multiple times to build historical data:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# Multiple updates to build historical data
|
|
216
|
+
skill-audit --update-db
|
|
217
|
+
skill-audit --update-db
|
|
218
|
+
skill-audit --update-db
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Note: NVD API rate limits apply (5 requests/30 sec without API key). Set `NVD_API_KEY` environment variable for 50 requests/30 sec.
|
|
222
|
+
|
|
103
223
|
## Trust Sources
|
|
104
224
|
|
|
105
225
|
1. Static pattern matching for known attack vectors
|
package/SKILL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: skill-audit
|
|
3
|
-
description: This skill should be used when the user asks to "audit AI agent skills for security vulnerabilities", "evaluate third-party skills before installing", "check for prompt injection or secrets leakage", "scan skills for code execution risks", "validate skills against Agent Skills specification", or "assess skill security posture with CVE/GHSA/KEV/EPSS intelligence".
|
|
3
|
+
description: This skill should be used when the user asks to "audit AI agent skills for security vulnerabilities", "evaluate third-party skills before installing", "check for prompt injection or secrets leakage", "scan skills for code execution risks", "validate skills against Agent Skills specification", or "assess skill security posture with CVE/GHSA/KEV/EPSS/NVD intelligence".
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Node.js 18+ with npm or yarn
|
|
6
6
|
metadata:
|
|
7
|
-
repo: https://github.com/
|
|
8
|
-
version: 0.
|
|
7
|
+
repo: https://github.com/harrypham2000/skill-audit
|
|
8
|
+
version: 0.3.0
|
|
9
9
|
allowed-tools:
|
|
10
10
|
- skill:exec
|
|
11
11
|
- skill:read
|
|
@@ -14,7 +14,56 @@ allowed-tools:
|
|
|
14
14
|
|
|
15
15
|
# skill-audit
|
|
16
16
|
|
|
17
|
-
Security auditing CLI for AI agent skills
|
|
17
|
+
Security auditing CLI for AI agent skills.
|
|
18
|
+
|
|
19
|
+
## Installation for Agents
|
|
20
|
+
|
|
21
|
+
### Claude Code
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Option 1: Install as skill from GitHub
|
|
25
|
+
npx skills add harrypham2000/skill-audit -g -y
|
|
26
|
+
|
|
27
|
+
# Option 2: Install CLI via npm
|
|
28
|
+
npm install -g @hungpg/skill-audit
|
|
29
|
+
|
|
30
|
+
# Option 3: Install CLI via bun (faster)
|
|
31
|
+
bun install -g @hungpg/skill-audit
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Qwen Code
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Clone to Qwen skills directory
|
|
38
|
+
mkdir -p ~/.qwen/skills
|
|
39
|
+
git clone https://github.com/harrypham2000/skill-audit.git ~/.qwen/skills/skill-audit
|
|
40
|
+
cd ~/.qwen/skills/skill-audit/skill-audit
|
|
41
|
+
|
|
42
|
+
# Install with npm
|
|
43
|
+
npm install && npm run build
|
|
44
|
+
|
|
45
|
+
# Or with bun (faster)
|
|
46
|
+
bun install && bun run build
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Gemini CLI
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Clone to Gemini skills directory
|
|
53
|
+
mkdir -p ~/.gemini/skills
|
|
54
|
+
git clone https://github.com/harrypham2000/skill-audit.git ~/.gemini/skills/skill-audit
|
|
55
|
+
cd ~/.gemini/skills/skill-audit/skill-audit
|
|
56
|
+
|
|
57
|
+
# Install with npm
|
|
58
|
+
npm install && npm run build
|
|
59
|
+
|
|
60
|
+
# Or with bun (faster)
|
|
61
|
+
bun install && bun run build
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
> ⚠️ **Important for Skills CLI**: Use `owner/repo` format, not npm scoped names.
|
|
65
|
+
> - ✅ Correct: `harrypham2000/skill-audit`
|
|
66
|
+
> - ❌ Incorrect: `@hungpg/skill-audit`
|
|
18
67
|
|
|
19
68
|
## When to Use
|
|
20
69
|
|
|
@@ -75,6 +124,8 @@ Full security audit including:
|
|
|
75
124
|
Pulls latest vulnerability intelligence:
|
|
76
125
|
- CISA KEV (Known Exploited Vulnerabilities)
|
|
77
126
|
- FIRST EPSS (Exploit Prediction Scoring) - via api.first.org/data/v1
|
|
127
|
+
- NIST NVD (National Vulnerability Database) - CVSS scores, CWE mappings
|
|
128
|
+
- GitHub Security Advisories (GHSA) - ecosystem-specific advisories
|
|
78
129
|
- OSV.dev vulnerabilities
|
|
79
130
|
|
|
80
131
|
Caches to `.cache/skill-audit/feeds/` for offline use.
|
|
@@ -148,7 +199,7 @@ npx skill-audit -g -o ./audit-report.json
|
|
|
148
199
|
npx skill-audit -g -t 3.0
|
|
149
200
|
|
|
150
201
|
# Update intelligence feeds
|
|
151
|
-
npx skill-audit --update-db --source kev epss
|
|
202
|
+
npx skill-audit --update-db --source kev epss nvd
|
|
152
203
|
|
|
153
204
|
# Audit project-level skills only
|
|
154
205
|
npx skill-audit -p --mode audit -v
|
|
@@ -164,7 +215,7 @@ Found 3 skills
|
|
|
164
215
|
Safe: 1 | Risky: 1 | Dangerous: 1 | Malicious: 0
|
|
165
216
|
Skills with spec issues: 1 | Security issues: 2
|
|
166
217
|
|
|
167
|
-
⚠️ Vulnerability DB is stale (4.2 days for KEV, 5.1 days for EPSS)
|
|
218
|
+
⚠️ Vulnerability DB is stale (4.2 days for KEV, 5.1 days for EPSS, 2.0 days for NVD)
|
|
168
219
|
Run: npx skill-audit --update-db
|
|
169
220
|
|
|
170
221
|
❌ 1 skills exceed threshold 3.0
|
|
@@ -189,8 +240,9 @@ Three-layer validation approach:
|
|
|
189
240
|
- Maps to OWASP Agentic Top 10
|
|
190
241
|
|
|
191
242
|
3. **Intelligence Service**
|
|
192
|
-
- Caches CVE/GHSA/KEV/EPSS data
|
|
243
|
+
- Caches CVE/GHSA/KEV/EPSS/NVD data
|
|
193
244
|
- Native HTTP/fetch (no shell dependencies)
|
|
245
|
+
- Differentiated cache lifetimes by source (KEV/NVD: 1 day, EPSS/GHSA: 3 days, OSV: 7 days)
|
|
194
246
|
|
|
195
247
|
## Related Skills
|
|
196
248
|
|
|
@@ -216,6 +268,8 @@ Three-layer validation approach:
|
|
|
216
268
|
- **[OWASP AI Security Top 10](https://owasp.org/www-project-top-ten.html)** - ASI01-ASI10 threat categories
|
|
217
269
|
- **[CISA KEV Catalog](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)** - Actively exploited vulnerabilities
|
|
218
270
|
- **[FIRST EPSS](https://www.first.org/epss/)** - Exploit Prediction Scoring System
|
|
271
|
+
- **[NIST NVD](https://nvd.nist.gov/)** - National Vulnerability Database (official CVE database)
|
|
272
|
+
- **[GitHub Security Advisories](https://github.com/advisories)** - GHSA vulnerability database
|
|
219
273
|
- **[OSV.dev](https://osv.dev/)** - Open Source Vulnerability database
|
|
220
274
|
|
|
221
275
|
### Intelligence Cache
|
|
@@ -223,5 +277,7 @@ Three-layer validation approach:
|
|
|
223
277
|
| Source | Update Frequency | Max Cache Age | Warning Threshold |
|
|
224
278
|
|--------|-----------------|---------------|-------------------|
|
|
225
279
|
| CISA KEV | Daily | 1 day | 3 days |
|
|
280
|
+
| NIST NVD | Daily | 1 day | 3 days |
|
|
281
|
+
| GitHub GHSA | 3 days | 3 days | 3 days |
|
|
226
282
|
| FIRST EPSS | 3-day cycle | 3 days | 3 days |
|
|
227
283
|
| OSV.dev | On-query | 7 days | 3 days |
|
package/dist/deps.js
CHANGED
|
@@ -14,6 +14,52 @@ const OSV_ECOSYSTEMS = {
|
|
|
14
14
|
'RubyGems': 'ruby',
|
|
15
15
|
'Packagist': 'php',
|
|
16
16
|
'Pub': 'dart',
|
|
17
|
+
'NuGet': 'dotnet',
|
|
18
|
+
'Hex': 'elixir',
|
|
19
|
+
'ConanCenter': 'cpp',
|
|
20
|
+
'Bioconductor': 'r',
|
|
21
|
+
'SwiftURL': 'swift',
|
|
22
|
+
};
|
|
23
|
+
// Supported lockfile patterns and their ecosystems
|
|
24
|
+
const LOCKFILE_PATTERNS = {
|
|
25
|
+
// JavaScript/TypeScript
|
|
26
|
+
'package-lock.json': { ecosystem: 'npm', parser: 'json' },
|
|
27
|
+
'yarn.lock': { ecosystem: 'npm', parser: 'yarn' },
|
|
28
|
+
'pnpm-lock.yaml': { ecosystem: 'npm', parser: 'yaml' },
|
|
29
|
+
'bun.lockb': { ecosystem: 'npm', parser: 'binary' },
|
|
30
|
+
// Python
|
|
31
|
+
'requirements.txt': { ecosystem: 'PyPI', parser: 'text' },
|
|
32
|
+
'Pipfile.lock': { ecosystem: 'PyPI', parser: 'json' },
|
|
33
|
+
'poetry.lock': { ecosystem: 'PyPI', parser: 'toml' },
|
|
34
|
+
'pdm.lock': { ecosystem: 'PyPI', parser: 'toml' },
|
|
35
|
+
'uv.lock': { ecosystem: 'PyPI', parser: 'toml' },
|
|
36
|
+
'pylock.toml': { ecosystem: 'PyPI', parser: 'toml' },
|
|
37
|
+
// Rust
|
|
38
|
+
'Cargo.lock': { ecosystem: 'crates.io', parser: 'toml' },
|
|
39
|
+
// Ruby
|
|
40
|
+
'Gemfile.lock': { ecosystem: 'RubyGems', parser: 'text' },
|
|
41
|
+
'gems.locked': { ecosystem: 'RubyGems', parser: 'text' },
|
|
42
|
+
// PHP
|
|
43
|
+
'composer.lock': { ecosystem: 'Packagist', parser: 'json' },
|
|
44
|
+
// Java
|
|
45
|
+
'pom.xml': { ecosystem: 'Maven', parser: 'xml' },
|
|
46
|
+
'buildscript-gradle.lockfile': { ecosystem: 'Maven', parser: 'text' },
|
|
47
|
+
'gradle.lockfile': { ecosystem: 'Maven', parser: 'text' },
|
|
48
|
+
// Go
|
|
49
|
+
'go.mod': { ecosystem: 'Go', parser: 'text' },
|
|
50
|
+
'go.sum': { ecosystem: 'Go', parser: 'text' },
|
|
51
|
+
// .NET
|
|
52
|
+
'packages.lock.json': { ecosystem: 'NuGet', parser: 'json' },
|
|
53
|
+
'deps.json': { ecosystem: 'NuGet', parser: 'json' },
|
|
54
|
+
'packages.config': { ecosystem: 'NuGet', parser: 'xml' },
|
|
55
|
+
// Dart
|
|
56
|
+
'pubspec.lock': { ecosystem: 'Pub', parser: 'yaml' },
|
|
57
|
+
// Elixir
|
|
58
|
+
'mix.lock': { ecosystem: 'Hex', parser: 'elixir' },
|
|
59
|
+
// C/C++
|
|
60
|
+
'conan.lock': { ecosystem: 'ConanCenter', parser: 'text' },
|
|
61
|
+
// R
|
|
62
|
+
'renv.lock': { ecosystem: 'Bioconductor', parser: 'json' },
|
|
17
63
|
};
|
|
18
64
|
// Check if a scanner is available
|
|
19
65
|
function isScannerAvailable(scanner) {
|
|
@@ -251,62 +297,214 @@ function extractPackagesFromLockfiles(resolvedPath) {
|
|
|
251
297
|
const packages = [];
|
|
252
298
|
try {
|
|
253
299
|
const files = readdirSync(resolvedPath);
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
300
|
+
// Iterate through all supported lockfile patterns
|
|
301
|
+
for (const [filename, config] of Object.entries(LOCKFILE_PATTERNS)) {
|
|
302
|
+
const lockfile = files.find(f => f === filename);
|
|
303
|
+
if (!lockfile)
|
|
304
|
+
continue;
|
|
305
|
+
const filepath = join(resolvedPath, lockfile);
|
|
306
|
+
const content = readFileSync(filepath, 'utf-8');
|
|
307
|
+
try {
|
|
308
|
+
switch (config.parser) {
|
|
309
|
+
case 'json':
|
|
310
|
+
parseJSONLockfile(content, config.ecosystem, packages);
|
|
311
|
+
break;
|
|
312
|
+
case 'yaml':
|
|
313
|
+
parseYAMLLockfile(content, config.ecosystem, packages);
|
|
314
|
+
break;
|
|
315
|
+
case 'toml':
|
|
316
|
+
parseTOMLLockfile(content, config.ecosystem, packages);
|
|
317
|
+
break;
|
|
318
|
+
case 'text':
|
|
319
|
+
parseTextLockfile(content, config.ecosystem, packages, filename);
|
|
320
|
+
break;
|
|
321
|
+
// Binary and XML parsers would require additional dependencies
|
|
322
|
+
// For now, skip binary files and use basic XML parsing
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch (e) {
|
|
326
|
+
console.warn(`Failed to parse ${filename}:`, e);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch (e) {
|
|
331
|
+
// Ignore top-level errors
|
|
332
|
+
}
|
|
333
|
+
return packages;
|
|
334
|
+
}
|
|
335
|
+
// Parse JSON lockfiles (package-lock.json, Pipfile.lock, composer.lock, etc.)
|
|
336
|
+
function parseJSONLockfile(content, ecosystem, packages) {
|
|
337
|
+
const data = JSON.parse(content);
|
|
338
|
+
// package-lock.json format (object with packages)
|
|
339
|
+
if (data.packages && typeof data.packages === 'object' && !Array.isArray(data.packages)) {
|
|
340
|
+
for (const [path, pkg] of Object.entries(data.packages)) {
|
|
341
|
+
const p = pkg;
|
|
342
|
+
if (p.version && path !== '') {
|
|
343
|
+
const name = p.name || path.split('node_modules/').pop()?.split('/')[0];
|
|
344
|
+
if (name) {
|
|
345
|
+
packages.push({ name, version: p.version.replace(/^\^|~/, ''), ecosystem });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Pipfile.lock format
|
|
351
|
+
if (data.default || data.develop) {
|
|
352
|
+
for (const section of ['default', 'develop']) {
|
|
353
|
+
if (data[section]) {
|
|
354
|
+
for (const [name, pkg] of Object.entries(data[section])) {
|
|
260
355
|
const p = pkg;
|
|
261
|
-
if (p.version
|
|
262
|
-
|
|
263
|
-
const name = path.split('node_modules/').pop()?.split('/')[0];
|
|
264
|
-
if (name) {
|
|
265
|
-
packages.push({ name, version: p.version.replace(/^\^|~/, ''), ecosystem: 'npm' });
|
|
266
|
-
}
|
|
356
|
+
if (p.version) {
|
|
357
|
+
packages.push({ name: name.toLowerCase(), version: p.version.replace(/^[=<>!~]+/, ''), ecosystem });
|
|
267
358
|
}
|
|
268
359
|
}
|
|
269
360
|
}
|
|
270
361
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
362
|
+
}
|
|
363
|
+
// composer.lock format (array of packages)
|
|
364
|
+
if (Array.isArray(data.packages)) {
|
|
365
|
+
for (const pkg of data.packages) {
|
|
366
|
+
if (pkg.name && pkg.version) {
|
|
367
|
+
packages.push({ name: pkg.name, version: pkg.version.replace(/^[=<>!~v]+/, ''), ecosystem });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// renv.lock format
|
|
372
|
+
if (data.Packages) {
|
|
373
|
+
for (const [name, pkg] of Object.entries(data.Packages)) {
|
|
374
|
+
const p = pkg;
|
|
375
|
+
if (p.Version) {
|
|
376
|
+
packages.push({ name, version: p.Version, ecosystem });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// Parse YAML lockfiles (yarn.lock, pubspec.lock, pnpm-lock.yaml)
|
|
382
|
+
function parseYAMLLockfile(content, ecosystem, packages) {
|
|
383
|
+
// Simple YAML parsing without external dependency
|
|
384
|
+
// For production, consider using a YAML parser library
|
|
385
|
+
const lines = content.split('\n');
|
|
386
|
+
let currentPackage = '';
|
|
387
|
+
for (const line of lines) {
|
|
388
|
+
// yarn.lock format: "package@version":
|
|
389
|
+
const yarnMatch = line.match(/^"?([^@"]+)@([^"]+)":/);
|
|
390
|
+
if (yarnMatch) {
|
|
391
|
+
packages.push({ name: yarnMatch[1], version: yarnMatch[2].replace(/^[^0-9]*/, ''), ecosystem });
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
// pubspec.lock format
|
|
395
|
+
const pubMatch = line.match(/^\s+name:\s*(.+)$/);
|
|
396
|
+
if (pubMatch) {
|
|
397
|
+
currentPackage = pubMatch[1].trim();
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
const pubVersion = line.match(/^\s+version:\s*"?(.+)"?$/);
|
|
401
|
+
if (pubVersion && currentPackage) {
|
|
402
|
+
packages.push({ name: currentPackage, version: pubVersion[1], ecosystem });
|
|
403
|
+
currentPackage = '';
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// Parse TOML lockfiles (Cargo.lock, poetry.lock, etc.)
|
|
408
|
+
function parseTOMLLockfile(content, ecosystem, packages) {
|
|
409
|
+
// Simple TOML parsing without external dependency
|
|
410
|
+
const lines = content.split('\n');
|
|
411
|
+
let currentPackage = '';
|
|
412
|
+
for (const line of lines) {
|
|
413
|
+
// Cargo.lock format: [[package]]
|
|
414
|
+
if (line.startsWith('[[')) {
|
|
415
|
+
currentPackage = '';
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const nameMatch = line.match(/^name\s*=\s*"(.+)"$/);
|
|
419
|
+
if (nameMatch) {
|
|
420
|
+
currentPackage = nameMatch[1];
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
const versionMatch = line.match(/^version\s*=\s*"(.+)"$/);
|
|
424
|
+
if (versionMatch && currentPackage) {
|
|
425
|
+
packages.push({ name: currentPackage, version: versionMatch[1], ecosystem });
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// Parse text-based lockfiles (requirements.txt, Gemfile.lock, go.mod, etc.)
|
|
430
|
+
function parseTextLockfile(content, ecosystem, packages, filename) {
|
|
431
|
+
const lines = content.split('\n');
|
|
432
|
+
// requirements.txt format
|
|
433
|
+
if (filename === 'requirements.txt') {
|
|
434
|
+
for (const line of lines) {
|
|
435
|
+
const match = line.match(/^([a-zA-Z0-9_-]+)([=<>!~]+)(.+)$/);
|
|
436
|
+
if (match) {
|
|
437
|
+
packages.push({ name: match[1], version: match[3].trim(), ecosystem });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
// Gemfile.lock format
|
|
443
|
+
if (filename === 'Gemfile.lock') {
|
|
444
|
+
let inSpecs = false;
|
|
445
|
+
for (const line of lines) {
|
|
446
|
+
if (line.includes('specs:')) {
|
|
447
|
+
inSpecs = true;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (inSpecs && line.startsWith(' ')) {
|
|
451
|
+
const match = line.match(/^\s+([a-zA-Z0-9_-]+)\s+\(([^)]+)\)/);
|
|
277
452
|
if (match) {
|
|
278
|
-
packages.push({ name: match[1], version: match[
|
|
453
|
+
packages.push({ name: match[1], version: match[2], ecosystem });
|
|
279
454
|
}
|
|
280
455
|
}
|
|
456
|
+
if (inSpecs && line.trim() && !line.startsWith(' ')) {
|
|
457
|
+
inSpecs = false;
|
|
458
|
+
}
|
|
281
459
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
// go.mod format
|
|
463
|
+
if (filename === 'go.mod') {
|
|
464
|
+
let inRequire = false;
|
|
465
|
+
for (const line of lines) {
|
|
466
|
+
if (line.startsWith('require (')) {
|
|
467
|
+
inRequire = true;
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
if (inRequire) {
|
|
471
|
+
if (line === ')') {
|
|
472
|
+
inRequire = false;
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
const match = line.match(/^\s*([a-zA-Z0-9\/]+)\s+v?(.+)$/);
|
|
476
|
+
if (match) {
|
|
477
|
+
packages.push({ name: match[1], version: match[2].replace(/^v/, ''), ecosystem });
|
|
290
478
|
}
|
|
291
479
|
}
|
|
480
|
+
// Single-line require
|
|
481
|
+
const singleMatch = line.match(/^require\s+([a-zA-Z0-9\/]+)\s+v?(.+)$/);
|
|
482
|
+
if (singleMatch) {
|
|
483
|
+
packages.push({ name: singleMatch[1], version: singleMatch[2].replace(/^v/, ''), ecosystem });
|
|
484
|
+
}
|
|
292
485
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
}
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
// go.sum format
|
|
489
|
+
if (filename === 'go.sum') {
|
|
490
|
+
for (const line of lines) {
|
|
491
|
+
const match = line.match(/^([a-zA-Z0-9\/]+)\s+v?([^\/\s]+)\//);
|
|
492
|
+
if (match) {
|
|
493
|
+
packages.push({ name: match[1], version: match[2].replace(/^v/, ''), ecosystem });
|
|
303
494
|
}
|
|
304
495
|
}
|
|
496
|
+
return;
|
|
305
497
|
}
|
|
306
|
-
|
|
307
|
-
|
|
498
|
+
// gradle.lockfile format
|
|
499
|
+
if (filename.includes('gradle.lockfile')) {
|
|
500
|
+
for (const line of lines) {
|
|
501
|
+
const match = line.match(/:([a-zA-Z0-9_-]+):([a-zA-Z0-9._-]+):([a-zA-Z0-9._-]+)/);
|
|
502
|
+
if (match) {
|
|
503
|
+
packages.push({ name: `${match[2]}:${match[3]}`, version: match[3], ecosystem });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return;
|
|
308
507
|
}
|
|
309
|
-
return packages;
|
|
310
508
|
}
|
|
311
509
|
export function scanDependencies(skillPath) {
|
|
312
510
|
const findings = [];
|