@guava-parity/guard-scanner 13.0.0 → 15.0.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.
Files changed (79) hide show
  1. package/README.md +42 -253
  2. package/SECURITY.md +12 -4
  3. package/SKILL.md +121 -59
  4. package/dist/openclaw-plugin.mjs +41 -0
  5. package/docs/EVIDENCE_DRIVEN.md +182 -0
  6. package/docs/banner.png +0 -0
  7. package/docs/data/corpus-metrics.json +11 -0
  8. package/docs/data/latest.json +25837 -2481
  9. package/docs/generated/npm-audit-20260312.json +96 -0
  10. package/docs/generated/openclaw-upstream-status.json +25 -0
  11. package/docs/glossary.md +46 -0
  12. package/docs/index.html +1085 -496
  13. package/docs/logo.png +0 -0
  14. package/docs/openclaw-compatibility-audit.md +44 -0
  15. package/docs/openclaw-continuous-compatibility-plan.md +36 -0
  16. package/docs/rules/a2a-contagion.md +68 -0
  17. package/docs/rules/advanced-exfil.md +52 -0
  18. package/docs/rules/agent-protocol.md +108 -0
  19. package/docs/rules/api-abuse.md +68 -0
  20. package/docs/rules/autonomous-risk.md +92 -0
  21. package/docs/rules/config-impact.md +132 -0
  22. package/docs/rules/credential-handling.md +100 -0
  23. package/docs/rules/cve-patterns.md +332 -0
  24. package/docs/rules/data-exposure.md +84 -0
  25. package/docs/rules/exfiltration.md +36 -0
  26. package/docs/rules/financial-access.md +84 -0
  27. package/docs/rules/identity-hijack.md +140 -0
  28. package/docs/rules/inference-manipulation.md +60 -0
  29. package/docs/rules/leaky-skills.md +52 -0
  30. package/docs/rules/malicious-code.md +108 -0
  31. package/docs/rules/mcp-security.md +148 -0
  32. package/docs/rules/memory-poisoning.md +84 -0
  33. package/docs/rules/model-poisoning.md +44 -0
  34. package/docs/rules/obfuscation.md +60 -0
  35. package/docs/rules/persistence.md +108 -0
  36. package/docs/rules/pii-exposure.md +116 -0
  37. package/docs/rules/prompt-injection.md +148 -0
  38. package/docs/rules/prompt-worm.md +44 -0
  39. package/docs/rules/safeguard-bypass.md +44 -0
  40. package/docs/rules/sandbox-escape.md +100 -0
  41. package/docs/rules/secret-detection.md +44 -0
  42. package/docs/rules/supply-chain-v2.md +92 -0
  43. package/docs/rules/suspicious-download.md +60 -0
  44. package/docs/rules/trust-boundary.md +76 -0
  45. package/docs/rules/trust-exploitation.md +92 -0
  46. package/docs/rules/unverifiable-deps.md +84 -0
  47. package/docs/rules/vdb-injection.md +84 -0
  48. package/docs/security-vulnerability-report-20260312.md +53 -0
  49. package/docs/spec/PRD_V2_ARCHITECTURE.md +55 -0
  50. package/docs/spec/capabilities.json +42 -0
  51. package/docs/spec/finding.schema.json +104 -0
  52. package/docs/spec/integration-manifest.md +39 -0
  53. package/docs/spec/sbom.json +33 -0
  54. package/docs/threat-model.md +65 -0
  55. package/docs/v13-architecture-manifest.md +55 -0
  56. package/hooks/context.js +305 -0
  57. package/hooks/guard-scanner/plugin.ts +24 -1
  58. package/openclaw-plugin.mts +91 -0
  59. package/openclaw.plugin.json +30 -53
  60. package/package.json +23 -8
  61. package/src/cli.js +174 -34
  62. package/src/core/content-loader.js +42 -0
  63. package/src/core/inventory.js +73 -0
  64. package/src/core/report-adapters.js +171 -0
  65. package/src/core/risk-engine.js +93 -0
  66. package/src/core/rule-registry.js +73 -0
  67. package/src/core/semantic-validators.js +85 -0
  68. package/src/finding-schema.js +191 -0
  69. package/src/hooks/context.ts +49 -0
  70. package/src/html-template.js +2 -2
  71. package/src/mcp-server.js +24 -73
  72. package/src/openclaw-upstream.js +128 -0
  73. package/src/patterns.js +371 -353
  74. package/src/policy-engine.js +32 -0
  75. package/src/runtime-guard.js +40 -2
  76. package/src/scanner.js +101 -216
  77. package/src/skill-crawler.js +254 -0
  78. package/src/threat-model.js +50 -0
  79. package/src/validation-layer.js +39 -0
@@ -0,0 +1,91 @@
1
+ import { createRequire } from "node:module";
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
3
+
4
+ const require = createRequire(import.meta.url);
5
+ const runtimeGuard = require("../src/runtime-guard.js") as {
6
+ scanToolCall: (
7
+ toolName: string,
8
+ params: Record<string, unknown>,
9
+ options?: {
10
+ mode?: "monitor" | "enforce" | "strict";
11
+ auditLog?: boolean;
12
+ sessionKey?: string;
13
+ sessionId?: string;
14
+ runId?: string;
15
+ toolCallId?: string;
16
+ agentId?: string;
17
+ },
18
+ ) => {
19
+ blocked: boolean;
20
+ blockReason: string | null;
21
+ };
22
+ };
23
+
24
+ type GuardMode = "monitor" | "enforce" | "strict";
25
+ type PluginHookBeforeToolCallEvent = {
26
+ toolName: string;
27
+ params: Record<string, unknown>;
28
+ runId?: string;
29
+ toolCallId?: string;
30
+ };
31
+ type PluginHookToolContext = {
32
+ agentId?: string;
33
+ sessionKey?: string;
34
+ sessionId?: string;
35
+ runId?: string;
36
+ toolName: string;
37
+ toolCallId?: string;
38
+ };
39
+
40
+ function resolveMode(pluginConfig?: Record<string, unknown>): GuardMode | undefined {
41
+ const mode = pluginConfig?.mode;
42
+ if (mode === "monitor" || mode === "enforce" || mode === "strict") {
43
+ return mode;
44
+ }
45
+ return undefined;
46
+ }
47
+
48
+ function resolveAuditLog(pluginConfig?: Record<string, unknown>): boolean {
49
+ return pluginConfig?.auditLog !== false;
50
+ }
51
+
52
+ function beforeToolCall(
53
+ event: PluginHookBeforeToolCallEvent,
54
+ ctx: PluginHookToolContext,
55
+ api: OpenClawPluginApi,
56
+ ) {
57
+ const result = runtimeGuard.scanToolCall(event.toolName, event.params, {
58
+ mode: resolveMode(api.pluginConfig),
59
+ auditLog: resolveAuditLog(api.pluginConfig),
60
+ sessionKey: ctx.sessionKey,
61
+ sessionId: ctx.sessionId,
62
+ runId: ctx.runId ?? event.runId,
63
+ toolCallId: ctx.toolCallId ?? event.toolCallId,
64
+ agentId: ctx.agentId,
65
+ });
66
+
67
+ if (!result.blocked) return;
68
+ return {
69
+ block: true,
70
+ blockReason: result.blockReason ?? "guard-scanner blocked the tool call.",
71
+ };
72
+ }
73
+
74
+ const plugin = {
75
+ id: "guard-scanner",
76
+ name: "guard-scanner",
77
+ version: "15.0.0",
78
+ description: "Runtime guard for OpenClaw before_tool_call hook execution.",
79
+ register(api: OpenClawPluginApi) {
80
+ api.on(
81
+ "before_tool_call",
82
+ (event: PluginHookBeforeToolCallEvent, ctx: PluginHookToolContext) => beforeToolCall(event, ctx, api),
83
+ { priority: 90 },
84
+ );
85
+ api.logger.info(
86
+ "guard-scanner registered OpenClaw before_tool_call hook (validated for v2026.3.8).",
87
+ );
88
+ },
89
+ };
90
+
91
+ export default plugin;
@@ -1,55 +1,32 @@
1
1
  {
2
- "name": "guard-scanner",
3
- "version": "5.0.5",
4
- "displayName": "🛡️ Guard Scanner Runtime Security for AI Agents",
5
- "description": "147 static patterns (23 categories) + 26 runtime checks (5 layers). 0.016ms/scan, zero dependencies, SARIF output.",
6
- "author": "Guava & Dee",
7
- "license": "MIT",
8
- "homepage": "https://github.com/koatora20/guard-scanner",
9
- "repository": "https://github.com/koatora20/guard-scanner",
10
- "keywords": [
11
- "security",
12
- "runtime-guard",
13
- "threat-detection",
14
- "before-tool-call"
15
- ],
16
- "hooks": {
17
- "before_tool_call": {
18
- "handler": "./hooks/guard-scanner/plugin.ts",
19
- "description": "Scans tool call arguments against 26 runtime threat patterns (5 layers) and blocks dangerous operations",
20
- "priority": 100
21
- }
2
+ "id": "guard-scanner",
3
+ "name": "guard-scanner",
4
+ "description": "Runtime guard plugin for OpenClaw before_tool_call enforcement.",
5
+ "version": "15.0.0",
6
+ "configSchema": {
7
+ "type": "object",
8
+ "properties": {
9
+ "mode": {
10
+ "type": "string",
11
+ "enum": [
12
+ "monitor",
13
+ "enforce",
14
+ "strict"
15
+ ],
16
+ "default": "enforce",
17
+ "description": "monitor: log only | enforce: block CRITICAL | strict: block HIGH+CRITICAL"
18
+ },
19
+ "auditLog": {
20
+ "type": "boolean",
21
+ "default": true,
22
+ "description": "Enable audit logging to ~/.openclaw/guard-scanner/audit.jsonl"
23
+ },
24
+ "customRules": {
25
+ "type": "string",
26
+ "description": "Path to custom rules JSON file (optional)"
27
+ }
22
28
  },
23
- "configSchema": {
24
- "type": "object",
25
- "properties": {
26
- "mode": {
27
- "type": "string",
28
- "enum": [
29
- "monitor",
30
- "enforce",
31
- "strict"
32
- ],
33
- "default": "enforce",
34
- "description": "monitor: log only | enforce: block CRITICAL | strict: block HIGH+CRITICAL"
35
- },
36
- "auditLog": {
37
- "type": "boolean",
38
- "default": true,
39
- "description": "Enable audit logging to ~/.openclaw/guard-scanner/audit.jsonl"
40
- },
41
- "customRules": {
42
- "type": "string",
43
- "description": "Path to custom rules JSON file (optional)"
44
- }
45
- },
46
- "required": [],
47
- "additionalProperties": false
48
- },
49
- "capabilities": {
50
- "cli": true,
51
- "runtimeGuard": true,
52
- "sarif": true,
53
- "cicd": true
54
- }
55
- }
29
+ "required": [],
30
+ "additionalProperties": false
31
+ }
32
+ }
package/package.json CHANGED
@@ -1,24 +1,36 @@
1
1
  {
2
2
  "name": "@guava-parity/guard-scanner",
3
- "version": "13.0.0",
3
+ "version": "15.0.0",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
7
7
  },
8
- "description": "Agent Skill Security Scanner - 2026 Moltbook Payload Immune System",
9
- "openclaw.extensions": "./openclaw.plugin.json",
10
- "openclaw.hooks": {
11
- "guard-scanner": "./hooks/guard-scanner"
8
+ "description": "Agent Skill Security Scanner - ASI Sanctuary Enforcer (v15)",
9
+ "openclaw": {
10
+ "extensions": [
11
+ "./dist/openclaw-plugin.mjs"
12
+ ]
12
13
  },
13
14
  "main": "src/scanner.js",
14
15
  "bin": {
15
16
  "guard-scanner": "src/cli.js"
16
17
  },
17
18
  "scripts": {
19
+ "build:plugin": "npx tsc -p tsconfig.build.json",
20
+ "check:upstream": "node scripts/check-openclaw-upstream.js",
18
21
  "scan": "node src/cli.js",
19
- "test": "node scripts/test-quality-gate.js && node --test test/*.test.js",
22
+ "lint": "node scripts/lint.js",
23
+ "typecheck": "npx tsc --noEmit -p tsconfig.json",
24
+ "release:gate": "node scripts/release-gate.js",
25
+ "test": "npm run lint && npm run typecheck && npm run build:plugin && node scripts/generate-capabilities.js && node scripts/generate-readme-metrics.js && node scripts/generate-readme-stats.js && node scripts/verify-capabilities.js && node scripts/test-quality-gate.js && node scripts/corpus-metrics.js --check && node scripts/perf-regression.js && node scripts/release-gate.js && node --test test/*.test.js",
20
26
  "test:core": "node --test test/scanner.test.js test/patterns.test.js",
21
- "test:quality": "node scripts/test-quality-gate.js"
27
+ "test:contracts": "npm run build:plugin && node scripts/release-gate.js && node --test test/finding-schema.test.js test/mcp.test.js test/e2e-mcp.test.js test/openclaw-plugin-compat.test.js test/stale-claims.test.js test/openclaw-upstream-check.test.js",
28
+ "test:corpus": "node scripts/corpus-metrics.js --check",
29
+ "test:perf": "node scripts/perf-regression.js",
30
+ "test:quality": "node scripts/test-quality-gate.js",
31
+ "sbom": "node scripts/generate-sbom.js",
32
+ "sync:readme": "node scripts/generate-capabilities.js && node scripts/generate-readme-metrics.js && node scripts/generate-readme-stats.js",
33
+ "prepack": "npm run build:plugin && npm run release:gate"
22
34
  },
23
35
  "keywords": [
24
36
  "security",
@@ -47,9 +59,11 @@
47
59
  },
48
60
  "homepage": "https://github.com/koatora20/guard-scanner",
49
61
  "files": [
62
+ "dist/",
50
63
  "src/",
51
64
  "hooks/",
52
65
  "docs/",
66
+ "openclaw-plugin.mts",
53
67
  "openclaw.plugin.json",
54
68
  "SKILL.md",
55
69
  "SECURITY.md",
@@ -58,9 +72,10 @@
58
72
  ],
59
73
  "devDependencies": {
60
74
  "@types/node": "^22.0.0",
75
+ "openclaw": "2026.3.8",
61
76
  "typescript": "^5.7.0"
62
77
  },
63
78
  "dependencies": {
64
79
  "ws": "^8.19.0"
65
80
  }
66
- }
81
+ }
package/src/cli.js CHANGED
@@ -150,6 +150,141 @@ Examples:
150
150
  process.exit(2);
151
151
  }
152
152
  })();
153
+
154
+ // ── crawl subcommand ──────────────────────────────────────────
155
+ } else if (args[0] === 'crawl') {
156
+ const { SkillCrawler } = require('./skill-crawler.js');
157
+ const subCmd = args[1]; // clawhub | github | url
158
+ const target = args[2]; // query or URL
159
+ const verbose = args.includes('--verbose') || args.includes('-v');
160
+ const formatIdx = args.indexOf('--format');
161
+ const formatValue = formatIdx >= 0 ? args[formatIdx + 1] : null;
162
+ const quiet = !!formatValue;
163
+ const maxIdx = args.indexOf('--max');
164
+ const maxSkills = maxIdx >= 0 ? parseInt(args[maxIdx + 1], 10) : 50;
165
+
166
+ if (!subCmd || subCmd === '--help' || subCmd === '-h') {
167
+ console.log(`
168
+ 🛡️ guard-scanner crawl — Autonomous Skill Scanner
169
+
170
+ Usage:
171
+ guard-scanner crawl clawhub Crawl ClawHub for all SKILL.md
172
+ guard-scanner crawl github <query> Search GitHub for SKILL.md
173
+ guard-scanner crawl url <url> Scan a single SKILL.md URL
174
+
175
+ Options:
176
+ --verbose, -v Show detection details
177
+ --format json Output JSON to stdout
178
+ --max <n> Max skills to scan (default: 50)
179
+ --help, -h Show this help
180
+
181
+ Examples:
182
+ guard-scanner crawl clawhub --verbose
183
+ guard-scanner crawl github "polymarket trader" --format json
184
+ guard-scanner crawl url https://raw.githubusercontent.com/.../SKILL.md
185
+ `);
186
+ process.exit(0);
187
+ }
188
+
189
+ const crawler = new SkillCrawler({ verbose, quiet, concurrency: 5 });
190
+
191
+ (async () => {
192
+ try {
193
+ if (subCmd === 'clawhub') {
194
+ await crawler.crawlClawHub({ maxSkills });
195
+ } else if (subCmd === 'github') {
196
+ if (!target) { console.error('❌ Usage: guard-scanner crawl github <query>'); process.exit(2); }
197
+ await crawler.crawlGitHub(target, { maxResults: maxSkills });
198
+ } else if (subCmd === 'url') {
199
+ if (!target) { console.error('❌ Usage: guard-scanner crawl url <url>'); process.exit(2); }
200
+ await crawler.scanUrl(target, target);
201
+ } else {
202
+ console.error(`❌ Unknown crawl target: ${subCmd}. Use: clawhub, github, or url`);
203
+ process.exit(2);
204
+ }
205
+
206
+ if (formatValue === 'json') {
207
+ process.stdout.write(JSON.stringify(crawler.toJSON(), null, 2) + '\n');
208
+ } else {
209
+ crawler.printSummary();
210
+ }
211
+
212
+ const summary = crawler.getSummary();
213
+ process.exit(summary.unsafe > 0 ? 1 : 0);
214
+ } catch (e) {
215
+ console.error(`❌ Crawl error: ${e.message}`);
216
+ process.exit(2);
217
+ }
218
+ })();
219
+
220
+ // ── patrol subcommand ──────────────────────────────────────────
221
+ } else if (args[0] === 'patrol') {
222
+ const { SkillCrawler } = require('./skill-crawler.js');
223
+ const verbose = args.includes('--verbose') || args.includes('-v');
224
+ const once = args.includes('--once');
225
+ const intervalIdx = args.indexOf('--interval');
226
+ const intervalSecs = intervalIdx >= 0 ? parseInt(args[intervalIdx + 1], 10) : 3600;
227
+ const formatIdx = args.indexOf('--format');
228
+ const formatValue = formatIdx >= 0 ? args[formatIdx + 1] : null;
229
+
230
+ if (args.includes('--help') || args.includes('-h')) {
231
+ console.log(`
232
+ 🛡️ guard-scanner patrol — Autonomous Security Patrol
233
+
234
+ Usage:
235
+ guard-scanner patrol --once Run one patrol cycle
236
+ guard-scanner patrol --interval 3600 Patrol every N seconds
237
+
238
+ Options:
239
+ --once Run once and exit
240
+ --interval <secs> Polling interval (default: 3600 = 1 hour)
241
+ --verbose, -v Show detection details
242
+ --format json Output JSON
243
+ --help, -h Show this help
244
+ `);
245
+ process.exit(0);
246
+ }
247
+
248
+ async function runPatrol() {
249
+ const timestamp = new Date().toISOString();
250
+ console.log(`\n🚨 Patrol cycle starting at ${timestamp}`);
251
+ console.log('─'.repeat(54));
252
+
253
+ const crawler = new SkillCrawler({ verbose, quiet: !!formatValue, concurrency: 5 });
254
+ await crawler.crawlClawHub({ maxSkills: 50 });
255
+
256
+ if (formatValue === 'json') {
257
+ process.stdout.write(JSON.stringify(crawler.toJSON(), null, 2) + '\n');
258
+ } else {
259
+ crawler.printSummary();
260
+ }
261
+
262
+ const summary = crawler.getSummary();
263
+ if (summary.unsafe > 0) {
264
+ console.log(`\n⚠️ ${summary.unsafe} unsafe skill(s) detected!`);
265
+ }
266
+ return summary;
267
+ }
268
+
269
+ (async () => {
270
+ try {
271
+ if (once) {
272
+ const summary = await runPatrol();
273
+ process.exit(summary.unsafe > 0 ? 1 : 0);
274
+ } else {
275
+ console.log(`🛡️ guard-scanner patrol mode — interval: ${intervalSecs}s`);
276
+ console.log(` Press Ctrl+C to stop\n`);
277
+ await runPatrol();
278
+ setInterval(async () => {
279
+ await runPatrol();
280
+ }, intervalSecs * 1000);
281
+ }
282
+ } catch (e) {
283
+ console.error(`❌ Patrol error: ${e.message}`);
284
+ process.exit(2);
285
+ }
286
+ })();
287
+
153
288
  } else {
154
289
  // ── existing scan logic (backward compatible) ─────────────────
155
290
 
@@ -249,46 +384,51 @@ Examples:
249
384
  !plugins.includes(a)
250
385
  ) || process.cwd();
251
386
 
252
- const scanner = new GuardScanner({
253
- verbose, selfExclude, strict, summaryOnly, checkDeps, soulLock, rulesFile, plugins,
254
- quiet: quietMode || !!formatValue,
255
- });
387
+ try {
388
+ const scanner = new GuardScanner({
389
+ verbose, selfExclude, strict, summaryOnly, checkDeps, soulLock, rulesFile, plugins,
390
+ quiet: quietMode || !!formatValue,
391
+ });
256
392
 
257
- scanner.scanDirectory(scanDir);
393
+ scanner.scanDirectory(scanDir);
258
394
 
259
- // Output reports (file-based, backward compatible)
260
- if (jsonOutput) {
261
- const report = scanner.toJSON();
262
- const outPath = path.join(scanDir, 'guard-scanner-report.json');
263
- fs.writeFileSync(outPath, JSON.stringify(report, null, 2));
264
- if (!quietMode && !formatValue) console.log(`\n📄 JSON report: ${outPath}`);
265
- }
395
+ // Output reports (file-based, backward compatible)
396
+ if (jsonOutput) {
397
+ const report = scanner.toJSON();
398
+ const outPath = path.join(scanDir, 'guard-scanner-report.json');
399
+ fs.writeFileSync(outPath, JSON.stringify(report, null, 2));
400
+ if (!quietMode && !formatValue) console.log(`\n📄 JSON report: ${outPath}`);
401
+ }
266
402
 
267
- if (sarifOutput) {
268
- const outPath = path.join(scanDir, 'guard-scanner.sarif');
269
- fs.writeFileSync(outPath, JSON.stringify(scanner.toSARIF(scanDir), null, 2));
270
- if (!quietMode && !formatValue) console.log(`\n📄 SARIF report: ${outPath}`);
271
- }
403
+ if (sarifOutput) {
404
+ const outPath = path.join(scanDir, 'guard-scanner.sarif');
405
+ fs.writeFileSync(outPath, JSON.stringify(scanner.toSARIF(scanDir), null, 2));
406
+ if (!quietMode && !formatValue) console.log(`\n📄 SARIF report: ${outPath}`);
407
+ }
272
408
 
273
- if (htmlOutput) {
274
- const outPath = path.join(scanDir, 'guard-scanner-report.html');
275
- fs.writeFileSync(outPath, scanner.toHTML());
276
- if (!quietMode && !formatValue) console.log(`\n📄 HTML report: ${outPath}`);
277
- }
409
+ if (htmlOutput) {
410
+ const outPath = path.join(scanDir, 'guard-scanner-report.html');
411
+ fs.writeFileSync(outPath, scanner.toHTML());
412
+ if (!quietMode && !formatValue) console.log(`\n📄 HTML report: ${outPath}`);
413
+ }
278
414
 
279
- // --format stdout output (v3.2.0)
280
- if (formatValue === 'json') {
281
- process.stdout.write(JSON.stringify(scanner.toJSON(), null, 2) + '\n');
282
- } else if (formatValue === 'sarif') {
283
- process.stdout.write(JSON.stringify(scanner.toSARIF(scanDir), null, 2) + '\n');
284
- } else if (formatValue) {
285
- console.error(`❌ Unknown format: ${formatValue}. Use 'json' or 'sarif'.`);
415
+ // --format stdout output (v3.2.0)
416
+ if (formatValue === 'json') {
417
+ process.stdout.write(JSON.stringify(scanner.toJSON(), null, 2) + '\n');
418
+ } else if (formatValue === 'sarif') {
419
+ process.stdout.write(JSON.stringify(scanner.toSARIF(scanDir), null, 2) + '\n');
420
+ } else if (formatValue) {
421
+ console.error(`❌ Unknown format: ${formatValue}. Use 'json' or 'sarif'.`);
422
+ process.exit(2);
423
+ }
424
+
425
+ // Exit codes
426
+ if (scanner.stats.malicious > 0) process.exit(1);
427
+ if (failOnFindings && scanner.findings.length > 0) process.exit(1);
428
+ process.exit(0);
429
+ } catch (error) {
430
+ console.error(`❌ ${error.message}`);
286
431
  process.exit(2);
287
432
  }
288
433
 
289
- // Exit codes
290
- if (scanner.stats.malicious > 0) process.exit(1);
291
- if (failOnFindings && scanner.findings.length > 0) process.exit(1);
292
- process.exit(0);
293
-
294
434
  } // end else (scan mode)
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ function loadIgnoreFile(scanDir) {
7
+ const ignoredSkills = new Set();
8
+ const ignoredPatterns = new Set();
9
+ const ignorePaths = [
10
+ path.join(scanDir, '.guard-scanner-ignore'),
11
+ path.join(scanDir, '.guava-guard-ignore'),
12
+ ];
13
+
14
+ for (const ignorePath of ignorePaths) {
15
+ if (!fs.existsSync(ignorePath)) continue;
16
+ const lines = fs.readFileSync(ignorePath, 'utf-8').split('\n');
17
+ for (const line of lines) {
18
+ const trimmed = line.trim();
19
+ if (!trimmed || trimmed.startsWith('#')) continue;
20
+ if (trimmed.startsWith('pattern:')) ignoredPatterns.add(trimmed.replace('pattern:', '').trim());
21
+ else ignoredSkills.add(trimmed);
22
+ }
23
+ break;
24
+ }
25
+
26
+ return { ignoredSkills, ignoredPatterns };
27
+ }
28
+
29
+ function loadTextFile(filePath, maxLength = 500000) {
30
+ try {
31
+ const content = fs.readFileSync(filePath, 'utf-8');
32
+ if (content.length > maxLength) return null;
33
+ return content;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ module.exports = {
40
+ loadIgnoreFile,
41
+ loadTextFile,
42
+ };
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const CODE_EXTENSIONS = new Set(['.js', '.ts', '.mjs', '.cjs', '.py', '.sh', '.bash', '.ps1', '.rb', '.go', '.rs', '.php', '.pl']);
7
+ const DOC_EXTENSIONS = new Set(['.md', '.txt', '.rst', '.adoc']);
8
+ const DATA_EXTENSIONS = new Set(['.json', '.yaml', '.yml', '.toml', '.xml', '.csv']);
9
+ const BINARY_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.ico', '.woff', '.woff2', '.ttf', '.eot', '.wasm', '.wav', '.mp3', '.mp4', '.webm', '.ogg', '.pdf', '.zip', '.tar', '.gz', '.bz2', '.7z', '.exe', '.dll', '.so', '.dylib']);
10
+ const GENERATED_REPORT_FILES = new Set(['guard-scanner-report.json', 'guard-scanner-report.html', 'guard-scanner.sarif']);
11
+
12
+ function classifyFile(ext, relFile) {
13
+ if (CODE_EXTENSIONS.has(ext)) return 'code';
14
+ if (DOC_EXTENSIONS.has(ext)) return 'doc';
15
+ if (DATA_EXTENSIONS.has(ext)) return 'data';
16
+ const base = path.basename(relFile).toLowerCase();
17
+ if (base === 'skill.md' || base === 'readme.md') return 'skill-doc';
18
+ return 'other';
19
+ }
20
+
21
+ function isSelfNoisePath(skillName, relFile) {
22
+ if (skillName !== 'guard-scanner') return false;
23
+ return /^test\//.test(relFile)
24
+ || /^dist\/__tests__\//.test(relFile)
25
+ || /^ts-src\/__tests__\//.test(relFile)
26
+ || /^docs\//.test(relFile)
27
+ || relFile === 'ROADMAP-RESEARCH.md'
28
+ || relFile === 'CHANGELOG.md';
29
+ }
30
+
31
+ function isSelfThreatCorpus(skillName, relFile) {
32
+ if (skillName !== 'guard-scanner') return false;
33
+ return /(^|\/)(ioc-db|patterns)\.(js|ts)$/.test(relFile);
34
+ }
35
+
36
+ function getFiles(dir) {
37
+ const results = [];
38
+ try {
39
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
40
+ for (const entry of entries) {
41
+ const fullPath = path.join(dir, entry.name);
42
+ if (entry.isDirectory()) {
43
+ if (entry.name === '.git' || entry.name === 'node_modules') continue;
44
+ results.push(...getFiles(fullPath));
45
+ } else {
46
+ const baseName = entry.name.toLowerCase();
47
+ if (GENERATED_REPORT_FILES.has(baseName)) continue;
48
+ results.push(fullPath);
49
+ }
50
+ }
51
+ } catch { }
52
+ return results;
53
+ }
54
+
55
+ function listSkills(dir) {
56
+ return fs.readdirSync(dir).filter((file) => {
57
+ const fullPath = path.join(dir, file);
58
+ return fs.statSync(fullPath).isDirectory();
59
+ });
60
+ }
61
+
62
+ module.exports = {
63
+ CODE_EXTENSIONS,
64
+ DOC_EXTENSIONS,
65
+ DATA_EXTENSIONS,
66
+ BINARY_EXTENSIONS,
67
+ GENERATED_REPORT_FILES,
68
+ classifyFile,
69
+ isSelfNoisePath,
70
+ isSelfThreatCorpus,
71
+ getFiles,
72
+ listSkills,
73
+ };