@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 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 | 7 days |
93
- | FIRST EPSS | Daily | 7 days |
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/vercel/skill-audit
8
- version: 0.2.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 in the Vercel ecosystem.
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
- // Parse package-lock.json
255
- const pkgLock = files.find(f => f === 'package-lock.json');
256
- if (pkgLock) {
257
- const content = JSON.parse(readFileSync(join(resolvedPath, pkgLock), 'utf-8'));
258
- if (content.packages) {
259
- for (const [path, pkg] of Object.entries(content.packages)) {
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 && path !== '') {
262
- // Extract package name from path
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
- // Parse requirements.txt
272
- const reqTxt = files.find(f => f === 'requirements.txt');
273
- if (reqTxt) {
274
- const content = readFileSync(join(resolvedPath, reqTxt), 'utf-8');
275
- for (const line of content.split('\n')) {
276
- const match = line.match(/^([a-zA-Z0-9_-]+)([=<>!~]+)(.+)$/);
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[3].trim(), ecosystem: 'PyPI' });
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
- // Parse go.mod
283
- const goMod = files.find(f => f === 'go.mod');
284
- if (goMod) {
285
- const content = readFileSync(join(resolvedPath, goMod), 'utf-8');
286
- for (const line of content.split('\n')) {
287
- const match = line.match(/^\s+([a-zA-Z0-9\/]+)\s+v?(.+)$/);
288
- if (match && !match[1].startsWith('gopkg.in') && !match[1].startsWith('github.com/')) {
289
- packages.push({ name: match[1], version: match[2].replace(/^v/, ''), ecosystem: 'Go' });
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
- // Parse Cargo.lock
294
- const cargoLock = files.find(f => f === 'Cargo.lock');
295
- if (cargoLock) {
296
- const content = JSON.parse(readFileSync(join(resolvedPath, cargoLock), 'utf-8'));
297
- if (content.package) {
298
- for (const pkg of content.package) {
299
- if (pkg.name && pkg.version) {
300
- packages.push({ name: pkg.name, version: pkg.version, ecosystem: 'crates.io' });
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
- catch (e) {
307
- // Ignore parse errors
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 = [];