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 +23 -14
- package/docs/THREAT_TAXONOMY.md +35 -0
- package/package.json +4 -3
- package/src/cli.js +168 -0
- package/src/html-template.js +239 -0
- package/src/ioc-db.js +54 -0
- package/src/patterns.js +221 -0
- package/src/quarantine.js +41 -0
- package/src/runtime-guard.js +346 -0
- package/src/scanner.js +1024 -0
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>
|
|
5
|
-
|
|
6
|
-
|
|
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="
|
|
10
|
-
<img src="https://img.shields.io/
|
|
11
|
-
<img src="https://img.shields.io/badge/
|
|
12
|
-
<img src="https://img.shields.io/badge/dependencies-0-success" alt="Zero Dependencies">
|
|
13
|
-
<img src="https://img.shields.io/badge/tests-
|
|
14
|
-
<img src="https://img.shields.io/badge/
|
|
15
|
-
<img src="https://img.shields.io/badge/
|
|
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
|
|
547
|
-
âš suites
|
|
548
|
-
âš pass
|
|
553
|
+
âš tests 133
|
|
554
|
+
âš suites 24
|
|
555
|
+
âš pass 133
|
|
549
556
|
âš fail 0
|
|
550
|
-
âš duration_ms
|
|
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
|
---
|
package/docs/THREAT_TAXONOMY.md
CHANGED
|
@@ -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.
|
|
4
|
-
"description": "Agent security scanner + runtime guard â
|
|
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} — Zero dependencies. Zero compromises. đĄď¸<br>
|
|
233
|
+
Built by <a href="https://github.com/koatora20">Guava đ & 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 };
|