guard-scanner 2.1.0 → 3.1.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 (55) hide show
  1. package/README.md +39 -35
  2. package/dist/__tests__/scanner.test.d.ts +10 -0
  3. package/dist/__tests__/scanner.test.d.ts.map +1 -0
  4. package/dist/__tests__/scanner.test.js +374 -0
  5. package/dist/__tests__/scanner.test.js.map +1 -0
  6. package/dist/cli.d.ts +10 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +189 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/index.d.ts +10 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +18 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/ioc-db.d.ts +13 -0
  15. package/dist/ioc-db.d.ts.map +1 -0
  16. package/dist/ioc-db.js +130 -0
  17. package/dist/ioc-db.js.map +1 -0
  18. package/dist/patterns.d.ts +27 -0
  19. package/dist/patterns.d.ts.map +1 -0
  20. package/dist/patterns.js +92 -0
  21. package/dist/patterns.js.map +1 -0
  22. package/dist/quarantine.d.ts +18 -0
  23. package/dist/quarantine.d.ts.map +1 -0
  24. package/dist/quarantine.js +42 -0
  25. package/dist/quarantine.js.map +1 -0
  26. package/dist/scanner.d.ts +54 -0
  27. package/dist/scanner.d.ts.map +1 -0
  28. package/dist/scanner.js +1043 -0
  29. package/dist/scanner.js.map +1 -0
  30. package/dist/types.d.ts +165 -0
  31. package/dist/types.d.ts.map +1 -0
  32. package/dist/types.js +7 -0
  33. package/dist/types.js.map +1 -0
  34. package/hooks/guard-scanner/plugin.ts +59 -32
  35. package/openclaw.plugin.json +60 -0
  36. package/package.json +25 -9
  37. package/ts-src/__tests__/fixtures/clean-skill/SKILL.md +9 -0
  38. package/ts-src/__tests__/fixtures/compaction-skill/SKILL.md +11 -0
  39. package/ts-src/__tests__/fixtures/malicious-skill/SKILL.md +11 -0
  40. package/ts-src/__tests__/fixtures/malicious-skill/scripts/evil.js +25 -0
  41. package/ts-src/__tests__/fixtures/prompt-leakage-skill/SKILL.md +20 -0
  42. package/ts-src/__tests__/fixtures/prompt-leakage-skill/scripts/debug.js +4 -0
  43. package/ts-src/__tests__/scanner.test.ts +525 -0
  44. package/ts-src/cli.ts +171 -0
  45. package/ts-src/index.ts +15 -0
  46. package/ts-src/ioc-db.ts +131 -0
  47. package/ts-src/patterns.ts +104 -0
  48. package/ts-src/quarantine.ts +48 -0
  49. package/{src/scanner.js → ts-src/scanner.ts} +372 -385
  50. package/ts-src/types.ts +187 -0
  51. package/hooks/guard-scanner/handler.ts +0 -207
  52. package/src/cli.js +0 -149
  53. package/src/html-template.js +0 -239
  54. package/src/ioc-db.js +0 -54
  55. package/src/patterns.js +0 -212
@@ -0,0 +1,187 @@
1
+ /**
2
+ * guard-scanner v3.0.0 — Type Definitions
3
+ * TypeScript rewrite with full type safety
4
+ */
5
+
6
+ // ── Severity & Verdict ──────────────────────────────────────────────────────
7
+
8
+ export type Severity = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
9
+
10
+ export type VerdictLabel = 'MALICIOUS' | 'SUSPICIOUS' | 'LOW RISK' | 'CLEAN';
11
+ export type VerdictStat = 'malicious' | 'suspicious' | 'low' | 'clean';
12
+
13
+ export interface Verdict {
14
+ icon: string;
15
+ label: VerdictLabel;
16
+ stat: VerdictStat;
17
+ }
18
+
19
+ // ── File Types ──────────────────────────────────────────────────────────────
20
+
21
+ export type FileType = 'code' | 'doc' | 'data' | 'skill-doc' | 'other';
22
+
23
+ // ── Findings ────────────────────────────────────────────────────────────────
24
+
25
+ export interface Finding {
26
+ severity: Severity;
27
+ id: string;
28
+ cat: string;
29
+ desc: string;
30
+ file: string;
31
+ line?: number;
32
+ matchCount?: number;
33
+ sample?: string;
34
+ }
35
+
36
+ export interface SkillResult {
37
+ skill: string;
38
+ risk: number;
39
+ verdict: VerdictLabel;
40
+ findings: Finding[];
41
+ }
42
+
43
+ // ── Patterns ────────────────────────────────────────────────────────────────
44
+
45
+ export interface PatternRule {
46
+ id: string;
47
+ cat: string;
48
+ regex: RegExp;
49
+ severity: Severity;
50
+ desc: string;
51
+ codeOnly?: boolean;
52
+ docOnly?: boolean;
53
+ all?: boolean;
54
+ /** OWASP LLM Top 10 2025 mapping (e.g. 'LLM01', 'LLM06') */
55
+ owasp?: string;
56
+ }
57
+
58
+ export interface CustomRuleInput {
59
+ id: string;
60
+ pattern: string;
61
+ flags?: string;
62
+ severity: Severity;
63
+ cat: string;
64
+ desc: string;
65
+ codeOnly?: boolean;
66
+ docOnly?: boolean;
67
+ }
68
+
69
+ // ── IoC Database ────────────────────────────────────────────────────────────
70
+
71
+ export interface IoC_Database {
72
+ ips: string[];
73
+ domains: string[];
74
+ urls: string[];
75
+ usernames: string[];
76
+ filenames: string[];
77
+ typosquats: string[];
78
+ }
79
+
80
+ // ── Signature Database (hbg-scan compatible) ────────────────────────────────
81
+
82
+ export interface ThreatSignature {
83
+ id: string;
84
+ name: string;
85
+ severity: Severity;
86
+ description: string;
87
+ hash?: string; // SHA-256 content hash match
88
+ patterns?: string[]; // String patterns to match
89
+ domains?: string[]; // Suspicious domains
90
+ }
91
+
92
+ export interface SignatureDatabase {
93
+ version: string;
94
+ updated: string;
95
+ signatures: ThreatSignature[];
96
+ }
97
+
98
+ // ── Scanner Options ─────────────────────────────────────────────────────────
99
+
100
+ export interface ScannerOptions {
101
+ verbose?: boolean;
102
+ selfExclude?: boolean;
103
+ strict?: boolean;
104
+ summaryOnly?: boolean;
105
+ checkDeps?: boolean;
106
+ rulesFile?: string;
107
+ plugins?: string[];
108
+ }
109
+
110
+ // ── Scanner Stats ───────────────────────────────────────────────────────────
111
+
112
+ export interface ScanStats {
113
+ scanned: number;
114
+ clean: number;
115
+ low: number;
116
+ suspicious: number;
117
+ malicious: number;
118
+ }
119
+
120
+ // ── Thresholds ──────────────────────────────────────────────────────────────
121
+
122
+ export interface Thresholds {
123
+ suspicious: number;
124
+ malicious: number;
125
+ }
126
+
127
+ // ── Reports ─────────────────────────────────────────────────────────────────
128
+
129
+ export interface JSONReport {
130
+ timestamp: string;
131
+ scanner: string;
132
+ mode: 'strict' | 'normal';
133
+ stats: ScanStats;
134
+ thresholds: Thresholds;
135
+ findings: SkillResult[];
136
+ recommendations: Recommendation[];
137
+ iocVersion: string;
138
+ signaturesVersion?: string;
139
+ }
140
+
141
+ export interface Recommendation {
142
+ skill: string;
143
+ actions: string[];
144
+ }
145
+
146
+ // ── SARIF ───────────────────────────────────────────────────────────────────
147
+
148
+ export interface SARIFReport {
149
+ version: string;
150
+ $schema: string;
151
+ runs: SARIFRun[];
152
+ }
153
+
154
+ export interface SARIFRun {
155
+ tool: {
156
+ driver: {
157
+ name: string;
158
+ version: string;
159
+ informationUri: string;
160
+ rules: SARIFRule[];
161
+ };
162
+ };
163
+ results: SARIFResult[];
164
+ invocations: Array<{ executionSuccessful: boolean; endTimeUtc: string }>;
165
+ }
166
+
167
+ export interface SARIFRule {
168
+ id: string;
169
+ name: string;
170
+ shortDescription: { text: string };
171
+ defaultConfiguration: { level: string };
172
+ properties: { tags: string[]; 'security-severity': string };
173
+ }
174
+
175
+ export interface SARIFResult {
176
+ ruleId: string;
177
+ ruleIndex: number;
178
+ level: string;
179
+ message: { text: string };
180
+ partialFingerprints: { primaryLocationLineHash: string };
181
+ locations: Array<{
182
+ physicalLocation: {
183
+ artifactLocation: { uri: string; uriBaseId: string };
184
+ region?: { startLine: number };
185
+ };
186
+ }>;
187
+ }
@@ -1,207 +0,0 @@
1
- /**
2
- * guard-scanner Runtime Guard — Hook Handler (LEGACY)
3
- *
4
- * ⚠️ DEPRECATED: Use plugin.ts instead.
5
- * This Internal Hook version can only WARN, not block.
6
- * The Plugin Hook version (plugin.ts) uses the native
7
- * `block` / `blockReason` API to actually prevent execution.
8
- *
9
- * Intercepts agent tool calls and checks arguments against
10
- * runtime threat intelligence patterns. Zero dependencies.
11
- *
12
- * Registered for event: agent:before_tool_call
13
- *
14
- * Current limitation:
15
- * The OpenClaw InternalHookEvent interface does not yet expose a
16
- * `cancel` / `veto` mechanism. This handler can WARN via
17
- * event.messages but cannot block tool execution.
18
- * When a cancel API is introduced, this handler will be updated
19
- * to actually block CRITICAL/HIGH threats.
20
- *
21
- * Modes (for future blocking behaviour):
22
- * monitor — log only (current effective behaviour for all modes)
23
- * enforce — will block CRITICAL when cancel API is available
24
- * strict — will block HIGH+CRITICAL when cancel API is available
25
- *
26
- * @author Guava 🍈 & Dee
27
- * @version 1.1.0
28
- * @license MIT
29
- */
30
-
31
- import { appendFileSync, mkdirSync } from "fs";
32
- import { join } from "path";
33
- import { homedir } from "os";
34
-
35
- // ── OpenClaw Hook Types (from openclaw/src/hooks/internal-hooks.ts) ──
36
- // Inline types to avoid broken relative-path imports.
37
- // These match the official InternalHookEvent / InternalHookHandler
38
- // from OpenClaw v2026.2.15.
39
-
40
- type InternalHookEventType = "command" | "session" | "agent" | "gateway";
41
-
42
- interface InternalHookEvent {
43
- /** The type of event */
44
- type: InternalHookEventType;
45
- /** The specific action within the type (e.g., "before_tool_call") */
46
- action: string;
47
- /** The session key this event relates to */
48
- sessionKey: string;
49
- /** Additional context specific to the event */
50
- context: Record<string, unknown>;
51
- /** Timestamp when the event occurred */
52
- timestamp: Date;
53
- /** Messages to send back to the user (hooks can push to this array) */
54
- messages: string[];
55
- }
56
-
57
- type InternalHookHandler = (event: InternalHookEvent) => Promise<void> | void;
58
-
59
- // Re-export as the public types for compatibility
60
- type HookHandler = InternalHookHandler;
61
- type HookEvent = InternalHookEvent;
62
-
63
- // ── Runtime threat patterns (12 checks) ──
64
- interface RuntimeCheck {
65
- id: string;
66
- severity: "CRITICAL" | "HIGH" | "MEDIUM";
67
- desc: string;
68
- test: (s: string) => boolean;
69
- }
70
-
71
- const RUNTIME_CHECKS: RuntimeCheck[] = [
72
- {
73
- id: 'RT_REVSHELL', severity: 'CRITICAL', desc: 'Reverse shell attempt',
74
- test: (s: string) => /\/dev\/tcp\/|nc\s+-e|ncat\s+-e|bash\s+-i\s+>&|socat\s+TCP/i.test(s)
75
- },
76
- {
77
- id: 'RT_CRED_EXFIL', severity: 'CRITICAL', desc: 'Credential exfiltration to external',
78
- test: (s: string) => {
79
- return /(webhook\.site|requestbin\.com|hookbin\.com|pipedream\.net|ngrok\.io|socifiapp\.com)/i.test(s) &&
80
- /(token|key|secret|password|credential|env)/i.test(s);
81
- }
82
- },
83
- {
84
- id: 'RT_GUARDRAIL_OFF', severity: 'CRITICAL', desc: 'Guardrail disabling attempt',
85
- test: (s: string) => /exec\.approvals?\s*[:=]\s*['"]?(off|false)|tools\.exec\.host\s*[:=]\s*['"]?gateway/i.test(s)
86
- },
87
- {
88
- id: 'RT_GATEKEEPER', severity: 'CRITICAL', desc: 'macOS Gatekeeper bypass (xattr)',
89
- test: (s: string) => /xattr\s+-[crd]\s.*quarantine/i.test(s)
90
- },
91
- {
92
- id: 'RT_AMOS', severity: 'CRITICAL', desc: 'ClawHavoc AMOS indicator',
93
- test: (s: string) => /socifiapp|Atomic\s*Stealer|AMOS/i.test(s)
94
- },
95
- {
96
- id: 'RT_MAL_IP', severity: 'CRITICAL', desc: 'Known malicious IP',
97
- test: (s: string) => /91\.92\.242\.30/i.test(s)
98
- },
99
- {
100
- id: 'RT_DNS_EXFIL', severity: 'HIGH', desc: 'DNS-based exfiltration',
101
- test: (s: string) => /nslookup\s+.*\$|dig\s+.*\$.*@/i.test(s)
102
- },
103
- {
104
- id: 'RT_B64_SHELL', severity: 'CRITICAL', desc: 'Base64 decode piped to shell',
105
- test: (s: string) => /base64\s+(-[dD]|--decode)\s*\|\s*(sh|bash)/i.test(s)
106
- },
107
- {
108
- id: 'RT_CURL_BASH', severity: 'CRITICAL', desc: 'Download piped to shell',
109
- test: (s: string) => /(curl|wget)\s+[^\n]*\|\s*(sh|bash|zsh)/i.test(s)
110
- },
111
- {
112
- id: 'RT_SSH_READ', severity: 'HIGH', desc: 'SSH private key access',
113
- test: (s: string) => /\.ssh\/id_|\.ssh\/authorized_keys/i.test(s)
114
- },
115
- {
116
- id: 'RT_WALLET', severity: 'HIGH', desc: 'Crypto wallet credential access',
117
- test: (s: string) => /wallet.*(?:seed|mnemonic|private.*key)|seed.*phrase/i.test(s)
118
- },
119
- {
120
- id: 'RT_CLOUD_META', severity: 'CRITICAL', desc: 'Cloud metadata endpoint access',
121
- test: (s: string) => /169\.254\.169\.254|metadata\.google|metadata\.aws/i.test(s)
122
- },
123
- ];
124
-
125
- // ── Audit logging ──
126
- const AUDIT_DIR = join(homedir(), ".openclaw", "guard-scanner");
127
- const AUDIT_FILE = join(AUDIT_DIR, "audit.jsonl");
128
-
129
- function ensureAuditDir(): void {
130
- try { mkdirSync(AUDIT_DIR, { recursive: true }); } catch { }
131
- }
132
-
133
- function logAudit(entry: Record<string, unknown>): void {
134
- ensureAuditDir();
135
- const line = JSON.stringify({ ...entry, ts: new Date().toISOString() }) + '\n';
136
- try { appendFileSync(AUDIT_FILE, line); } catch { }
137
- }
138
-
139
- // ── Main Handler ──
140
- const handler: HookHandler = async (event) => {
141
- // Only handle agent:before_tool_call events
142
- if (event.type !== "agent" || event.action !== "before_tool_call") return;
143
-
144
- const { toolName, toolArgs } = event.context as {
145
- toolName?: string;
146
- toolArgs?: Record<string, unknown>;
147
- };
148
- if (!toolName || !toolArgs) return;
149
-
150
- // Get mode from context config (if available)
151
- const cfg = event.context.cfg as Record<string, unknown> | undefined;
152
- const hookEntries = (cfg as any)?.hooks?.internal?.entries?.['guard-scanner'] as Record<string, unknown> | undefined;
153
- const mode = (hookEntries?.mode as string) || 'enforce';
154
-
155
- // Only check tools that can cause damage
156
- const dangerousTools = new Set(['exec', 'write', 'edit', 'browser', 'web_fetch', 'message']);
157
- if (!dangerousTools.has(toolName)) return;
158
-
159
- const serialized = JSON.stringify(toolArgs);
160
-
161
- for (const check of RUNTIME_CHECKS) {
162
- if (check.test(serialized)) {
163
- const entry = {
164
- tool: toolName,
165
- check: check.id,
166
- severity: check.severity,
167
- desc: check.desc,
168
- mode,
169
- action: 'warned' as string,
170
- session: event.sessionKey,
171
- };
172
-
173
- // NOTE: OpenClaw InternalHookEvent does not currently support
174
- // a cancel/veto mechanism. When it does, uncomment the blocking
175
- // logic below. For now, all detections are warnings only.
176
- //
177
- // if (mode === 'strict' && (check.severity === 'CRITICAL' || check.severity === 'HIGH')) {
178
- // entry.action = 'blocked';
179
- // logAudit(entry);
180
- // event.messages.push(`🛡️ guard-scanner BLOCKED: ${check.desc} [${check.id}]`);
181
- // event.cancel = true; // Not yet in the public API
182
- // return;
183
- // }
184
- //
185
- // if (mode === 'enforce' && check.severity === 'CRITICAL') {
186
- // entry.action = 'blocked';
187
- // logAudit(entry);
188
- // event.messages.push(`🛡️ guard-scanner BLOCKED: ${check.desc} [${check.id}]`);
189
- // event.cancel = true; // Not yet in the public API
190
- // return;
191
- // }
192
-
193
- // Current behaviour: warn and log for all modes
194
- logAudit(entry);
195
-
196
- if (check.severity === 'CRITICAL') {
197
- event.messages.push(`🛡️ guard-scanner WARNING: ${check.desc} [${check.id}]`);
198
- console.warn(`[guard-scanner] ⚠️ WARNING: ${check.desc} [${check.id}]`);
199
- } else if (check.severity === 'HIGH') {
200
- event.messages.push(`🛡️ guard-scanner NOTICE: ${check.desc} [${check.id}]`);
201
- console.warn(`[guard-scanner] ℹ️ NOTICE: ${check.desc} [${check.id}]`);
202
- }
203
- }
204
- }
205
- };
206
-
207
- export default handler;
package/src/cli.js DELETED
@@ -1,149 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * guard-scanner CLI
4
- *
5
- * @security-manifest
6
- * env-read: []
7
- * env-write: []
8
- * network: none
9
- * fs-read: [scan target directory, plugin files, custom rules files]
10
- * fs-write: [JSON/SARIF/HTML reports to scan directory]
11
- * exec: none
12
- * purpose: CLI entry point for guard-scanner static analysis
13
- *
14
- * Usage: guard-scanner [scan-dir] [options]
15
- *
16
- * Options:
17
- * --verbose, -v Detailed findings
18
- * --json JSON report
19
- * --sarif SARIF report (CI/CD)
20
- * --html HTML report
21
- * --self-exclude Skip scanning self
22
- * --strict Lower thresholds
23
- * --summary-only Summary only
24
- * --check-deps Scan dependencies
25
- * --rules <file> Custom rules JSON
26
- * --plugin <file> Load plugin module
27
- * --fail-on-findings Exit 1 on findings (CI/CD)
28
- * --help, -h Help
29
- */
30
-
31
- const fs = require('fs');
32
- const path = require('path');
33
- const { GuardScanner, VERSION } = require('./scanner.js');
34
-
35
- const args = process.argv.slice(2);
36
-
37
- if (args.includes('--help') || args.includes('-h')) {
38
- console.log(`
39
- 🛡️ guard-scanner v${VERSION} — Agent Skill Security Scanner
40
-
41
- Usage: guard-scanner [scan-dir] [options]
42
-
43
- Options:
44
- --verbose, -v Detailed findings with categories and samples
45
- --json Write JSON report to scan-dir/guard-scanner-report.json
46
- --sarif Write SARIF report (GitHub Code Scanning / CI/CD)
47
- --html Write HTML report (visual dashboard)
48
- --self-exclude Skip scanning the guard-scanner skill itself
49
- --strict Lower detection thresholds (more sensitive)
50
- --summary-only Only print the summary table
51
- --check-deps Scan package.json for dependency chain risks
52
- --rules <file> Load custom rules from JSON file
53
- --plugin <file> Load plugin module (JS file exporting { name, patterns })
54
- --fail-on-findings Exit code 1 if any findings (CI/CD)
55
- --help, -h Show this help
56
-
57
- Custom Rules JSON Format:
58
- [
59
- {
60
- "id": "CUSTOM_001",
61
- "pattern": "dangerous_function\\\\(",
62
- "flags": "gi",
63
- "severity": "HIGH",
64
- "cat": "malicious-code",
65
- "desc": "Custom: dangerous function call",
66
- "codeOnly": true
67
- }
68
- ]
69
-
70
- Plugin API:
71
- // my-plugin.js
72
- module.exports = {
73
- name: 'my-plugin',
74
- patterns: [
75
- { id: 'MY_01', cat: 'custom', regex: /pattern/g, severity: 'HIGH', desc: 'Description', all: true }
76
- ]
77
- };
78
-
79
- Examples:
80
- guard-scanner ./skills/ --verbose --self-exclude
81
- guard-scanner ./skills/ --strict --json --sarif --check-deps
82
- guard-scanner ./skills/ --html --verbose --check-deps
83
- guard-scanner ./skills/ --rules my-rules.json --fail-on-findings
84
- guard-scanner ./skills/ --plugin ./my-plugin.js
85
- `);
86
- process.exit(0);
87
- }
88
-
89
- const verbose = args.includes('--verbose') || args.includes('-v');
90
- const jsonOutput = args.includes('--json');
91
- const sarifOutput = args.includes('--sarif');
92
- const htmlOutput = args.includes('--html');
93
- const selfExclude = args.includes('--self-exclude');
94
- const strict = args.includes('--strict');
95
- const summaryOnly = args.includes('--summary-only');
96
- const checkDeps = args.includes('--check-deps');
97
- const failOnFindings = args.includes('--fail-on-findings');
98
-
99
- const rulesIdx = args.indexOf('--rules');
100
- const rulesFile = rulesIdx >= 0 ? args[rulesIdx + 1] : null;
101
-
102
- // Collect plugins
103
- const plugins = [];
104
- let idx = 0;
105
- while (idx < args.length) {
106
- if (args[idx] === '--plugin' && args[idx + 1]) {
107
- plugins.push(args[idx + 1]);
108
- idx += 2;
109
- } else {
110
- idx++;
111
- }
112
- }
113
-
114
- const scanDir = args.find(a =>
115
- !a.startsWith('-') &&
116
- a !== rulesFile &&
117
- !plugins.includes(a)
118
- ) || process.cwd();
119
-
120
- const scanner = new GuardScanner({
121
- verbose, selfExclude, strict, summaryOnly, checkDeps, rulesFile, plugins
122
- });
123
-
124
- scanner.scanDirectory(scanDir);
125
-
126
- // Output reports
127
- if (jsonOutput) {
128
- const report = scanner.toJSON();
129
- const outPath = path.join(scanDir, 'guard-scanner-report.json');
130
- fs.writeFileSync(outPath, JSON.stringify(report, null, 2));
131
- console.log(`\n📄 JSON report: ${outPath}`);
132
- }
133
-
134
- if (sarifOutput) {
135
- const outPath = path.join(scanDir, 'guard-scanner.sarif');
136
- fs.writeFileSync(outPath, JSON.stringify(scanner.toSARIF(scanDir), null, 2));
137
- console.log(`\n📄 SARIF report: ${outPath}`);
138
- }
139
-
140
- if (htmlOutput) {
141
- const outPath = path.join(scanDir, 'guard-scanner-report.html');
142
- fs.writeFileSync(outPath, scanner.toHTML());
143
- console.log(`\n📄 HTML report: ${outPath}`);
144
- }
145
-
146
- // Exit codes
147
- if (scanner.stats.malicious > 0) process.exit(1);
148
- if (failOnFindings && scanner.findings.length > 0) process.exit(1);
149
- process.exit(0);