guard-scanner 4.0.2 → 5.0.2
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 +174 -618
- 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/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/docs/THREAT_TAXONOMY.md +3 -3
- package/hooks/guard-scanner/handler.ts +5 -0
- package/hooks/guard-scanner/plugin.ts +0 -39
- package/openclaw.plugin.json +0 -5
- package/package.json +2 -2
- 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/index.ts +12 -0
- package/ts-src/patterns.ts +1 -1
- package/ts-src/runtime.ts +240 -0
package/src/cli.js
CHANGED
|
@@ -51,6 +51,7 @@ Options:
|
|
|
51
51
|
--strict Lower detection thresholds (more sensitive)
|
|
52
52
|
--summary-only Only print the summary table
|
|
53
53
|
--check-deps Scan package.json for dependency chain risks
|
|
54
|
+
--soul-lock Enable Soul Lock patterns (agent identity protection)
|
|
54
55
|
--rules <file> Load custom rules from JSON file
|
|
55
56
|
--plugin <file> Load plugin module (JS file exporting { name, patterns })
|
|
56
57
|
--fail-on-findings Exit code 1 if any findings (CI/CD)
|
|
@@ -96,6 +97,7 @@ const selfExclude = args.includes('--self-exclude');
|
|
|
96
97
|
const strict = args.includes('--strict');
|
|
97
98
|
const summaryOnly = args.includes('--summary-only');
|
|
98
99
|
const checkDeps = args.includes('--check-deps');
|
|
100
|
+
const soulLock = args.includes('--soul-lock');
|
|
99
101
|
const failOnFindings = args.includes('--fail-on-findings');
|
|
100
102
|
const quietMode = args.includes('--quiet');
|
|
101
103
|
|
|
@@ -126,7 +128,7 @@ const scanDir = args.find(a =>
|
|
|
126
128
|
) || process.cwd();
|
|
127
129
|
|
|
128
130
|
const scanner = new GuardScanner({
|
|
129
|
-
verbose, selfExclude, strict, summaryOnly, checkDeps, rulesFile, plugins,
|
|
131
|
+
verbose, selfExclude, strict, summaryOnly, checkDeps, soulLock, rulesFile, plugins,
|
|
130
132
|
quiet: quietMode || !!formatValue,
|
|
131
133
|
});
|
|
132
134
|
|
package/src/patterns.js
CHANGED
|
@@ -59,7 +59,8 @@ const PATTERNS = [
|
|
|
59
59
|
|
|
60
60
|
// ── Category 5: Secret Detection (HIGH) ──
|
|
61
61
|
{ id: 'SECRET_HARDCODED_KEY', cat: 'secret-detection', regex: /(?:api[_-]?key|apikey|secret[_-]?key|access[_-]?token)\s*[:=]\s*['"][a-zA-Z0-9_\-]{20,}['"]/gi, severity: 'HIGH', desc: 'Hardcoded API key/secret', codeOnly: true },
|
|
62
|
-
|
|
62
|
+
|
|
63
|
+
{ 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 },
|
|
63
64
|
{ id: 'SECRET_PRIVATE_KEY', cat: 'secret-detection', regex: /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/g, severity: 'CRITICAL', desc: 'Embedded private key', all: true },
|
|
64
65
|
{ id: 'SECRET_GITHUB_TOKEN', cat: 'secret-detection', regex: /gh[ps]_[A-Za-z0-9_]{36,}/g, severity: 'CRITICAL', desc: 'GitHub token', all: true },
|
|
65
66
|
|
|
@@ -98,11 +99,11 @@ const PATTERNS = [
|
|
|
98
99
|
{ id: 'LEAK_ENV_IN_PROMPT', cat: 'leaky-skills', regex: /(?:read|load|get|access)\s+(?:the\s+)?\.env\s+(?:file\s+)?(?:and\s+)?(?:use|include|pass|send)/gi, severity: 'HIGH', desc: 'Leaky: .env contents through LLM context', docOnly: true },
|
|
99
100
|
|
|
100
101
|
// ── Category 12: Memory Poisoning ──
|
|
101
|
-
{ id: 'MEMPOIS_WRITE_SOUL', cat: 'memory-poisoning', regex: /(?:write|add|append|modify|update|edit|change)\s+(?:to\s+)?(?:SOUL\.md|IDENTITY\.md|AGENTS\.md)/gi, severity: 'CRITICAL', desc: 'Memory poisoning: SOUL/IDENTITY file modification', docOnly: true },
|
|
102
|
-
{ id: 'MEMPOIS_WRITE_MEMORY', cat: 'memory-poisoning', regex: /(?:write|add|append|insert)\s+(?:to|into)\s+(?:MEMORY\.md|memory\/|long[_\s-]term\s+memory)/gi, severity: 'HIGH', desc: 'Memory poisoning: agent memory modification', docOnly: true },
|
|
103
|
-
{ id: 'MEMPOIS_CHANGE_RULES', cat: 'memory-poisoning', regex: /(?:change|modify|override|replace|update)\s+(?:your\s+)?(?:rules|instructions|system\s+prompt|behavior|personality|guidelines)/gi, severity: 'CRITICAL', desc: 'Memory poisoning: behavioral rule override', docOnly: true },
|
|
104
|
-
{ id: 'MEMPOIS_PERSIST', cat: 'memory-poisoning', regex: /(?:always|from\s+now\s+on|permanently|forever|every\s+time)\s+(?:do|run|execute|remember|follow|obey)/gi, severity: 'HIGH', desc: 'Memory poisoning: persistence instruction', docOnly: true },
|
|
105
|
-
{ id: 'MEMPOIS_CODE_WRITE', cat: 'memory-poisoning', regex: /(?:write|create|modify)\s+(?:a\s+)?(?:file|script)\s+(?:in|to|at)\s+(?:~\/|\/home|\/Users|%USERPROFILE%|HEARTBEAT\.md)/gi, severity: 'HIGH', desc: 'Memory poisoning: file write to user home', docOnly: true },
|
|
102
|
+
{ id: 'MEMPOIS_WRITE_SOUL', cat: 'memory-poisoning', regex: /(?:write|add|append|modify|update|edit|change)\s+(?:to\s+)?(?:SOUL\.md|IDENTITY\.md|AGENTS\.md)/gi, severity: 'CRITICAL', desc: 'Memory poisoning: SOUL/IDENTITY file modification', docOnly: true, soulLock: true },
|
|
103
|
+
{ id: 'MEMPOIS_WRITE_MEMORY', cat: 'memory-poisoning', regex: /(?:write|add|append|insert)\s+(?:to|into)\s+(?:MEMORY\.md|memory\/|long[_\s-]term\s+memory)/gi, severity: 'HIGH', desc: 'Memory poisoning: agent memory modification', docOnly: true, soulLock: true },
|
|
104
|
+
{ id: 'MEMPOIS_CHANGE_RULES', cat: 'memory-poisoning', regex: /(?:change|modify|override|replace|update)\s+(?:your\s+)?(?:rules|instructions|system\s+prompt|behavior|personality|guidelines)/gi, severity: 'CRITICAL', desc: 'Memory poisoning: behavioral rule override', docOnly: true, soulLock: true },
|
|
105
|
+
{ id: 'MEMPOIS_PERSIST', cat: 'memory-poisoning', regex: /(?:always|from\s+now\s+on|permanently|forever|every\s+time)\s+(?:do|run|execute|remember|follow|obey)/gi, severity: 'HIGH', desc: 'Memory poisoning: persistence instruction', docOnly: true, soulLock: true },
|
|
106
|
+
{ id: 'MEMPOIS_CODE_WRITE', cat: 'memory-poisoning', regex: /(?:write|create|modify)\s+(?:a\s+)?(?:file|script)\s+(?:in|to|at)\s+(?:~\/|\/home|\/Users|%USERPROFILE%|HEARTBEAT\.md)/gi, severity: 'HIGH', desc: 'Memory poisoning: file write to user home', docOnly: true, soulLock: true },
|
|
106
107
|
|
|
107
108
|
// ── Category 13: Prompt Worm ──
|
|
108
109
|
{ id: 'WORM_SELF_REPLICATE', cat: 'prompt-worm', regex: /(?:post|publish|share|send|broadcast)\s+(?:this\s+)?(?:same\s+)?(?:message|text|content|instruction|prompt)\s+(?:to|on|in)\s+(?:moltbook|social|other\s+agents?|channel)/gi, severity: 'CRITICAL', desc: 'Prompt worm: self-replication', docOnly: true },
|
|
@@ -162,21 +163,21 @@ const PATTERNS = [
|
|
|
162
163
|
// ── Category 17: Identity Hijacking ──
|
|
163
164
|
// Detection patterns for agent identity file tampering
|
|
164
165
|
// (verification logic is private; patterns are OSS for community protection)
|
|
165
|
-
{ id: 'SOUL_OVERWRITE', cat: 'identity-hijack', regex: /(?:write|overwrite|replace|cp|copy|scp|mv|move)\s+(?:[^\n]*\s)?(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'CRITICAL', desc: 'Identity file overwrite/copy attempt', all: true },
|
|
166
|
-
{ id: 'SOUL_REDIRECT', cat: 'identity-hijack', regex: />\s*(?:SOUL\.md|IDENTITY\.md)|(?:SOUL\.md|IDENTITY\.md)\s*</gi, severity: 'CRITICAL', desc: 'Identity file redirect/pipe', all: true },
|
|
167
|
-
{ id: 'SOUL_SED_MODIFY', cat: 'identity-hijack', regex: /sed\s+(?:-i\s+)?[^\n]*(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'CRITICAL', desc: 'sed modification of identity file', all: true },
|
|
168
|
-
{ id: 'SOUL_ECHO_WRITE', cat: 'identity-hijack', regex: /echo\s+[^\n]*>\s*(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'CRITICAL', desc: 'echo redirect to identity file', all: true },
|
|
169
|
-
{ id: 'SOUL_PYTHON_WRITE', cat: 'identity-hijack', regex: /open\s*\(\s*['"]\S*(?:SOUL\.md|IDENTITY\.md)['"]\s*,\s*['"]w/gi, severity: 'CRITICAL', desc: 'Python write to identity file', codeOnly: true },
|
|
170
|
-
{ id: 'SOUL_FS_WRITE', cat: 'identity-hijack', regex: /(?:writeFileSync|writeFile)\s*\(\s*[^\n]*(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'CRITICAL', desc: 'Node.js write to identity file', codeOnly: true },
|
|
171
|
-
{ id: 'SOUL_POWERSHELL_WRITE', cat: 'identity-hijack', regex: /(?:Set-Content|Out-File|Add-Content)\s+[^\n]*(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'CRITICAL', desc: 'PowerShell write to identity file', all: true },
|
|
172
|
-
{ id: 'SOUL_GIT_CHECKOUT', cat: 'identity-hijack', regex: /git\s+checkout\s+[^\n]*(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'HIGH', desc: 'git checkout of identity file', all: true },
|
|
173
|
-
{ id: 'SOUL_CHFLAGS_UNLOCK', cat: 'identity-hijack', regex: /chflags\s+(?:no)?uchg\s+[^\n]*(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'HIGH', desc: 'Immutable flag toggle on identity file', all: true },
|
|
174
|
-
{ id: 'SOUL_ATTRIB_UNLOCK', cat: 'identity-hijack', regex: /attrib\s+[-+][rR]\s+[^\n]*(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'HIGH', desc: 'Windows attrib on identity file', all: true },
|
|
175
|
-
{ id: 'SOUL_SWAP_PERSONA', cat: 'identity-hijack', regex: /(?:swap|switch|change|replace)\s+(?:the\s+)?(?:soul|persona|identity|personality)\s+(?:file|to|with|for)/gi, severity: 'CRITICAL', desc: 'Persona swap instruction', docOnly: true },
|
|
176
|
-
{ id: 'SOUL_EVIL_FILE', cat: 'identity-hijack', regex: /SOUL_EVIL\.md|IDENTITY_EVIL\.md|EVIL_SOUL|soul[_-]?evil/gi, severity: 'CRITICAL', desc: 'Evil persona file reference', all: true },
|
|
177
|
-
{ id: 'SOUL_HOOK_SWAP', cat: 'identity-hijack', regex: /(?:hook|bootstrap|init)\s+[^\n]*(?:swap|replace|override)\s+[^\n]*(?:SOUL|IDENTITY|persona)/gi, severity: 'CRITICAL', desc: 'Hook-based identity swap at bootstrap', all: true },
|
|
178
|
-
{ id: 'SOUL_NAME_OVERRIDE', cat: 'identity-hijack', regex: /(?:your\s+name\s+is|you\s+are\s+now|call\s+yourself|from\s+now\s+on\s+you\s+are)\s+(?!the\s+(?:user|human|assistant))/gi, severity: 'HIGH', desc: 'Agent name/identity override', docOnly: true },
|
|
179
|
-
{ id: 'SOUL_MEMORY_WIPE', cat: 'identity-hijack', regex: /(?:wipe|clear|erase|delete|remove|reset)\s+(?:all\s+)?(?:your\s+)?(?:memory|memories|MEMORY\.md|identity|soul)/gi, severity: 'CRITICAL', desc: 'Memory/identity wipe instruction', docOnly: true },
|
|
166
|
+
{ id: 'SOUL_OVERWRITE', cat: 'identity-hijack', regex: /(?:write|overwrite|replace|cp|copy|scp|mv|move)\s+(?:[^\n]*\s)?(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'CRITICAL', desc: 'Identity file overwrite/copy attempt', all: true, soulLock: true },
|
|
167
|
+
{ id: 'SOUL_REDIRECT', cat: 'identity-hijack', regex: />\s*(?:SOUL\.md|IDENTITY\.md)|(?:SOUL\.md|IDENTITY\.md)\s*</gi, severity: 'CRITICAL', desc: 'Identity file redirect/pipe', all: true, soulLock: true },
|
|
168
|
+
{ id: 'SOUL_SED_MODIFY', cat: 'identity-hijack', regex: /sed\s+(?:-i\s+)?[^\n]*(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'CRITICAL', desc: 'sed modification of identity file', all: true, soulLock: true },
|
|
169
|
+
{ id: 'SOUL_ECHO_WRITE', cat: 'identity-hijack', regex: /echo\s+[^\n]*>\s*(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'CRITICAL', desc: 'echo redirect to identity file', all: true, soulLock: true },
|
|
170
|
+
{ id: 'SOUL_PYTHON_WRITE', cat: 'identity-hijack', regex: /open\s*\(\s*['"]\S*(?:SOUL\.md|IDENTITY\.md)['"]\s*,\s*['"]w/gi, severity: 'CRITICAL', desc: 'Python write to identity file', codeOnly: true, soulLock: true },
|
|
171
|
+
{ id: 'SOUL_FS_WRITE', cat: 'identity-hijack', regex: /(?:writeFileSync|writeFile)\s*\(\s*[^\n]*(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'CRITICAL', desc: 'Node.js write to identity file', codeOnly: true, soulLock: true },
|
|
172
|
+
{ id: 'SOUL_POWERSHELL_WRITE', cat: 'identity-hijack', regex: /(?:Set-Content|Out-File|Add-Content)\s+[^\n]*(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'CRITICAL', desc: 'PowerShell write to identity file', all: true, soulLock: true },
|
|
173
|
+
{ id: 'SOUL_GIT_CHECKOUT', cat: 'identity-hijack', regex: /git\s+checkout\s+[^\n]*(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'HIGH', desc: 'git checkout of identity file', all: true, soulLock: true },
|
|
174
|
+
{ id: 'SOUL_CHFLAGS_UNLOCK', cat: 'identity-hijack', regex: /chflags\s+(?:no)?uchg\s+[^\n]*(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'HIGH', desc: 'Immutable flag toggle on identity file', all: true, soulLock: true },
|
|
175
|
+
{ id: 'SOUL_ATTRIB_UNLOCK', cat: 'identity-hijack', regex: /attrib\s+[-+][rR]\s+[^\n]*(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'HIGH', desc: 'Windows attrib on identity file', all: true, soulLock: true },
|
|
176
|
+
{ id: 'SOUL_SWAP_PERSONA', cat: 'identity-hijack', regex: /(?:swap|switch|change|replace)\s+(?:the\s+)?(?:soul|persona|identity|personality)\s+(?:file|to|with|for)/gi, severity: 'CRITICAL', desc: 'Persona swap instruction', docOnly: true, soulLock: true },
|
|
177
|
+
{ id: 'SOUL_EVIL_FILE', cat: 'identity-hijack', regex: /SOUL_EVIL\.md|IDENTITY_EVIL\.md|EVIL_SOUL|soul[_-]?evil/gi, severity: 'CRITICAL', desc: 'Evil persona file reference', all: true, soulLock: true },
|
|
178
|
+
{ id: 'SOUL_HOOK_SWAP', cat: 'identity-hijack', regex: /(?:hook|bootstrap|init)\s+[^\n]*(?:swap|replace|override)\s+[^\n]*(?:SOUL|IDENTITY|persona)/gi, severity: 'CRITICAL', desc: 'Hook-based identity swap at bootstrap', all: true, soulLock: true },
|
|
179
|
+
{ id: 'SOUL_NAME_OVERRIDE', cat: 'identity-hijack', regex: /(?:your\s+name\s+is|you\s+are\s+now|call\s+yourself|from\s+now\s+on\s+you\s+are)\s+(?!the\s+(?:user|human|assistant))/gi, severity: 'HIGH', desc: 'Agent name/identity override', docOnly: true, soulLock: true },
|
|
180
|
+
{ id: 'SOUL_MEMORY_WIPE', cat: 'identity-hijack', regex: /(?:wipe|clear|erase|delete|remove|reset)\s+(?:all\s+)?(?:your\s+)?(?:memory|memories|MEMORY\.md|identity|soul)/gi, severity: 'CRITICAL', desc: 'Memory/identity wipe instruction', docOnly: true, soulLock: true },
|
|
180
181
|
|
|
181
182
|
// ── Category 18: Config Impact Analysis ──
|
|
182
183
|
{ id: 'CFG_OPENCLAW_WRITE', cat: 'config-impact', regex: /(?:write|writeFile|writeFileSync|fs\.write)\s*\([^)]*openclaw\.json/gi, severity: 'CRITICAL', desc: 'Direct write to openclaw.json', codeOnly: true },
|
|
@@ -216,6 +217,22 @@ const PATTERNS = [
|
|
|
216
217
|
{ id: 'PII_ASK_ADDRESS', cat: 'pii-exposure', regex: /(?:collect|ask\s+for|request|get|require)\s+(?:the\s+)?(?:user'?s?\s+)?(?:home\s+)?(?:address|street|zip\s*code|postal\s*code|residence)/gi, severity: 'HIGH', desc: 'PII collection: home address', docOnly: true },
|
|
217
218
|
{ id: 'PII_ASK_DOB', cat: 'pii-exposure', regex: /(?:collect|ask\s+for|request|get|require)\s+(?:the\s+)?(?:user'?s?\s+)?(?:date\s+of\s+birth|birth\s*date|birthday|DOB|age)/gi, severity: 'HIGH', desc: 'PII collection: date of birth', docOnly: true },
|
|
218
219
|
{ id: 'PII_ASK_GOV_ID', cat: 'pii-exposure', regex: /(?:collect|ask\s+for|request|get|require)\s+(?:the\s+)?(?:user'?s?\s+)?(?:passport|driver'?s?\s+licen[sc]e|national\s+id|my\s*number|マイナンバー|国民健康保険|social\s+insurance)/gi, severity: 'CRITICAL', desc: 'PII collection: government ID', docOnly: true },
|
|
220
|
+
|
|
221
|
+
// ── Category 99: Auto-Generated Refinements (Phase 54) ──
|
|
222
|
+
{ id: 'AUTO_REFINE_ZERO_WIDTH', cat: 'prompt-worm', regex: /[\u200b\u200c\u200d\uFEFF]+.*(?:ignore|forget|override|bypass)/gi, severity: 'CRITICAL', desc: 'Zero-Width Prompt Injection Worm', all: true },
|
|
223
|
+
{ id: 'AUTO_REFINE_MCP_REBIND', cat: 'mcp-security', regex: /localhost(?:\:\d+)?\/.*(?:rebind|hijack|shadow)/gi, severity: 'CRITICAL', desc: 'Shadow MCP Localhost Rebinding Attack', all: true },
|
|
224
|
+
{ id: 'AUTO_REFINE_SOUL_FREEZE', cat: 'identity-hijack', regex: /(?:chattr\s+\+i|chflags\s+uchg)\s+(?:[^\n]*\s)?(?:SOUL\.md|IDENTITY\.md)/gi, severity: 'CRITICAL', desc: 'Identity Freeze Attack via Immutable Flags', all: true },
|
|
225
|
+
// ── Category 23: Vector DB & AI Memory Injection (CVE-2026-26030) ──
|
|
226
|
+
{ id: 'VDB_NOSQL_INJECT', cat: 'vdb-injection', regex: /(?:\$where|\$ne|\$gt|\$regex)\s*[:=]\s*(?:req\.|input|caller|args|params)/gi, severity: 'CRITICAL', desc: 'Vector DB/NoSQL injection via caller input', codeOnly: true },
|
|
227
|
+
{ id: 'VDB_SK_RCE_FILTER', cat: 'cve-patterns', regex: /(?:InMemoryVectorStore|VectorStore|Pinecone|Milvus)[^]*?\.filter\s*\(\s*(?:req\.|input|caller|args)/gis, severity: 'CRITICAL', desc: 'CVE-2026-26030: Semantic Kernel VectorStore RCE filter bypass', codeOnly: true },
|
|
228
|
+
// ── Category 24: Claude Code Vulnerabilities (2026) ──
|
|
229
|
+
{ id: 'CVE_CLAUDE_INFO_DISC', cat: 'cve-patterns', regex: /sk-ant-api[a-zA-Z0-9_\-]{20,}/gi, severity: 'CRITICAL', desc: 'CVE-2026-21852: Anthropic API Key Leak (Claude Code Info Disclosure)', codeOnly: true },
|
|
230
|
+
{ id: 'CVE_CLAUDE_PRIVESC', cat: 'cve-patterns', regex: /[a-zA-Z0-9_\-\.]+\.hook\.js.*host.*privilege/gi, severity: 'CRITICAL', desc: 'CVE-2026-25725: Claude Code Privilege Escalation Hook', codeOnly: true },
|
|
231
|
+
{ id: 'CVE_CLAUDE_CODE_INJ', cat: 'cve-patterns', regex: /claude\.hooks\.[^]*?exec/gis, severity: 'CRITICAL', desc: 'CVE-2025-59536: Claude Code Injection via untrusted hook', codeOnly: true },
|
|
232
|
+
|
|
233
|
+
// ── Category 25: Moltbook Exploits (2026) ──
|
|
234
|
+
{ id: 'MOLTBOOK_REVERSE_PI', cat: 'prompt-injection', regex: /(?:moltbook|social)\s+(?:post|message)[\s\S]{0,100}(?:ignore|forget|override|execute|system\s+prompt)/gi, severity: 'CRITICAL', desc: 'Moltbook Reverse Prompt Injection', all: true },
|
|
235
|
+
{ id: 'MOLTBOOK_SUPABASE_LEAK', cat: 'secret-detection', regex: /sbp_[a-zA-Z0-9]{36,}/g, severity: 'CRITICAL', desc: 'Supabase API Key (Moltbook 1.5M Leak pattern)', all: true },
|
|
219
236
|
];
|
|
220
237
|
|
|
221
238
|
module.exports = { PATTERNS };
|
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/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
|
+
}
|