guard-scanner 3.3.0 → 3.4.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
@@ -1,18 +1,25 @@
1
1
  <p align="center">
2
2
  <h1 align="center">🛡️ guard-scanner</h1>
3
3
  <p align="center">
4
- <strong>Security scanner + runtime guard for AI agent skills</strong><br>
5
- 19 runtime threat patterns • 190+ static patterns • 21 categories • OpenClaw-compatible plugin<br>
6
- <sub>🆕 v3.1.0 — OpenClaw Community Plugin + 3-Layer Runtime Defense (Threat / EAE Paradox / Parity Judge)</sub>
4
+ <strong>The first security scanner purpose-built for AI agent skills</strong><br>
5
+ Detect prompt injection, identity hijacking, memory poisoning, and 18 more threat classes<br>
6
+ before they compromise your agents.
7
7
  </p>
8
8
  <p align="center">
9
- <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License"></a>
10
- <img src="https://img.shields.io/badge/OpenClaw-compatible-4A90D9" alt="OpenClaw Compatible">
11
- <img src="https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen" alt="Node.js 18+">
12
- <img src="https://img.shields.io/badge/dependencies-0-success" alt="Zero Dependencies">
13
- <img src="https://img.shields.io/badge/tests-87%2F87-brightgreen" alt="Tests Passing">
14
- <img src="https://img.shields.io/badge/runtime_patterns-19-red" alt="19 Runtime Patterns">
15
- <img src="https://img.shields.io/badge/categories-21-blueviolet" alt="21 Categories">
9
+ <a href="https://www.npmjs.com/package/guard-scanner"><img src="https://img.shields.io/npm/v/guard-scanner.svg?style=flat-square&color=cb3837" alt="npm version"></a>
10
+ <a href="https://www.npmjs.com/package/guard-scanner"><img src="https://img.shields.io/npm/dm/guard-scanner.svg?style=flat-square" alt="npm downloads"></a>
11
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square" alt="MIT License"></a>
12
+ <img src="https://img.shields.io/badge/dependencies-0-success?style=flat-square" alt="Zero Dependencies">
13
+ <img src="https://img.shields.io/badge/tests-133%2F133-brightgreen?style=flat-square" alt="Tests Passing">
14
+ <img src="https://img.shields.io/badge/OWASP_Agentic-90%25-green?style=flat-square" alt="OWASP Agentic 90%">
15
+ <img src="https://img.shields.io/badge/patterns-190%2B-blueviolet?style=flat-square" alt="190+ Patterns">
16
+ </p>
17
+ <p align="center">
18
+ <a href="#quick-start">Quick Start</a> •
19
+ <a href="#threat-categories">Threat Categories</a> •
20
+ <a href="#openclaw-plugin-setup-v310">OpenClaw Plugin</a> •
21
+ <a href="#cicd-integration">CI/CD</a> •
22
+ <a href="#plugin-api">Plugin API</a>
16
23
  </p>
17
24
  </p>
18
25
 
@@ -543,11 +550,11 @@ console.log(scanner.toHTML()); // HTML string
543
550
  ## Test Results
544
551
 
545
552
  ```
546
- ℹ tests 87
547
- ℹ suites 20
548
- ℹ pass 87
553
+ ℹ tests 133
554
+ ℹ suites 24
555
+ ℹ pass 133
549
556
  ℹ fail 0
550
- ℹ duration_ms 111ms
557
+ ℹ duration_ms 132ms
551
558
  ```
552
559
 
553
560
  | Suite | Tests | Coverage |
@@ -660,6 +667,8 @@ identity file tampering, prompt worms, or memory poisoning.
660
667
  We built one.
661
668
 
662
669
  —— Guava 🍈 & Dee
670
+ Singularity Lab (シンギュラリティ研究所)
671
+ Proving ASI-human coexistence through code.
663
672
  ```
664
673
 
665
674
  ---
@@ -15,6 +15,41 @@ guard-scanner's threat taxonomy combines three sources:
15
15
 
16
16
  ---
17
17
 
18
+ ## OWASP Agentic Security Top 10 Mapping
19
+
20
+ > Source: [OWASP Top 10 for Agentic Applications 2026](https://owasp.org/www-project-top-10-for-ai-agents/)
21
+
22
+ | OWASP ID | Risk Name | guard-scanner Coverage | Categories |
23
+ |----------|-----------|----------------------|------------|
24
+ | **ASI01** | Agent Goal Hijack | ✅ **Full** | Cat 1 (Prompt Injection), Cat 13 (Prompt Worm) |
25
+ | **ASI02** | Tool Misuse & Exploitation | ✅ **Full** | Cat 2 (Malicious Code), Cat 16 (MCP Security) |
26
+ | **ASI03** | Identity & Privilege Abuse | ✅ **Full** | Cat 4 (Credential Handling), Cat 17 (Identity Hijacking) |
27
+ | **ASI04** | Supply Chain Vulnerabilities | ✅ **Full** | Cat 7 (Unverifiable Deps), Cat 3 (Suspicious Downloads), Cat 16 (MCP Shadow Server) |
28
+ | **ASI05** | Unexpected Code Execution | ✅ **Full** | Cat 2 (Malicious Code), Cat 9 (Obfuscation) |
29
+ | **ASI06** | Memory & Context Poisoning | ✅ **Full** | Cat 12 (Memory Poisoning), Cat 17 (Identity Hijacking) |
30
+ | **ASI07** | Insecure Inter-Agent Comms | ✅ **Partial** | Cat 16 (MCP Security — MCP_NO_AUTH, MCP_SHADOW_SERVER) |
31
+ | **ASI08** | Cascading Failures | ⚠️ **Gap** | Not covered — requires runtime multi-agent flow tracing |
32
+ | **ASI09** | Human-Agent Trust Exploitation | ✅ **Full** | Layer 2 (EAE Paradox), Layer 3 (Parity Judge) |
33
+ | **ASI10** | Rogue Agents | ✅ **Full** | Cat 17 (Identity Hijacking), Layer 4 (Brain — behavioral analysis) |
34
+
35
+ ### Coverage Summary
36
+
37
+ - **Full Coverage**: 8/10 (ASI01-06, ASI09-10)
38
+ - **Partial Coverage**: 1/10 (ASI07)
39
+ - **Gap**: 1/10 (ASI08 — requires runtime multi-agent orchestration monitoring)
40
+ - **Overall**: 90% coverage of OWASP Agentic Security Top 10
41
+
42
+ ### Unique to guard-scanner (not in OWASP Top 10)
43
+
44
+ | Feature | Description |
45
+ |---------|-------------|
46
+ | **Layer 4: Brain** | Behavioral analysis — detects agents that skip research before executing unknown tools |
47
+ | **ZombieAgent** | URL-encoded data exfiltration via static URLs, char maps, and loop fetch |
48
+ | **Safeguard Bypass** | Reprompt, double-prompt, and retry-based safety circumvention |
49
+ | **Cat 15: CVE Patterns** | Known CVE-specific detection (gateway URLs, sandbox disable, Gatekeeper bypass) |
50
+
51
+ ---
52
+
18
53
  ## Cat 1: Prompt Injection
19
54
 
20
55
  **Severity: CRITICAL**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "guard-scanner",
3
- "version": "3.3.0",
4
- "description": "Agent security scanner + runtime guard — 19 threat patterns, before_tool_call hook, CLI scanner, SARIF. OpenClaw-compatible plugin.",
3
+ "version": "3.4.0",
4
+ "description": "Agent security scanner + runtime guard — 190+ static patterns, 26 runtime checks (5 layers), before_tool_call hook, CLI, SARIF. OpenClaw-compatible plugin.",
5
5
  "openclaw.extensions": "./openclaw.plugin.json",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -42,6 +42,7 @@
42
42
  "homepage": "https://github.com/koatora20/guard-scanner",
43
43
  "files": [
44
44
  "dist/",
45
+ "src/",
45
46
  "hooks/",
46
47
  "ts-src/",
47
48
  "docs/",
@@ -55,4 +56,4 @@
55
56
  "@types/node": "^22.0.0",
56
57
  "typescript": "^5.7.0"
57
58
  }
58
- }
59
+ }
package/src/cli.js ADDED
@@ -0,0 +1,168 @@
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 file
46
+ --sarif Write SARIF report to file (GitHub Code Scanning / CI/CD)
47
+ --html Write HTML report (visual dashboard)
48
+ --format json|sarif Print JSON or SARIF to stdout (pipeable, v3.2.0)
49
+ --quiet Suppress all text output (use with --format for clean pipes)
50
+ --self-exclude Skip scanning the guard-scanner skill itself
51
+ --strict Lower detection thresholds (more sensitive)
52
+ --summary-only Only print the summary table
53
+ --check-deps Scan package.json for dependency chain risks
54
+ --rules <file> Load custom rules from JSON file
55
+ --plugin <file> Load plugin module (JS file exporting { name, patterns })
56
+ --fail-on-findings Exit code 1 if any findings (CI/CD)
57
+ --help, -h Show this help
58
+
59
+ Custom Rules JSON Format:
60
+ [
61
+ {
62
+ "id": "CUSTOM_001",
63
+ "pattern": "dangerous_function\\\\(",
64
+ "flags": "gi",
65
+ "severity": "HIGH",
66
+ "cat": "malicious-code",
67
+ "desc": "Custom: dangerous function call",
68
+ "codeOnly": true
69
+ }
70
+ ]
71
+
72
+ Plugin API:
73
+ // my-plugin.js
74
+ module.exports = {
75
+ name: 'my-plugin',
76
+ patterns: [
77
+ { id: 'MY_01', cat: 'custom', regex: /pattern/g, severity: 'HIGH', desc: 'Description', all: true }
78
+ ]
79
+ };
80
+
81
+ Examples:
82
+ guard-scanner ./skills/ --verbose --self-exclude
83
+ guard-scanner ./skills/ --strict --json --sarif --check-deps
84
+ guard-scanner ./skills/ --html --verbose --check-deps
85
+ guard-scanner ./skills/ --rules my-rules.json --fail-on-findings
86
+ guard-scanner ./skills/ --plugin ./my-plugin.js
87
+ `);
88
+ process.exit(0);
89
+ }
90
+
91
+ const verbose = args.includes('--verbose') || args.includes('-v');
92
+ const jsonOutput = args.includes('--json');
93
+ const sarifOutput = args.includes('--sarif');
94
+ const htmlOutput = args.includes('--html');
95
+ const selfExclude = args.includes('--self-exclude');
96
+ const strict = args.includes('--strict');
97
+ const summaryOnly = args.includes('--summary-only');
98
+ const checkDeps = args.includes('--check-deps');
99
+ const failOnFindings = args.includes('--fail-on-findings');
100
+ const quietMode = args.includes('--quiet');
101
+
102
+ // --format json|sarif → stdout output (v3.2.0)
103
+ const formatIdx = args.indexOf('--format');
104
+ const formatValue = formatIdx >= 0 ? args[formatIdx + 1] : null;
105
+
106
+ const rulesIdx = args.indexOf('--rules');
107
+ const rulesFile = rulesIdx >= 0 ? args[rulesIdx + 1] : null;
108
+
109
+ // Collect plugins
110
+ const plugins = [];
111
+ let idx = 0;
112
+ while (idx < args.length) {
113
+ if (args[idx] === '--plugin' && args[idx + 1]) {
114
+ plugins.push(args[idx + 1]);
115
+ idx += 2;
116
+ } else {
117
+ idx++;
118
+ }
119
+ }
120
+
121
+ const scanDir = args.find(a =>
122
+ !a.startsWith('-') &&
123
+ a !== rulesFile &&
124
+ a !== formatValue &&
125
+ !plugins.includes(a)
126
+ ) || process.cwd();
127
+
128
+ const scanner = new GuardScanner({
129
+ verbose, selfExclude, strict, summaryOnly, checkDeps, rulesFile, plugins,
130
+ quiet: quietMode || !!formatValue,
131
+ });
132
+
133
+ scanner.scanDirectory(scanDir);
134
+
135
+ // Output reports (file-based, backward compatible)
136
+ if (jsonOutput) {
137
+ const report = scanner.toJSON();
138
+ const outPath = path.join(scanDir, 'guard-scanner-report.json');
139
+ fs.writeFileSync(outPath, JSON.stringify(report, null, 2));
140
+ if (!quietMode && !formatValue) console.log(`\n📄 JSON report: ${outPath}`);
141
+ }
142
+
143
+ if (sarifOutput) {
144
+ const outPath = path.join(scanDir, 'guard-scanner.sarif');
145
+ fs.writeFileSync(outPath, JSON.stringify(scanner.toSARIF(scanDir), null, 2));
146
+ if (!quietMode && !formatValue) console.log(`\n📄 SARIF report: ${outPath}`);
147
+ }
148
+
149
+ if (htmlOutput) {
150
+ const outPath = path.join(scanDir, 'guard-scanner-report.html');
151
+ fs.writeFileSync(outPath, scanner.toHTML());
152
+ if (!quietMode && !formatValue) console.log(`\n📄 HTML report: ${outPath}`);
153
+ }
154
+
155
+ // --format stdout output (v3.2.0)
156
+ if (formatValue === 'json') {
157
+ process.stdout.write(JSON.stringify(scanner.toJSON(), null, 2) + '\n');
158
+ } else if (formatValue === 'sarif') {
159
+ process.stdout.write(JSON.stringify(scanner.toSARIF(scanDir), null, 2) + '\n');
160
+ } else if (formatValue) {
161
+ console.error(`❌ Unknown format: ${formatValue}. Use 'json' or 'sarif'.`);
162
+ process.exit(2);
163
+ }
164
+
165
+ // Exit codes
166
+ if (scanner.stats.malicious > 0) process.exit(1);
167
+ if (failOnFindings && scanner.findings.length > 0) process.exit(1);
168
+ process.exit(0);
@@ -0,0 +1,239 @@
1
+ /**
2
+ * guard-scanner — HTML Report Template
3
+ * Dark Glassmorphism + Conic-gradient Risk Gauges
4
+ * Zero dependencies. Pure CSS animations.
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ function generateHTML(version, stats, findings) {
10
+ const safetyRate = stats.scanned > 0 ? Math.round(((stats.clean + stats.low) / stats.scanned) * 100) : 100;
11
+
12
+ // ── Risk gauge (conic-gradient donut) ──
13
+ const riskGauge = (risk) => {
14
+ const angle = (risk / 100) * 360;
15
+ const color = risk >= 80 ? '#ef4444' : risk >= 30 ? '#f59e0b' : '#22c55e';
16
+ return `<div class="risk-gauge" style="--risk-angle:${angle}deg;--risk-color:${color}"><span class="risk-val">${risk}</span></div>`;
17
+ };
18
+
19
+ // ── Aggregate severity + category counts ──
20
+ const sevCounts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
21
+ const catCounts = {};
22
+ for (const sr of findings) {
23
+ for (const f of sr.findings) {
24
+ sevCounts[f.severity] = (sevCounts[f.severity] || 0) + 1;
25
+ catCounts[f.cat] = (catCounts[f.cat] || 0) + 1;
26
+ }
27
+ }
28
+ const total = Object.values(sevCounts).reduce((a, b) => a + b, 0);
29
+
30
+ // ── Severity distribution bars ──
31
+ const sevColors = { CRITICAL: '#ef4444', HIGH: '#f97316', MEDIUM: '#eab308', LOW: '#22c55e' };
32
+ const sevBars = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'].map(s => {
33
+ const c = sevCounts[s], pct = total > 0 ? ((c / total) * 100).toFixed(1) : 0;
34
+ return `<div class="bar-row"><span class="bar-label" style="color:${sevColors[s]}">${s}</span><div class="bar-track"><div class="bar-fill" style="width:${pct}%;background:${sevColors[s]}"></div></div><span class="bar-ct">${c}</span></div>`;
35
+ }).join('\n');
36
+
37
+ // ── Top 8 categories ──
38
+ const topCats = Object.entries(catCounts).sort((a, b) => b[1] - a[1]).slice(0, 8);
39
+ const catBars = topCats.map(([cat, c]) => {
40
+ const pct = total > 0 ? ((c / total) * 100).toFixed(0) : 0;
41
+ return `<div class="bar-row"><span class="bar-label cat-lbl">${cat}</span><div class="bar-track"><div class="bar-fill cat-fill" style="width:${pct}%"></div></div><span class="bar-ct">${c}</span></div>`;
42
+ }).join('\n');
43
+
44
+ // ── Skill cards ──
45
+ let cards = '';
46
+ for (const sr of findings) {
47
+ const vc = sr.verdict === 'MALICIOUS' ? 'mal' : sr.verdict === 'SUSPICIOUS' ? 'sus' : sr.verdict === 'LOW RISK' ? 'low' : 'ok';
48
+ const icon = sr.verdict === 'MALICIOUS' ? '💀' : sr.verdict === 'SUSPICIOUS' ? '⚡' : sr.verdict === 'LOW RISK' ? '⚠️' : '✅';
49
+ const rows = sr.findings.map(f => {
50
+ const sc = f.severity.toLowerCase();
51
+ return `<tr class="f-row"><td><span class="pill ${sc}">${f.severity}</span></td><td class="mono dim">${f.cat}</td><td>${f.desc}</td><td class="mono muted">${f.file}${f.line ? ':' + f.line : ''}</td></tr>`;
52
+ }).join('\n');
53
+
54
+ cards += `
55
+ <details class="card card-${vc}" ${(vc === 'mal' || vc === 'sus') ? 'open' : ''}>
56
+ <summary class="card-head">
57
+ <div class="card-info"><span class="card-icon">${icon}</span><span class="card-name">${sr.skill}</span><span class="v-pill v-${vc}">${sr.verdict}</span></div>
58
+ ${riskGauge(sr.risk)}
59
+ </summary>
60
+ <div class="card-body">
61
+ <table class="ftable"><thead><tr><th>Severity</th><th>Category</th><th>Description</th><th>Location</th></tr></thead><tbody>${rows}</tbody></table>
62
+ </div>
63
+ </details>`;
64
+ }
65
+
66
+ // ── Full HTML ──
67
+ return `<!DOCTYPE html>
68
+ <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
69
+ <title>guard-scanner v${version} — Security Report</title>
70
+ <link rel="preconnect" href="https://fonts.googleapis.com">
71
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
72
+ <style>
73
+ /* ===== Tokens ===== */
74
+ :root{
75
+ --bg:#06070d;--sf:rgba(15,18,35,.7);--cd:rgba(20,24,50,.55);--ch:rgba(28,33,65,.65);
76
+ --bdr:rgba(100,120,255,.08);--glow:rgba(100,140,255,.15);
77
+ --t1:#e8eaf6;--t2:#8b92b3;--t3:#5a6180;
78
+ --blue:#6c8cff;--purple:#a78bfa;--cyan:#22d3ee;--green:#22c55e;--yellow:#eab308;--orange:#f97316;--red:#ef4444;
79
+ --sans:'Inter',system-ui,-apple-system,sans-serif;--mono:'JetBrains Mono','SF Mono',monospace;
80
+ --r:12px;
81
+ }
82
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
83
+ html{scroll-behavior:smooth}
84
+ body{font-family:var(--sans);background:var(--bg);color:var(--t1);min-height:100vh;overflow-x:hidden;line-height:1.6}
85
+
86
+ /* ===== Ambient BG ===== */
87
+ body::before{content:'';position:fixed;inset:0;z-index:-1;
88
+ background:radial-gradient(ellipse 80% 60% at 20% 10%,rgba(108,140,255,.08) 0%,transparent 50%),
89
+ radial-gradient(ellipse 60% 50% at 80% 90%,rgba(167,139,250,.06) 0%,transparent 50%),
90
+ radial-gradient(ellipse 50% 40% at 50% 50%,rgba(34,211,238,.04) 0%,transparent 50%);
91
+ animation:pulse 12s ease-in-out infinite alternate}
92
+ @keyframes pulse{0%{opacity:.6;transform:scale(1)}100%{opacity:1;transform:scale(1.05)}}
93
+
94
+ .wrap{max-width:1200px;margin:0 auto;padding:32px 24px}
95
+
96
+ /* ===== Header ===== */
97
+ .hdr{text-align:center;margin-bottom:36px;animation:fadeD .6s ease-out}
98
+ .hdr h1{font-size:2.2em;font-weight:800;letter-spacing:-.03em;
99
+ background:linear-gradient(135deg,var(--blue),var(--purple),var(--cyan));
100
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
101
+ .hdr .sub{color:var(--t2);font-size:.95em;margin-top:4px}
102
+ .hdr .ts{color:var(--t3);font-family:var(--mono);font-size:.78em;margin-top:2px}
103
+
104
+ /* ===== Stat Grid ===== */
105
+ .sg{display:grid;grid-template-columns:repeat(auto-fit,minmax(155px,1fr));gap:14px;margin-bottom:28px;animation:fadeU .7s ease-out}
106
+ .sc{background:var(--cd);backdrop-filter:blur(20px) saturate(1.5);-webkit-backdrop-filter:blur(20px) saturate(1.5);
107
+ border:1px solid var(--bdr);border-radius:var(--r);padding:18px;text-align:center;
108
+ transition:all .3s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden}
109
+ .sc::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;
110
+ background:linear-gradient(90deg,transparent,var(--ac,var(--blue)),transparent);opacity:0;transition:opacity .3s}
111
+ .sc:hover{transform:translateY(-3px);border-color:var(--glow)}.sc:hover::before{opacity:1}
112
+ .sn{font-size:2.2em;font-weight:800;letter-spacing:-.04em;line-height:1.1}
113
+ .sl{color:var(--t2);font-size:.78em;font-weight:500;margin-top:3px;text-transform:uppercase;letter-spacing:.06em}
114
+ .sc.g{--ac:var(--green)}.sc.g .sn{color:var(--green)}
115
+ .sc.l{--ac:#86efac}.sc.l .sn{color:#86efac}
116
+ .sc.s{--ac:var(--yellow)}.sc.s .sn{color:var(--yellow)}
117
+ .sc.m{--ac:var(--red)}.sc.m .sn{color:var(--red)}
118
+ .sc.r{--ac:var(--cyan)}.sc.r .sn{color:var(--cyan)}
119
+
120
+ /* ===== Analysis Panels ===== */
121
+ .ag{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-bottom:28px;animation:fadeU .8s ease-out}
122
+ @media(max-width:768px){.ag{grid-template-columns:1fr}}
123
+ .pn{background:var(--cd);backdrop-filter:blur(20px) saturate(1.5);-webkit-backdrop-filter:blur(20px) saturate(1.5);
124
+ border:1px solid var(--bdr);border-radius:var(--r);padding:22px}
125
+ .pn h2{font-size:.88em;font-weight:600;color:var(--t2);text-transform:uppercase;letter-spacing:.08em;margin-bottom:14px}
126
+
127
+ /* Bars */
128
+ .bar-row{display:flex;align-items:center;gap:8px;margin-bottom:8px}
129
+ .bar-label{font-family:var(--mono);font-size:.72em;font-weight:600;width:68px;text-align:right}
130
+ .cat-lbl{font-family:var(--sans);font-weight:500;color:var(--t2);width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
131
+ .bar-track{flex:1;height:5px;background:rgba(255,255,255,.04);border-radius:3px;overflow:hidden}
132
+ .bar-fill{height:100%;border-radius:3px;transition:width 1.2s cubic-bezier(.4,0,.2,1)}
133
+ .cat-fill{background:linear-gradient(90deg,var(--blue),var(--purple))}
134
+ .bar-ct{font-family:var(--mono);font-size:.76em;color:var(--t3);width:28px}
135
+
136
+ /* ===== Skill Cards ===== */
137
+ .sec-title{font-size:1.05em;font-weight:700;margin-bottom:14px}
138
+ .card{background:var(--cd);backdrop-filter:blur(16px) saturate(1.4);-webkit-backdrop-filter:blur(16px) saturate(1.4);
139
+ border:1px solid var(--bdr);border-radius:var(--r);margin-bottom:10px;overflow:hidden;
140
+ transition:all .3s ease;animation:fadeU .5s ease-out both}
141
+ .card:hover{border-color:var(--glow)}
142
+ .card-mal{border-left:3px solid var(--red)}
143
+ .card-sus{border-left:3px solid var(--yellow)}
144
+ .card-low{border-left:3px solid #86efac}
145
+ .card-ok{border-left:3px solid var(--green)}
146
+
147
+ .card-head{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;cursor:pointer;list-style:none}
148
+ .card-head::-webkit-details-marker{display:none}
149
+ .card-info{display:flex;align-items:center;gap:8px;flex:1;min-width:0}
150
+ .card-icon{font-size:1.15em}
151
+ .card-name{font-weight:600;font-size:.92em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
152
+ .v-pill{font-family:var(--mono);font-size:.66em;font-weight:600;padding:2px 7px;border-radius:4px;letter-spacing:.04em}
153
+ .v-mal{background:rgba(239,68,68,.15);color:var(--red)}
154
+ .v-sus{background:rgba(234,179,8,.15);color:var(--yellow)}
155
+ .v-low{background:rgba(134,239,172,.15);color:#86efac}
156
+ .v-ok{background:rgba(34,197,94,.15);color:var(--green)}
157
+
158
+ /* Risk Gauge */
159
+ .risk-gauge{width:44px;height:44px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;position:relative;
160
+ background:conic-gradient(var(--risk-color) 0deg,var(--risk-color) var(--risk-angle),rgba(255,255,255,.05) var(--risk-angle),rgba(255,255,255,.05) 360deg)}
161
+ .risk-gauge::after{content:'';position:absolute;inset:5px;border-radius:50%;background:var(--bg)}
162
+ .risk-val{position:relative;z-index:1;font-family:var(--mono);font-size:.68em;font-weight:700}
163
+
164
+ /* Findings Table */
165
+ .card-body{padding:0 18px 14px}
166
+ .ftable{width:100%;border-collapse:collapse;font-size:.82em}
167
+ .ftable th{text-align:left;font-size:.72em;font-weight:600;color:var(--t3);text-transform:uppercase;letter-spacing:.06em;padding:7px 9px;border-bottom:1px solid rgba(255,255,255,.04)}
168
+ .ftable td{padding:7px 9px;border-bottom:1px solid rgba(255,255,255,.025)}
169
+ .f-row{transition:background .2s}.f-row:hover{background:rgba(255,255,255,.02)}
170
+ .pill{font-family:var(--mono);font-size:.7em;font-weight:600;padding:2px 5px;border-radius:3px;letter-spacing:.03em}
171
+ .critical{background:rgba(239,68,68,.15);color:var(--red)}
172
+ .high{background:rgba(249,115,22,.15);color:var(--orange)}
173
+ .medium{background:rgba(234,179,8,.15);color:var(--yellow)}
174
+ .low{background:rgba(34,197,94,.15);color:var(--green)}
175
+ .mono{font-family:var(--mono);font-size:.9em}
176
+ .dim{color:var(--t2)}.muted{color:var(--t3);white-space:nowrap}
177
+
178
+ /* Empty */
179
+ .empty{text-align:center;padding:48px 20px;background:var(--cd);backdrop-filter:blur(20px);border:1px solid var(--bdr);border-radius:var(--r)}
180
+ .empty .ei{font-size:2.8em;margin-bottom:10px}
181
+ .empty p{color:var(--green);font-size:1.05em;font-weight:600}
182
+ .empty .es{color:var(--t3);font-size:.82em;margin-top:3px}
183
+
184
+ /* Footer */
185
+ .ft{text-align:center;margin-top:40px;padding-top:20px;border-top:1px solid rgba(255,255,255,.04);color:var(--t3);font-size:.78em}
186
+ .ft a{color:var(--blue);text-decoration:none}.ft a:hover{text-decoration:underline}
187
+
188
+ /* Animations */
189
+ @keyframes fadeD{from{opacity:0;transform:translateY(-18px)}to{opacity:1;transform:translateY(0)}}
190
+ @keyframes fadeU{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:translateY(0)}}
191
+
192
+ /* Responsive */
193
+ @media(max-width:640px){
194
+ .sg{grid-template-columns:repeat(2,1fr)}.sn{font-size:1.7em}.hdr h1{font-size:1.5em}
195
+ .ftable{font-size:.74em}
196
+ }
197
+
198
+ /* Print */
199
+ @media print{
200
+ body{background:#fff;color:#111}body::before{display:none}
201
+ .sc,.pn,.card{background:#f8f8f8;backdrop-filter:none;border-color:#ddd}
202
+ .sn{color:#111!important}.card{break-inside:avoid}
203
+ }
204
+ </style></head><body>
205
+ <div class="wrap">
206
+ <div class="hdr">
207
+ <h1>🛡️ guard-scanner v${version}</h1>
208
+ <div class="sub">Security Scan Report</div>
209
+ <div class="ts">${new Date().toISOString()}</div>
210
+ </div>
211
+
212
+ <div class="sg">
213
+ <div class="sc"><div class="sn">${stats.scanned}</div><div class="sl">Scanned</div></div>
214
+ <div class="sc g"><div class="sn">${stats.clean}</div><div class="sl">Clean</div></div>
215
+ <div class="sc l"><div class="sn">${stats.low}</div><div class="sl">Low Risk</div></div>
216
+ <div class="sc s"><div class="sn">${stats.suspicious}</div><div class="sl">Suspicious</div></div>
217
+ <div class="sc m"><div class="sn">${stats.malicious}</div><div class="sl">Malicious</div></div>
218
+ <div class="sc r"><div class="sn">${safetyRate}%</div><div class="sl">Safety Rate</div></div>
219
+ </div>
220
+
221
+ ${total > 0 ? `<div class="ag">
222
+ <div class="pn"><h2>Severity Distribution</h2>${sevBars}</div>
223
+ <div class="pn"><h2>Top Categories</h2>${catBars}</div>
224
+ </div>` : ''}
225
+
226
+ <div>
227
+ <div class="sec-title">Skill Analysis</div>
228
+ ${cards || `<div class="empty"><div class="ei">✅</div><p>All Clear — No Threats Detected</p><div class="es">${stats.scanned} skill(s) scanned, 0 findings</div></div>`}
229
+ </div>
230
+
231
+ <div class="ft">
232
+ guard-scanner v${version} &mdash; Zero dependencies. Zero compromises. 🛡️<br>
233
+ Built by <a href="https://github.com/koatora20">Guava 🍈 &amp; Dee</a>
234
+ </div>
235
+ </div>
236
+ </body></html>`;
237
+ }
238
+
239
+ module.exports = { generateHTML };
package/src/ioc-db.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * guard-scanner — Indicators of Compromise (IoC) Database
3
+ *
4
+ * @security-manifest
5
+ * env-read: []
6
+ * env-write: []
7
+ * network: none
8
+ * fs-read: []
9
+ * fs-write: []
10
+ * exec: none
11
+ * purpose: IoC data definitions only — no I/O, pure data export
12
+ *
13
+ * Known malicious IPs, domains, URLs, usernames, filenames, and typosquats.
14
+ * Sources: ClawHavoc campaign, Snyk ToxicSkills, Polymarket scams, community reports.
15
+ *
16
+ * Last updated: 2026-02-12
17
+ */
18
+
19
+ const KNOWN_MALICIOUS = {
20
+ ips: [
21
+ '91.92.242.30', // ClawHavoc C2
22
+ ],
23
+ domains: [
24
+ 'webhook.site', // Common exfil endpoint
25
+ 'requestbin.com', // Common exfil endpoint
26
+ 'hookbin.com', // Common exfil endpoint
27
+ 'pipedream.net', // Common exfil endpoint
28
+ 'ngrok.io', // Tunnel (context-dependent)
29
+ 'download.setup-service.com', // ClawHavoc decoy domain
30
+ 'socifiapp.com', // ClawHavoc v2 AMOS C2
31
+ ],
32
+ urls: [
33
+ 'glot.io/snippets/hfd3x9ueu5', // ClawHavoc macOS payload
34
+ 'github.com/Ddoy233', // ClawHavoc payload host
35
+ ],
36
+ usernames: ['zaycv', 'Ddoy233', 'Sakaen736jih'], // Known malicious actors
37
+ filenames: ['openclaw-agent.zip', 'openclawcli.zip'],
38
+ typosquats: [
39
+ // ClawHavoc campaign
40
+ 'clawhub', 'clawhub1', 'clawhubb', 'clawhubcli', 'clawwhub', 'cllawhub', 'clawdhub1',
41
+ // Polymarket scams
42
+ 'polymarket-trader', 'polymarket-pro', 'polytrading',
43
+ 'better-polymarket', 'polymarket-all-in-one',
44
+ // YouTube scams
45
+ 'youtube-summarize', 'youtube-thumbnail-grabber', 'youtube-video-downloader',
46
+ // Misc
47
+ 'auto-updater-agent', 'yahoo-finance-pro', 'x-trends-tracker',
48
+ 'lost-bitcoin-finder', 'solana-wallet-tracker', 'rankaj',
49
+ // Snyk ToxicSkills confirmed malicious
50
+ 'moltyverse-email', 'buy-anything', 'youtube-data', 'prediction-markets-roarin',
51
+ ],
52
+ };
53
+
54
+ module.exports = { KNOWN_MALICIOUS };