@guava-parity/guard-scanner 13.0.0 → 16.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.
- package/README.md +170 -215
- package/README_ja.md +252 -0
- package/SECURITY.md +12 -4
- package/SKILL.md +148 -57
- package/dist/cli.cjs +5997 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +6003 -0
- package/dist/index.cjs +4825 -0
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.mjs +4798 -0
- package/dist/mcp-server.cjs +4756 -0
- package/dist/mcp-server.d.mts +1 -0
- package/dist/mcp-server.d.ts +1 -0
- package/dist/mcp-server.mjs +4767 -0
- package/dist/openclaw-plugin.cjs +4863 -0
- package/dist/openclaw-plugin.d.mts +11 -0
- package/dist/openclaw-plugin.d.ts +11 -0
- package/dist/openclaw-plugin.mjs +4854 -0
- package/dist/types.cjs +18 -0
- package/dist/types.d.mts +215 -0
- package/dist/types.d.ts +215 -0
- package/dist/types.mjs +1 -0
- package/docs/EVIDENCE_DRIVEN.md +182 -0
- package/docs/banner.png +0 -0
- package/docs/data/benchmark-ledger.json +1428 -0
- package/docs/data/corpus-metrics.json +11 -0
- package/docs/data/fp-ledger.json +18 -0
- package/docs/data/latest.json +25837 -2481
- package/docs/data/quality-contract.json +36 -0
- package/docs/generated/npm-audit-20260312.json +96 -0
- package/docs/generated/openclaw-upstream-status.json +25 -0
- package/docs/glossary.md +46 -0
- package/docs/index.html +1085 -496
- package/docs/logo.png +0 -0
- package/docs/openclaw-compatibility-audit.md +45 -0
- package/docs/openclaw-continuous-compatibility-plan.md +37 -0
- package/docs/rules/a2a-contagion.md +68 -0
- package/docs/rules/advanced-exfil.md +52 -0
- package/docs/rules/agent-protocol.md +108 -0
- package/docs/rules/api-abuse.md +68 -0
- package/docs/rules/autonomous-risk.md +92 -0
- package/docs/rules/config-impact.md +132 -0
- package/docs/rules/credential-handling.md +100 -0
- package/docs/rules/cve-patterns.md +332 -0
- package/docs/rules/data-exposure.md +84 -0
- package/docs/rules/exfiltration.md +36 -0
- package/docs/rules/financial-access.md +84 -0
- package/docs/rules/identity-hijack.md +140 -0
- package/docs/rules/inference-manipulation.md +60 -0
- package/docs/rules/leaky-skills.md +52 -0
- package/docs/rules/malicious-code.md +108 -0
- package/docs/rules/mcp-security.md +148 -0
- package/docs/rules/memory-poisoning.md +84 -0
- package/docs/rules/model-poisoning.md +44 -0
- package/docs/rules/obfuscation.md +60 -0
- package/docs/rules/persistence.md +108 -0
- package/docs/rules/pii-exposure.md +116 -0
- package/docs/rules/prompt-injection.md +148 -0
- package/docs/rules/prompt-worm.md +44 -0
- package/docs/rules/safeguard-bypass.md +44 -0
- package/docs/rules/sandbox-escape.md +100 -0
- package/docs/rules/secret-detection.md +44 -0
- package/docs/rules/supply-chain-v2.md +92 -0
- package/docs/rules/suspicious-download.md +60 -0
- package/docs/rules/trust-boundary.md +76 -0
- package/docs/rules/trust-exploitation.md +92 -0
- package/docs/rules/unverifiable-deps.md +84 -0
- package/docs/rules/vdb-injection.md +84 -0
- package/docs/security-vulnerability-report-20260312.md +53 -0
- package/docs/spec/PRD_V2_ARCHITECTURE.md +55 -0
- package/docs/spec/capabilities.json +174 -0
- package/docs/spec/finding.schema.json +104 -0
- package/docs/spec/integration-manifest.md +39 -0
- package/docs/spec/plugin-trust.json +11 -0
- package/docs/spec/sbom.json +33 -0
- package/docs/threat-model.md +65 -0
- package/docs/v13-architecture-manifest.md +55 -0
- package/hooks/context.ts +306 -0
- package/hooks/guard-scanner/plugin.ts +24 -1
- package/openclaw-plugin.mts +107 -0
- package/openclaw.plugin.json +30 -53
- package/package.json +66 -13
- package/src/asset-auditor.js +0 -508
- package/src/ci-reporter.js +0 -135
- package/src/cli.js +0 -294
- package/src/html-template.js +0 -239
- package/src/ioc-db.js +0 -54
- package/src/mcp-server.js +0 -702
- package/src/patterns.js +0 -611
- package/src/quarantine.js +0 -41
- package/src/runtime-guard.js +0 -346
- package/src/scanner.js +0 -1157
- package/src/vt-client.js +0 -202
- package/src/watcher.js +0 -170
package/src/cli.js
DELETED
|
@@ -1,294 +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
|
-
const { AssetAuditor, AUDIT_VERSION } = require('./asset-auditor.js');
|
|
35
|
-
const { VTClient } = require('./vt-client.js');
|
|
36
|
-
const { GuardWatcher } = require('./watcher.js');
|
|
37
|
-
const { CIReporter } = require('./ci-reporter.js');
|
|
38
|
-
|
|
39
|
-
const args = process.argv.slice(2);
|
|
40
|
-
|
|
41
|
-
if (args.includes('--version') || args.includes('-V')) {
|
|
42
|
-
console.log(`guard-scanner v${VERSION} (audit v${AUDIT_VERSION})`);
|
|
43
|
-
process.exit(0);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ── serve subcommand (MCP server) ────────────────────────────
|
|
47
|
-
if (args[0] === 'serve') {
|
|
48
|
-
const { startServer } = require('./mcp-server.js');
|
|
49
|
-
startServer();
|
|
50
|
-
// Server runs until stdin closes
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ── watch subcommand ──────────────────────────────────────────
|
|
54
|
-
if (args[0] === 'watch') {
|
|
55
|
-
const watchDir = args[1] || '.';
|
|
56
|
-
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
57
|
-
const strict = args.includes('--strict');
|
|
58
|
-
const soulLock = args.includes('--soul-lock');
|
|
59
|
-
|
|
60
|
-
const watcher = new GuardWatcher({ verbose, strict, soulLock });
|
|
61
|
-
|
|
62
|
-
process.on('SIGINT', () => {
|
|
63
|
-
const stats = watcher.stop();
|
|
64
|
-
console.log(`\n📊 Session: ${stats.scanCount} scans, ${stats.alertCount} alerts`);
|
|
65
|
-
process.exit(0);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
watcher.watch(watchDir);
|
|
69
|
-
// Keep process alive
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ── audit subcommand ──────────────────────────────────────────
|
|
73
|
-
if (args[0] === 'audit') {
|
|
74
|
-
const subCmd = args[1]; // npm | github | clawhub | all
|
|
75
|
-
const target = args[2]; // username or query
|
|
76
|
-
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
77
|
-
const formatIdx = args.indexOf('--format');
|
|
78
|
-
const formatValue = formatIdx >= 0 ? args[formatIdx + 1] : null;
|
|
79
|
-
const quiet = args.includes('--quiet') || !!formatValue;
|
|
80
|
-
|
|
81
|
-
if (!subCmd || subCmd === '--help' || subCmd === '-h') {
|
|
82
|
-
console.log(`
|
|
83
|
-
🛡️ guard-scanner audit v${AUDIT_VERSION} — Asset Audit Platform
|
|
84
|
-
|
|
85
|
-
Usage:
|
|
86
|
-
guard-scanner audit npm <username> Audit npm packages for leaks
|
|
87
|
-
guard-scanner audit github <username> Audit GitHub repos for exposure
|
|
88
|
-
guard-scanner audit clawhub <query> Audit ClawHub skills for threats
|
|
89
|
-
guard-scanner audit all <username> Run all audits
|
|
90
|
-
|
|
91
|
-
Options:
|
|
92
|
-
--verbose, -v Detailed alert output
|
|
93
|
-
--format json|sarif Print report to stdout (pipeable)
|
|
94
|
-
--quiet Suppress text output
|
|
95
|
-
--help, -h Show this help
|
|
96
|
-
|
|
97
|
-
Examples:
|
|
98
|
-
guard-scanner audit npm koatora20 --verbose
|
|
99
|
-
guard-scanner audit github koatora20 --format json
|
|
100
|
-
guard-scanner audit all koatora20 --verbose
|
|
101
|
-
`);
|
|
102
|
-
process.exit(0);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (!target && subCmd !== 'all') {
|
|
106
|
-
console.error(`❌ Usage: guard-scanner audit ${subCmd} <username>`);
|
|
107
|
-
process.exit(2);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const vtScan = args.includes('--vt-scan');
|
|
111
|
-
let vtClient = null;
|
|
112
|
-
if (vtScan) {
|
|
113
|
-
const vtKey = process.env.VT_API_KEY;
|
|
114
|
-
if (!vtKey) { console.error('❌ --vt-scan requires VT_API_KEY environment variable'); process.exit(2); }
|
|
115
|
-
vtClient = new VTClient(vtKey, { verbose });
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const auditor = new AssetAuditor({ verbose, format: formatValue, quiet, vtClient });
|
|
119
|
-
|
|
120
|
-
(async () => {
|
|
121
|
-
try {
|
|
122
|
-
if (subCmd === 'npm' || subCmd === 'all') {
|
|
123
|
-
await auditor.auditNpm(target);
|
|
124
|
-
}
|
|
125
|
-
if (subCmd === 'github' || subCmd === 'all') {
|
|
126
|
-
await auditor.auditGithub(target);
|
|
127
|
-
}
|
|
128
|
-
if (subCmd === 'clawhub' || subCmd === 'all') {
|
|
129
|
-
await auditor.auditClawHub(target);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!['npm', 'github', 'clawhub', 'all'].includes(subCmd)) {
|
|
133
|
-
console.error(`❌ Unknown audit target: ${subCmd}. Use: npm, github, clawhub, or all`);
|
|
134
|
-
process.exit(2);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Output
|
|
138
|
-
if (formatValue === 'json') {
|
|
139
|
-
process.stdout.write(JSON.stringify(auditor.toJSON(), null, 2) + '\n');
|
|
140
|
-
} else if (formatValue === 'sarif') {
|
|
141
|
-
process.stdout.write(JSON.stringify(auditor.toSARIF(), null, 2) + '\n');
|
|
142
|
-
} else {
|
|
143
|
-
auditor.printSummary();
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const verdict = auditor.getVerdict();
|
|
147
|
-
process.exit(verdict.exitCode);
|
|
148
|
-
} catch (e) {
|
|
149
|
-
console.error(`❌ Audit error: ${e.message}`);
|
|
150
|
-
process.exit(2);
|
|
151
|
-
}
|
|
152
|
-
})();
|
|
153
|
-
} else {
|
|
154
|
-
// ── existing scan logic (backward compatible) ─────────────────
|
|
155
|
-
|
|
156
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
157
|
-
console.log(`
|
|
158
|
-
🛡️ guard-scanner v${VERSION} — Agent Skill Security Scanner
|
|
159
|
-
|
|
160
|
-
Usage: guard-scanner [scan-dir] [options]
|
|
161
|
-
guard-scanner serve Start as MCP server (stdio)
|
|
162
|
-
guard-scanner audit <npm|github|clawhub|all> <username> [options]
|
|
163
|
-
|
|
164
|
-
Options:
|
|
165
|
-
--verbose, -v Detailed findings with categories and samples
|
|
166
|
-
--json Write JSON report to file
|
|
167
|
-
--sarif Write SARIF report to file (GitHub Code Scanning / CI/CD)
|
|
168
|
-
--html Write HTML report (visual dashboard)
|
|
169
|
-
--format json|sarif Print JSON or SARIF to stdout (pipeable, v3.2.0)
|
|
170
|
-
--quiet Suppress all text output (use with --format for clean pipes)
|
|
171
|
-
--self-exclude Skip scanning the guard-scanner skill itself
|
|
172
|
-
--strict Lower detection thresholds (more sensitive)
|
|
173
|
-
--summary-only Only print the summary table
|
|
174
|
-
--check-deps Scan package.json for dependency chain risks
|
|
175
|
-
--soul-lock Enable Soul Lock patterns (agent identity protection)
|
|
176
|
-
--rules <file> Load custom rules from JSON file
|
|
177
|
-
--plugin <file> Load plugin module (JS file exporting { name, patterns })
|
|
178
|
-
--fail-on-findings Exit code 1 if any findings (CI/CD)
|
|
179
|
-
--help, -h Show this help
|
|
180
|
-
--version, -V Show version
|
|
181
|
-
|
|
182
|
-
Custom Rules JSON Format:
|
|
183
|
-
[
|
|
184
|
-
{
|
|
185
|
-
"id": "CUSTOM_001",
|
|
186
|
-
"pattern": "dangerous_function\\\\(",
|
|
187
|
-
"flags": "gi",
|
|
188
|
-
"severity": "HIGH",
|
|
189
|
-
"cat": "malicious-code",
|
|
190
|
-
"desc": "Custom: dangerous function call",
|
|
191
|
-
"codeOnly": true
|
|
192
|
-
}
|
|
193
|
-
]
|
|
194
|
-
|
|
195
|
-
Plugin API:
|
|
196
|
-
// my-plugin.js
|
|
197
|
-
module.exports = {
|
|
198
|
-
name: 'my-plugin',
|
|
199
|
-
patterns: [
|
|
200
|
-
{ id: 'MY_01', cat: 'custom', regex: /pattern/g, severity: 'HIGH', desc: 'Description', all: true }
|
|
201
|
-
]
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
Examples:
|
|
205
|
-
guard-scanner ./skills/ --verbose --self-exclude
|
|
206
|
-
guard-scanner ./skills/ --strict --json --sarif --check-deps
|
|
207
|
-
guard-scanner ./skills/ --html --verbose --check-deps
|
|
208
|
-
guard-scanner ./skills/ --rules my-rules.json --fail-on-findings
|
|
209
|
-
guard-scanner ./skills/ --plugin ./my-plugin.js
|
|
210
|
-
`);
|
|
211
|
-
process.exit(0);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
215
|
-
const jsonOutput = args.includes('--json');
|
|
216
|
-
const sarifOutput = args.includes('--sarif');
|
|
217
|
-
const htmlOutput = args.includes('--html');
|
|
218
|
-
const selfExclude = args.includes('--self-exclude');
|
|
219
|
-
const strict = args.includes('--strict');
|
|
220
|
-
const summaryOnly = args.includes('--summary-only');
|
|
221
|
-
const checkDeps = args.includes('--check-deps');
|
|
222
|
-
const soulLock = args.includes('--soul-lock');
|
|
223
|
-
const failOnFindings = args.includes('--fail-on-findings');
|
|
224
|
-
const quietMode = args.includes('--quiet');
|
|
225
|
-
|
|
226
|
-
// --format json|sarif → stdout output (v3.2.0)
|
|
227
|
-
const formatIdx = args.indexOf('--format');
|
|
228
|
-
const formatValue = formatIdx >= 0 ? args[formatIdx + 1] : null;
|
|
229
|
-
|
|
230
|
-
const rulesIdx = args.indexOf('--rules');
|
|
231
|
-
const rulesFile = rulesIdx >= 0 ? args[rulesIdx + 1] : null;
|
|
232
|
-
|
|
233
|
-
// Collect plugins
|
|
234
|
-
const plugins = [];
|
|
235
|
-
let idx = 0;
|
|
236
|
-
while (idx < args.length) {
|
|
237
|
-
if (args[idx] === '--plugin' && args[idx + 1]) {
|
|
238
|
-
plugins.push(args[idx + 1]);
|
|
239
|
-
idx += 2;
|
|
240
|
-
} else {
|
|
241
|
-
idx++;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const scanDir = args.find(a =>
|
|
246
|
-
!a.startsWith('-') &&
|
|
247
|
-
a !== rulesFile &&
|
|
248
|
-
a !== formatValue &&
|
|
249
|
-
!plugins.includes(a)
|
|
250
|
-
) || process.cwd();
|
|
251
|
-
|
|
252
|
-
const scanner = new GuardScanner({
|
|
253
|
-
verbose, selfExclude, strict, summaryOnly, checkDeps, soulLock, rulesFile, plugins,
|
|
254
|
-
quiet: quietMode || !!formatValue,
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
scanner.scanDirectory(scanDir);
|
|
258
|
-
|
|
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
|
-
}
|
|
266
|
-
|
|
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
|
-
}
|
|
272
|
-
|
|
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
|
-
}
|
|
278
|
-
|
|
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'.`);
|
|
286
|
-
process.exit(2);
|
|
287
|
-
}
|
|
288
|
-
|
|
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
|
-
} // end else (scan mode)
|
package/src/html-template.js
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
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 };
|