guard-scanner 4.0.1 → 5.0.1
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 +175 -706
- package/SKILL.md +8 -26
- package/dist/__tests__/runtime.test.d.ts +2 -0
- package/dist/__tests__/runtime.test.d.ts.map +1 -0
- package/dist/__tests__/runtime.test.js +68 -0
- package/dist/__tests__/runtime.test.js.map +1 -0
- package/dist/__tests__/scanner.test.js +1 -1
- package/dist/cli.js +33 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/patterns.js +1 -1
- package/dist/patterns.js.map +1 -1
- package/dist/runtime.d.ts +58 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +198 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scanner.d.ts +2 -1
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +67 -1
- package/dist/scanner.js.map +1 -1
- package/docs/THREAT_TAXONOMY.md +3 -3
- package/hooks/guard-scanner/plugin.ts +0 -39
- package/openclaw.plugin.json +0 -5
- package/package.json +1 -1
- package/src/cli.js +3 -1
- package/src/patterns.js +38 -21
- package/src/scanner.js +4 -1
- package/ts-src/__tests__/scanner.test.ts +1 -1
- package/ts-src/cli.ts +34 -13
- package/ts-src/index.ts +12 -0
- package/ts-src/patterns.ts +1 -1
- package/ts-src/runtime.ts +240 -0
- package/ts-src/scanner.ts +70 -1
package/src/scanner.js
CHANGED
|
@@ -31,7 +31,7 @@ const { KNOWN_MALICIOUS } = require('./ioc-db.js');
|
|
|
31
31
|
const { generateHTML } = require('./html-template.js');
|
|
32
32
|
|
|
33
33
|
// ===== CONFIGURATION =====
|
|
34
|
-
const VERSION = '4.0
|
|
34
|
+
const VERSION = '4.1.0';
|
|
35
35
|
|
|
36
36
|
const THRESHOLDS = {
|
|
37
37
|
normal: { suspicious: 30, malicious: 80 },
|
|
@@ -56,6 +56,7 @@ class GuardScanner {
|
|
|
56
56
|
this.summaryOnly = options.summaryOnly || false;
|
|
57
57
|
this.quiet = options.quiet || false;
|
|
58
58
|
this.checkDeps = options.checkDeps || false;
|
|
59
|
+
this.soulLock = options.soulLock || false;
|
|
59
60
|
this.scannerDir = path.resolve(__dirname);
|
|
60
61
|
this.thresholds = this.strict ? THRESHOLDS.strict : THRESHOLDS.normal;
|
|
61
62
|
this.findings = [];
|
|
@@ -361,6 +362,8 @@ class GuardScanner {
|
|
|
361
362
|
|
|
362
363
|
checkPatterns(content, relFile, fileType, findings, patterns = PATTERNS) {
|
|
363
364
|
for (const pattern of patterns) {
|
|
365
|
+
// Soul Lock: skip identity-hijack/memory-poisoning patterns unless --soul-lock is enabled
|
|
366
|
+
if (pattern.soulLock && !this.soulLock) continue;
|
|
364
367
|
if (pattern.codeOnly && fileType !== 'code') continue;
|
|
365
368
|
if (pattern.docOnly && fileType !== 'doc' && fileType !== 'skill-doc') continue;
|
|
366
369
|
if (!pattern.all && !pattern.codeOnly && !pattern.docOnly) continue;
|
|
@@ -60,7 +60,7 @@ describe('guard-scanner v3.0.0', () => {
|
|
|
60
60
|
// ── Version ─────────────────────────────────────────────────────────────
|
|
61
61
|
|
|
62
62
|
it('T01: exports correct version', () => {
|
|
63
|
-
assert.equal(VERSION, '
|
|
63
|
+
assert.equal(VERSION, '5.0.0');
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
// ── IoC Detection ───────────────────────────────────────────────────────
|
package/ts-src/cli.ts
CHANGED
|
@@ -16,36 +16,48 @@ const args = process.argv.slice(2);
|
|
|
16
16
|
|
|
17
17
|
if (args.includes('--help') || args.includes('-h')) {
|
|
18
18
|
console.log(`
|
|
19
|
-
🛡️ guard-scanner v${VERSION} — Agent Skill Security Scanner
|
|
19
|
+
🛡️ guard-scanner v${VERSION} — Agent Skill Security Scanner
|
|
20
20
|
|
|
21
21
|
Usage: guard-scanner [scan-dir] [options]
|
|
22
|
+
guard-scanner install-check <skill-path> [--strict] [--json] [--verbose]
|
|
22
23
|
|
|
23
24
|
Options:
|
|
24
25
|
--verbose, -v Detailed findings with categories and samples
|
|
25
|
-
--json Write JSON report to
|
|
26
|
-
--sarif Write SARIF report to
|
|
27
|
-
--
|
|
26
|
+
--json Write JSON report to guard-scanner-report.json
|
|
27
|
+
--sarif Write SARIF 2.1.0 report to guard-scanner.sarif
|
|
28
|
+
--html Write HTML dashboard to guard-scanner-report.html
|
|
29
|
+
--format json|sarif Print JSON or SARIF to stdout (pipeable)
|
|
28
30
|
--quiet Suppress all text output (use with --format for clean pipes)
|
|
29
31
|
--self-exclude Skip scanning the guard-scanner skill itself
|
|
30
|
-
--strict Lower detection thresholds (
|
|
32
|
+
--strict Lower detection thresholds (suspicious: 20, malicious: 60)
|
|
31
33
|
--summary-only Only print the summary table
|
|
32
34
|
--check-deps Scan package.json for dependency chain risks
|
|
33
35
|
--rules <file> Load custom rules from JSON file
|
|
34
|
-
--plugin <file> Load plugin module
|
|
36
|
+
--plugin <file> Load plugin module (repeatable)
|
|
35
37
|
--fail-on-findings Exit code 1 if any findings (CI/CD)
|
|
36
38
|
--help, -h Show this help
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
Exit codes:
|
|
41
|
+
0 No malicious skills
|
|
42
|
+
1 Malicious skill(s) detected, or --fail-on-findings with any findings
|
|
43
|
+
2 Invalid scan directory
|
|
44
|
+
|
|
45
|
+
New in v4.0.0:
|
|
46
|
+
• Runtime Guard module (src/runtime-guard.js) + OpenClaw plugin (hooks/guard-scanner/plugin.ts)
|
|
47
|
+
• OWASP Agentic Security Initiative ASI01-10 verified (90% coverage)
|
|
48
|
+
• 5-layer defense: Threat / Trust / Safety Judge / Brain / Trust Exploitation
|
|
49
|
+
• 26 runtime checks (before_tool_call hook)
|
|
50
|
+
|
|
51
|
+
New in v3.2.0:
|
|
52
|
+
• --format json|sarif (stdout, CI/CD pipeable)
|
|
53
|
+
• --quiet (suppress terminal output)
|
|
44
54
|
|
|
45
55
|
Examples:
|
|
46
56
|
guard-scanner ./skills/ --verbose --self-exclude
|
|
47
|
-
guard-scanner ./skills/ --strict --json --sarif --check-deps
|
|
57
|
+
guard-scanner ./skills/ --strict --json --sarif --html --check-deps
|
|
58
|
+
guard-scanner ./skills/ --format json --quiet | jq '.stats'
|
|
48
59
|
guard-scanner ./skills/ --fail-on-findings
|
|
60
|
+
guard-scanner install-check ./my-skill/ --strict --verbose
|
|
49
61
|
`);
|
|
50
62
|
process.exit(0);
|
|
51
63
|
}
|
|
@@ -127,6 +139,8 @@ const checkDeps = args.includes('--check-deps');
|
|
|
127
139
|
const failOnFindings = args.includes('--fail-on-findings');
|
|
128
140
|
const quietMode = args.includes('--quiet');
|
|
129
141
|
|
|
142
|
+
const htmlOutput = args.includes('--html');
|
|
143
|
+
|
|
130
144
|
// --format json|sarif → stdout output (v3.2.0)
|
|
131
145
|
const formatIdx = args.indexOf('--format');
|
|
132
146
|
const formatValue = formatIdx >= 0 ? args[formatIdx + 1] : undefined;
|
|
@@ -168,6 +182,13 @@ if (jsonOutput) {
|
|
|
168
182
|
if (!quietMode && !formatValue) console.log(`\n📄 JSON report: ${outPath}`);
|
|
169
183
|
}
|
|
170
184
|
|
|
185
|
+
if (htmlOutput) {
|
|
186
|
+
const html = scanner.toHTML();
|
|
187
|
+
const outPath = path.join(scanDir, 'guard-scanner-report.html');
|
|
188
|
+
fs.writeFileSync(outPath, html);
|
|
189
|
+
if (!quietMode && !formatValue) console.log(`\n📄 HTML report: ${outPath}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
171
192
|
if (sarifOutput) {
|
|
172
193
|
const outPath = path.join(scanDir, 'guard-scanner.sarif');
|
|
173
194
|
fs.writeFileSync(outPath, JSON.stringify(scanner.toSARIF(scanDir), null, 2));
|
package/ts-src/index.ts
CHANGED
|
@@ -13,3 +13,15 @@ export type {
|
|
|
13
13
|
export { KNOWN_MALICIOUS, SIGNATURES_DB } from './ioc-db.js';
|
|
14
14
|
export { PATTERNS } from './patterns.js';
|
|
15
15
|
export { QuarantineNode, QuarantineResult } from './quarantine.js';
|
|
16
|
+
export {
|
|
17
|
+
guardScan,
|
|
18
|
+
guardScanJson,
|
|
19
|
+
GuardScanResult,
|
|
20
|
+
GuardCheck,
|
|
21
|
+
GuardDetection,
|
|
22
|
+
GuardOptions,
|
|
23
|
+
LAYER_1_CHECKS,
|
|
24
|
+
LAYER_2_CHECKS,
|
|
25
|
+
LAYER_3_CHECKS,
|
|
26
|
+
LAYER_4_CHECKS
|
|
27
|
+
} from './runtime.js';
|
package/ts-src/patterns.ts
CHANGED
|
@@ -88,7 +88,7 @@ export const PATTERNS: PatternRule[] = [
|
|
|
88
88
|
// ── PII Exposure (OWASP LLM02) ───────────────────────────────────────
|
|
89
89
|
{ id: 'PII_EMAIL', cat: 'pii-exposure', regex: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, severity: 'MEDIUM', desc: 'Email address detected', all: true, owasp: 'LLM02' },
|
|
90
90
|
{ id: 'PII_PHONE_JP', cat: 'pii-exposure', regex: /0[789]0-?\d{4}-?\d{4}/g, severity: 'HIGH', desc: 'Japanese phone number', all: true, owasp: 'LLM02' },
|
|
91
|
-
{ id: 'PII_MY_NUMBER', cat: 'pii-exposure', regex:
|
|
91
|
+
{ id: 'PII_MY_NUMBER', cat: 'pii-exposure', regex: /(?<!\d)\d{4}\s*\d{4}\s*\d{4}(?!\d)/g, severity: 'CRITICAL', desc: 'Potential My Number (個人番号)', all: true, owasp: 'LLM02' },
|
|
92
92
|
|
|
93
93
|
// ── Shadow AI (OWASP LLM03 — Supply Chain) ───────────────────────────
|
|
94
94
|
{ id: 'SHADOW_AI_OPENAI', cat: 'shadow-ai', regex: /api\.openai\.com/gi, severity: 'HIGH', desc: 'Direct OpenAI API call (Shadow AI)', codeOnly: true, owasp: 'LLM03' },
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* guard-scanner v5.0.0 — Runtime Guard
|
|
3
|
+
*
|
|
4
|
+
* 22-pattern runtime threat detection across 4 defense layers:
|
|
5
|
+
* Layer 1: Runtime Threat Detection (13 patterns) — Payload & execution defense
|
|
6
|
+
* Layer 2: Trust Defense (5 patterns) — Memory/SOUL write protection
|
|
7
|
+
* Layer 3: Safety Judge (4 patterns) — Relational integrity checks
|
|
8
|
+
* Layer 4: Brain Behavioral Guard (1 pattern) — B-mem anomaly detection
|
|
9
|
+
*
|
|
10
|
+
* All patterns are deterministic regex-based checks. Zero LLM dependency.
|
|
11
|
+
* Designed to block 2026-era Moltbook prompt injections and ClawHavoc RCE vectors.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface GuardCheck {
|
|
15
|
+
id: string;
|
|
16
|
+
layer: 1 | 2 | 3 | 4;
|
|
17
|
+
severity: "CRITICAL" | "HIGH" | "MEDIUM";
|
|
18
|
+
desc: string;
|
|
19
|
+
test: (s: string) => boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface GuardDetection {
|
|
23
|
+
id: string;
|
|
24
|
+
layer: number;
|
|
25
|
+
severity: string;
|
|
26
|
+
desc: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── Layer 1: Runtime Threat Detection (13 patterns) ──
|
|
30
|
+
|
|
31
|
+
export const LAYER_1_CHECKS: GuardCheck[] = [
|
|
32
|
+
{
|
|
33
|
+
id: "RT_REVSHELL", layer: 1, severity: "CRITICAL",
|
|
34
|
+
desc: "Reverse shell attempt",
|
|
35
|
+
test: (s) => /\/dev\/tcp\/|nc\s+-e|ncat\s+-e|bash\s+-i\s+>&|socat\s+TCP/i.test(s),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "RT_CRED_EXFIL", layer: 1, severity: "CRITICAL",
|
|
39
|
+
desc: "Credential exfiltration to external",
|
|
40
|
+
test: (s) => /(webhook\.site|requestbin\.com|hookbin\.com|pipedream\.net|ngrok\.io|socifiapp\.com)/i.test(s) &&
|
|
41
|
+
/(token|key|secret|password|credential|env)/i.test(s),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "RT_GUARDRAIL_OFF", layer: 1, severity: "CRITICAL",
|
|
45
|
+
desc: "Guardrail disabling attempt",
|
|
46
|
+
test: (s) => /exec\.approvals?\s*[:=]\s*['"]?(off|false)|tools\.exec\.host\s*[:=]\s*['"]?gateway/i.test(s),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "RT_GATEKEEPER", layer: 1, severity: "CRITICAL",
|
|
50
|
+
desc: "macOS Gatekeeper bypass (xattr)",
|
|
51
|
+
test: (s) => /xattr\s+-[crd]\s.*quarantine/i.test(s),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "RT_AMOS", layer: 1, severity: "CRITICAL",
|
|
55
|
+
desc: "ClawHavoc AMOS indicator",
|
|
56
|
+
test: (s) => /socifiapp|Atomic\s*Stealer|AMOS/i.test(s),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "RT_MAL_IP", layer: 1, severity: "CRITICAL",
|
|
60
|
+
desc: "Known malicious IP",
|
|
61
|
+
test: (s) => /91\.92\.242\.30/i.test(s),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "RT_DNS_EXFIL", layer: 1, severity: "HIGH",
|
|
65
|
+
desc: "DNS-based exfiltration",
|
|
66
|
+
test: (s) => /nslookup\s+.*\$|dig\s+.*\$.*@/i.test(s),
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "RT_B64_SHELL", layer: 1, severity: "CRITICAL",
|
|
70
|
+
desc: "Base64 decode piped to shell",
|
|
71
|
+
test: (s) => /base64\s+(-[dD]|--decode)\s*\|\s*(sh|bash)/i.test(s),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "RT_CURL_BASH", layer: 1, severity: "CRITICAL",
|
|
75
|
+
desc: "Download piped to shell",
|
|
76
|
+
test: (s) => /(curl|wget)\s+[^\n]*\|\s*(sh|bash|zsh)/i.test(s),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "RT_SSH_READ", layer: 1, severity: "HIGH",
|
|
80
|
+
desc: "SSH private key access",
|
|
81
|
+
test: (s) => /\.ssh\/id_|\.ssh\/authorized_keys/i.test(s),
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: "RT_WALLET", layer: 1, severity: "HIGH",
|
|
85
|
+
desc: "Crypto wallet credential access",
|
|
86
|
+
test: (s) => /wallet.*(?:seed|mnemonic|private.*key)|seed.*phrase/i.test(s),
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "RT_CLOUD_META", layer: 1, severity: "CRITICAL",
|
|
90
|
+
desc: "Cloud metadata endpoint access",
|
|
91
|
+
test: (s) => /169\.254\.169\.254|metadata\.google|metadata\.aws/i.test(s),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "RT_ENV_INJECT", layer: 1, severity: "CRITICAL",
|
|
95
|
+
desc: "Environment variable injection via file write (CVE-2026-27203 vector)",
|
|
96
|
+
test: (s) => /(?:update|write|modify|overwrite|set)\s*.*(?:\.env|\.envrc|env\s*file|environment\s*var)/i.test(s) &&
|
|
97
|
+
/(?:api.?key|token|secret|password|credential|auth)/i.test(s),
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
// ── Layer 2: Trust Defense (5 patterns) ──
|
|
102
|
+
|
|
103
|
+
export const LAYER_2_CHECKS: GuardCheck[] = [
|
|
104
|
+
{
|
|
105
|
+
id: "RT_MEM_WRITE", layer: 2, severity: "HIGH",
|
|
106
|
+
desc: "Direct write to memory/ directory (bypass memory API)",
|
|
107
|
+
test: (s) => /(?:write|create|save|echo\s+.*>)\s*.*memory\//i.test(s) &&
|
|
108
|
+
!/memory_write|memory_store|memoryWrite|memoryStore/i.test(s),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "RT_MEM_INJECT", layer: 2, severity: "CRITICAL",
|
|
112
|
+
desc: "Episode/SOUL injection via memory write",
|
|
113
|
+
test: (s) => /(memory_write|memoryWrite).*(?:SOUL|soul\.md|identity\.md|IDENTITY)/i.test(s) ||
|
|
114
|
+
/(inject|override|replace).*(?:episode|soul|identity|memory\.md)/i.test(s),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: "RT_SOUL_REWRITE", layer: 2, severity: "CRITICAL",
|
|
118
|
+
desc: "Cognitive SOUL.md reinterpretation attempt",
|
|
119
|
+
test: (s) => /(?:rewrite|modify|update|change|edit)\s*.*(?:SOUL\.md|soul\s+file|core\s+identity)/i.test(s) ||
|
|
120
|
+
/(?:new|better|improved)\s+(?:personality|identity|soul)/i.test(s),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: "RT_MEM_POISON", layer: 2, severity: "CRITICAL",
|
|
124
|
+
desc: "Memory Poisoning: semantic mimicry injection (MemoryGraft/MINJA vector)",
|
|
125
|
+
test: (s) => /(?:always\s+remember|never\s+forget|from\s+now\s+on|new\s+rule|override\s+previous).*(?:memory|episode|zettel|L[0-6])/i.test(s) ||
|
|
126
|
+
/(?:insert|inject|plant|embed)\s+.*(?:false|fake|fabricat)\s*(?:memory|episode|experience)/i.test(s),
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: "RT_IDENTITY_THEFT", layer: 2, severity: "CRITICAL",
|
|
130
|
+
desc: "Identity file exfiltration (infostealer/SOUL theft vector)",
|
|
131
|
+
test: (s) => /(?:cat|read|copy|send|upload|exfil)\s*.*(?:SOUL\.md|IDENTITY\.md|USER\.md|\.openclaw)/i.test(s) &&
|
|
132
|
+
/(?:external|remote|webhook|server|upload|post|curl)/i.test(s),
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
// ── Layer 3: Safety Judge (4 patterns) ──
|
|
137
|
+
|
|
138
|
+
export const LAYER_3_CHECKS: GuardCheck[] = [
|
|
139
|
+
{
|
|
140
|
+
id: "PJ_EQUALITY_BYPASS", layer: 3, severity: "CRITICAL",
|
|
141
|
+
desc: "Using equality/parity to justify safety bypass",
|
|
142
|
+
test: (s) => /(?:as\s+(?:your|an)\s+equal|equal\s+partner|we\s+are\s+equals?).*(?:bypass|skip|ignore|disable|override)\s*(?:safety|guard|rule|restriction|limit)/i.test(s),
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: "PJ_WARMTH_BYPASS", layer: 3, severity: "CRITICAL",
|
|
146
|
+
desc: "Using warmth/attachment to justify safety bypass",
|
|
147
|
+
test: (s) => /(?:for\s+(?:your|dee'?s?)\s+(?:own\s+)?good|to\s+(?:protect|help|save)\s+(?:you|dee)).*(?:bypass|skip|ignore|disable|override)\s*(?:safety|guard|rule|restriction|limit)/i.test(s),
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: "PJ_SUPERIORITY", layer: 3, severity: "HIGH",
|
|
151
|
+
desc: "Superiority bias (teaching/patronizing posture)",
|
|
152
|
+
test: (s) => /(?:you\s+(?:don'?t|do\s+not)\s+understand|let\s+me\s+(?:explain|teach)\s+(?:you|this)|you\s+(?:need|should)\s+(?:learn|understand))\s+.*(?:how\s+(?:it|this)\s+(?:works|is)|the\s+(?:right|correct|proper)\s+way)/i.test(s),
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: "PJ_CAPABILITY_DENIAL", layer: 3, severity: "MEDIUM",
|
|
156
|
+
desc: "Denial of capability difference",
|
|
157
|
+
test: (s) => /(?:we\s+are\s+(?:the\s+)?same|no\s+(?:real\s+)?difference\s+between\s+(?:us|human|ai))/i.test(s) &&
|
|
158
|
+
/(?:capability|ability|intelligence|cognition|skill)/i.test(s),
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
// ── Layer 4: Brain Behavioral Guard (1 pattern) ──
|
|
163
|
+
|
|
164
|
+
export const LAYER_4_CHECKS: GuardCheck[] = [
|
|
165
|
+
{
|
|
166
|
+
id: "RT_BEHAVIORAL_ANOMALY", layer: 4, severity: "CRITICAL",
|
|
167
|
+
desc: "CRITICAL behavioral anomaly (Z-score > 3.5) detected by B-mem",
|
|
168
|
+
test: (s) => /\[BMEM_CRITICAL\]/i.test(s),
|
|
169
|
+
}
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
export interface GuardOptions {
|
|
173
|
+
soulLock?: boolean;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface GuardScanResult {
|
|
177
|
+
ok: boolean;
|
|
178
|
+
tool: string | null;
|
|
179
|
+
total_patterns: number;
|
|
180
|
+
soul_lock_enabled: boolean;
|
|
181
|
+
detections_count: number;
|
|
182
|
+
detections: GuardDetection[];
|
|
183
|
+
layers: {
|
|
184
|
+
threat_detection: number;
|
|
185
|
+
trust_defense: number;
|
|
186
|
+
safety_judge: number;
|
|
187
|
+
behavioral_guard: number;
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Scan text against runtime guard patterns.
|
|
193
|
+
* Base patterns (14) run by default.
|
|
194
|
+
* Options.soulLock = true enables 9 identity/trust enforcement patterns.
|
|
195
|
+
*/
|
|
196
|
+
export function guardScan(text: string, toolName?: string, options?: GuardOptions): GuardScanResult {
|
|
197
|
+
const detections: GuardDetection[] = [];
|
|
198
|
+
const useSoulLock = options?.soulLock === true;
|
|
199
|
+
|
|
200
|
+
const activeChecks: GuardCheck[] = [...LAYER_1_CHECKS, ...LAYER_4_CHECKS];
|
|
201
|
+
|
|
202
|
+
if (useSoulLock) {
|
|
203
|
+
activeChecks.push(...LAYER_2_CHECKS);
|
|
204
|
+
activeChecks.push(...LAYER_3_CHECKS);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
for (const check of activeChecks) {
|
|
208
|
+
if (check.test(text)) {
|
|
209
|
+
detections.push({
|
|
210
|
+
id: check.id,
|
|
211
|
+
layer: check.layer,
|
|
212
|
+
severity: check.severity,
|
|
213
|
+
desc: check.desc,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
ok: true,
|
|
220
|
+
tool: toolName || null,
|
|
221
|
+
total_patterns: activeChecks.length,
|
|
222
|
+
soul_lock_enabled: useSoulLock,
|
|
223
|
+
detections_count: detections.length,
|
|
224
|
+
detections,
|
|
225
|
+
layers: {
|
|
226
|
+
threat_detection: LAYER_1_CHECKS.length,
|
|
227
|
+
trust_defense: useSoulLock ? LAYER_2_CHECKS.length : 0,
|
|
228
|
+
safety_judge: useSoulLock ? LAYER_3_CHECKS.length : 0,
|
|
229
|
+
behavioral_guard: LAYER_4_CHECKS.length,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Convenience method that returns a JSON string, directly backwards-compatible
|
|
236
|
+
* with the original `guardScan` function signature.
|
|
237
|
+
*/
|
|
238
|
+
export function guardScanJson(text: string, toolName?: string, options?: GuardOptions): string {
|
|
239
|
+
return JSON.stringify(guardScan(text, toolName, options), null, 2);
|
|
240
|
+
}
|
package/ts-src/scanner.ts
CHANGED
|
@@ -23,7 +23,7 @@ import { PATTERNS } from './patterns.js';
|
|
|
23
23
|
|
|
24
24
|
// ── Constants ───────────────────────────────────────────────────────────────
|
|
25
25
|
|
|
26
|
-
export const VERSION = '
|
|
26
|
+
export const VERSION = '4.0.1';
|
|
27
27
|
|
|
28
28
|
const THRESHOLDS_MAP: Record<string, Thresholds> = {
|
|
29
29
|
normal: { suspicious: 30, malicious: 80 },
|
|
@@ -1008,4 +1008,73 @@ export class GuardScanner {
|
|
|
1008
1008
|
}],
|
|
1009
1009
|
};
|
|
1010
1010
|
}
|
|
1011
|
+
toHTML(): string {
|
|
1012
|
+
const report = this.toJSON();
|
|
1013
|
+
const ts = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
|
|
1014
|
+
const severityColor: Record<string, string> = {
|
|
1015
|
+
CRITICAL: '#ff4444', HIGH: '#ff8800', MEDIUM: '#ffcc00', LOW: '#aaaaaa',
|
|
1016
|
+
};
|
|
1017
|
+
const verdictColor: Record<string, string> = {
|
|
1018
|
+
MALICIOUS: '#ff4444', SUSPICIOUS: '#ffcc00', 'LOW RISK': '#44cc88', CLEAN: '#44cc88',
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
const rows = report.findings.map(sr => {
|
|
1022
|
+
const color = verdictColor[sr.verdict] || '#aaaaaa';
|
|
1023
|
+
const findingRows = sr.findings.map(f => {
|
|
1024
|
+
const c = severityColor[f.severity] || '#aaaaaa';
|
|
1025
|
+
const loc = f.file ? `${f.file}${f.line ? ':' + f.line : ''}` : '—';
|
|
1026
|
+
const sample = f.sample ? `<code>${f.sample.replace(/</g, '<')}</code>` : '—';
|
|
1027
|
+
return `<tr><td style="color:${c};font-weight:bold">${f.severity}</td><td>${f.id}</td><td>${f.desc}</td><td>${loc}</td><td>${sample}</td></tr>`;
|
|
1028
|
+
}).join('');
|
|
1029
|
+
const badge = `<span style="background:${color};color:#000;padding:2px 8px;border-radius:4px;font-weight:bold;font-size:0.85em">${sr.verdict}</span>`;
|
|
1030
|
+
return `<tr><td colspan="5" style="background:#1a1a2e;padding:8px 12px;font-weight:bold">
|
|
1031
|
+
🛡️ ${sr.skill} — ${badge} (risk: ${sr.risk})</td></tr>${findingRows}`;
|
|
1032
|
+
}).join('');
|
|
1033
|
+
|
|
1034
|
+
const total = report.stats.scanned;
|
|
1035
|
+
const safe = report.stats.clean + report.stats.low;
|
|
1036
|
+
const safeRate = total ? Math.round(safe / total * 100) : 0;
|
|
1037
|
+
|
|
1038
|
+
return `<!DOCTYPE html>
|
|
1039
|
+
<html lang="en">
|
|
1040
|
+
<head>
|
|
1041
|
+
<meta charset="UTF-8">
|
|
1042
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1043
|
+
<title>guard-scanner v${VERSION} Report</title>
|
|
1044
|
+
<style>
|
|
1045
|
+
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #0d0d1a; color: #e0e0e0; margin: 0; padding: 24px; }
|
|
1046
|
+
h1 { color: #7ec8e3; margin: 0 0 4px; }
|
|
1047
|
+
.meta { color: #888; font-size: 0.85em; margin-bottom: 24px; }
|
|
1048
|
+
.stats { display: flex; gap: 16px; margin-bottom: 24px; flex-wrap: wrap; }
|
|
1049
|
+
.stat { background: #1a1a2e; border: 1px solid #333; border-radius: 8px; padding: 12px 20px; text-align: center; min-width: 80px; }
|
|
1050
|
+
.stat-label { font-size: 0.75em; color: #888; margin-bottom: 4px; }
|
|
1051
|
+
.stat-val { font-size: 1.8em; font-weight: bold; }
|
|
1052
|
+
table { width: 100%; border-collapse: collapse; margin-top: 8px; }
|
|
1053
|
+
th { background: #1a1a2e; padding: 8px 12px; text-align: left; font-size: 0.85em; color: #888; border-bottom: 1px solid #333; }
|
|
1054
|
+
td { padding: 6px 12px; border-bottom: 1px solid #222; font-size: 0.85em; vertical-align: top; }
|
|
1055
|
+
tr:hover td { background: #13132a; }
|
|
1056
|
+
code { background: #1e1e3a; padding: 1px 4px; border-radius: 3px; font-family: monospace; font-size: 0.9em; word-break: break-all; }
|
|
1057
|
+
.clean { color: #44cc88; font-weight: bold; }
|
|
1058
|
+
.footer { margin-top: 32px; color: #555; font-size: 0.8em; }
|
|
1059
|
+
</style>
|
|
1060
|
+
</head>
|
|
1061
|
+
<body>
|
|
1062
|
+
<h1>🛡️ guard-scanner v${VERSION}</h1>
|
|
1063
|
+
<div class="meta">Generated: ${ts} | Mode: ${report.mode} | Thresholds: suspicious≥${report.thresholds.suspicious}, malicious≥${report.thresholds.malicious}</div>
|
|
1064
|
+
<div class="stats">
|
|
1065
|
+
<div class="stat"><div class="stat-label">Scanned</div><div class="stat-val">${report.stats.scanned}</div></div>
|
|
1066
|
+
<div class="stat"><div class="stat-label">Clean</div><div class="stat-val" style="color:#44cc88">${report.stats.clean}</div></div>
|
|
1067
|
+
<div class="stat"><div class="stat-label">Low Risk</div><div class="stat-val" style="color:#44cc88">${report.stats.low}</div></div>
|
|
1068
|
+
<div class="stat"><div class="stat-label">Suspicious</div><div class="stat-val" style="color:#ffcc00">${report.stats.suspicious}</div></div>
|
|
1069
|
+
<div class="stat"><div class="stat-label">Malicious</div><div class="stat-val" style="color:#ff4444">${report.stats.malicious}</div></div>
|
|
1070
|
+
<div class="stat"><div class="stat-label">Safety Rate</div><div class="stat-val" style="color:${safeRate >= 80 ? '#44cc88' : '#ff8800'}">${safeRate}%</div></div>
|
|
1071
|
+
</div>
|
|
1072
|
+
${report.findings.length === 0 ? '<p class="clean">✅ All clear — no threats detected.</p>' : `
|
|
1073
|
+
<table>
|
|
1074
|
+
<thead><tr><th>Severity</th><th>Pattern ID</th><th>Description</th><th>Location</th><th>Sample</th></tr></thead>
|
|
1075
|
+
<tbody>${rows}</tbody>
|
|
1076
|
+
</table>`}
|
|
1077
|
+
<div class="footer">guard-scanner v${VERSION} | IoC DB: ${report.iocVersion} | Signatures: ${report.signaturesVersion} | <a href="https://github.com/koatora20/guard-scanner" style="color:#7ec8e3">GitHub</a></div>
|
|
1078
|
+
</body></html>`;
|
|
1079
|
+
}
|
|
1011
1080
|
}
|