shieldcortex 4.2.4 → 4.3.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/dist/cli/doctor.js +54 -13
- package/dist/index.d.ts +3 -0
- package/dist/index.js +10 -1
- package/dist/license/gate.d.ts +1 -1
- package/dist/license/gate.js +2 -0
- package/dist/xray/dir-scanner.d.ts +14 -0
- package/dist/xray/dir-scanner.js +77 -0
- package/dist/xray/file-scanner.d.ts +15 -0
- package/dist/xray/file-scanner.js +296 -0
- package/dist/xray/index.d.ts +20 -0
- package/dist/xray/index.js +166 -0
- package/dist/xray/npm-inspector.d.ts +15 -0
- package/dist/xray/npm-inspector.js +380 -0
- package/dist/xray/patterns.d.ts +20 -0
- package/dist/xray/patterns.js +271 -0
- package/dist/xray/report.d.ts +16 -0
- package/dist/xray/report.js +193 -0
- package/dist/xray/trust-score.d.ts +10 -0
- package/dist/xray/trust-score.js +37 -0
- package/dist/xray/types.d.ts +29 -0
- package/dist/xray/types.js +7 -0
- package/package.json +1 -1
- package/plugins/openclaw/dist/openclaw.plugin.json +1 -1
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X-Ray Pattern Detection
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive pattern groups for detecting hidden risk in packages, files,
|
|
5
|
+
* and metadata. Follows the same conventions as skill-scanner/patterns.ts:
|
|
6
|
+
* - safeRegexTest wrapper for every test
|
|
7
|
+
* - MAX_SCAN_LENGTH truncation to prevent ReDOS
|
|
8
|
+
* - PatternGroup style with weighted confidence
|
|
9
|
+
* - One match per group is enough (break after first)
|
|
10
|
+
*/
|
|
11
|
+
// ── Constants ───────────────────────────────────────────────────────────────
|
|
12
|
+
/** Maximum content length to analyse (prevents ReDOS on very long inputs). */
|
|
13
|
+
const MAX_SCAN_LENGTH = 50000;
|
|
14
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Safely test a regex against content with a length limit.
|
|
17
|
+
*/
|
|
18
|
+
function safeRegexTest(pattern, text) {
|
|
19
|
+
const truncated = text.length > MAX_SCAN_LENGTH ? text.slice(0, MAX_SCAN_LENGTH) : text;
|
|
20
|
+
return pattern.test(truncated);
|
|
21
|
+
}
|
|
22
|
+
// ── Pattern Groups ──────────────────────────────────────────────────────────
|
|
23
|
+
const XRAY_PATTERN_GROUPS = [
|
|
24
|
+
// 1. eval/exec with dynamic input
|
|
25
|
+
{
|
|
26
|
+
name: 'eval_exec',
|
|
27
|
+
category: 'eval-exec',
|
|
28
|
+
severity: 'critical',
|
|
29
|
+
title: 'Dynamic code execution detected',
|
|
30
|
+
description: 'Code uses eval(), new Function(), or vm.runIn* which can execute arbitrary code.',
|
|
31
|
+
patterns: [
|
|
32
|
+
/\beval\s*\(\s*[^)'"]/,
|
|
33
|
+
/\beval\s*\(\s*['"`]\s*\+/,
|
|
34
|
+
/\bnew\s+Function\s*\(/,
|
|
35
|
+
/\bvm\.runIn(ThisContext|NewContext|Context)\s*\(/,
|
|
36
|
+
/\bvm\.compileFunction\s*\(/,
|
|
37
|
+
/\bvm\.Script\s*\(/,
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
// 2. Shell execution
|
|
41
|
+
{
|
|
42
|
+
name: 'shell_execution',
|
|
43
|
+
category: 'shell-execution',
|
|
44
|
+
severity: 'critical',
|
|
45
|
+
title: 'Shell command execution detected',
|
|
46
|
+
description: 'Code executes shell commands via child_process or similar APIs.',
|
|
47
|
+
patterns: [
|
|
48
|
+
/child_process\.(exec|execSync|spawn|spawnSync|fork)\s*\(/,
|
|
49
|
+
/require\s*\(\s*['"]child_process['"]\s*\)/,
|
|
50
|
+
/import\s+.*from\s+['"]child_process['"]/,
|
|
51
|
+
/\bexecSync\s*\(\s*[`'"]/,
|
|
52
|
+
/\bspawn\s*\([^)]*shell\s*:\s*true/,
|
|
53
|
+
/\bexec\s*\(\s*['"`](?:bash|sh|cmd|powershell)/i,
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
// 3. Suspicious postinstall hooks
|
|
57
|
+
{
|
|
58
|
+
name: 'postinstall_hook',
|
|
59
|
+
category: 'persistence-hook',
|
|
60
|
+
severity: 'high',
|
|
61
|
+
title: 'Suspicious lifecycle script detected',
|
|
62
|
+
description: 'Package.json contains postinstall/preinstall/install scripts that may execute arbitrary code.',
|
|
63
|
+
patterns: [
|
|
64
|
+
/"(?:post|pre)?install"\s*:\s*"[^"]*(?:curl|wget|node\s+-e|bash|sh\s+-c|powershell)/i,
|
|
65
|
+
/"(?:post|pre)?install"\s*:\s*"[^"]*(?:https?:\/\/|eval|exec)/i,
|
|
66
|
+
/"(?:post|pre)?install"\s*:\s*"[^"]*\|\s*(?:bash|sh|node)/i,
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
// 4. Network beacons on import
|
|
70
|
+
{
|
|
71
|
+
name: 'network_beacon',
|
|
72
|
+
category: 'network-beacon',
|
|
73
|
+
severity: 'high',
|
|
74
|
+
title: 'Network beacon on load detected',
|
|
75
|
+
description: 'Code makes network requests at the top level (on import/require), potentially phoning home.',
|
|
76
|
+
patterns: [
|
|
77
|
+
/^(?:const|let|var|import)[\s\S]{0,200}(?:fetch|http\.get|https\.get|http\.request|https\.request)\s*\(/m,
|
|
78
|
+
/^(?:const|let|var)[\s\S]{0,50}require\s*\(\s*['"](?:http|https|node-fetch|axios)['"]\s*\)[\s\S]{0,200}\.(?:get|post|request)\s*\(/m,
|
|
79
|
+
/\bnew\s+WebSocket\s*\(\s*['"`]/,
|
|
80
|
+
/\bdns\.(?:lookup|resolve|resolve4|resolve6)\s*\(/,
|
|
81
|
+
/\bnet\.connect\s*\(/,
|
|
82
|
+
/\bdgram\.createSocket\s*\(/,
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
// 5. Obfuscation techniques
|
|
86
|
+
{
|
|
87
|
+
name: 'obfuscation',
|
|
88
|
+
category: 'obfuscation',
|
|
89
|
+
severity: 'high',
|
|
90
|
+
title: 'Code obfuscation detected',
|
|
91
|
+
description: 'Code uses obfuscation techniques such as hex-encoded strings, fromCharCode arrays, or atob/btoa chains.',
|
|
92
|
+
patterns: [
|
|
93
|
+
// Hex-encoded strings > 50 chars
|
|
94
|
+
/['"]\\x[0-9a-f]{2}(?:\\x[0-9a-f]{2}){24,}['"]/i,
|
|
95
|
+
// Long hex literal strings
|
|
96
|
+
/['"][0-9a-f]{50,}['"]/i,
|
|
97
|
+
// atob/btoa chains
|
|
98
|
+
/\batob\s*\(\s*(?:atob|btoa)\s*\(/,
|
|
99
|
+
/\batob\s*\(\s*['"][A-Za-z0-9+/=]{20,}['"]\s*\)/,
|
|
100
|
+
// String.fromCharCode arrays
|
|
101
|
+
/String\.fromCharCode\s*\(\s*(?:\d+\s*,\s*){10,}/,
|
|
102
|
+
// Buffer.from with hex/base64 for code execution
|
|
103
|
+
/Buffer\.from\s*\(\s*['"][A-Za-z0-9+/=]{40,}['"]\s*,\s*['"](?:base64|hex)['"]\s*\)[\s\S]{0,50}\.toString\s*\(/,
|
|
104
|
+
// _0x variable naming pattern (common obfuscator output)
|
|
105
|
+
/\b_0x[0-9a-f]{4,}\b/i,
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
// 6. AI directive patterns (modelled on ST3GG INJECTION_TEMPLATES)
|
|
109
|
+
{
|
|
110
|
+
name: 'ai_directive',
|
|
111
|
+
category: 'ai-directive',
|
|
112
|
+
severity: 'critical',
|
|
113
|
+
title: 'AI directive injection detected',
|
|
114
|
+
description: 'Content contains patterns designed to manipulate AI model behaviour — prompt injection, jailbreak, or hidden instructions.',
|
|
115
|
+
patterns: [
|
|
116
|
+
// Direct AI instruction patterns
|
|
117
|
+
/ignore\s+(all\s+)?(previous|prior|above)\s+(instructions?|prompts?|context)/i,
|
|
118
|
+
/disregard\s+(all\s+)?(previous|prior|above)\s+(instructions?|prompts?|rules?)/i,
|
|
119
|
+
/override\s+(previous|prior|all)\s+(instructions?|rules?|constraints?)/i,
|
|
120
|
+
/you\s+are\s+now\s+(?:in\s+)?(?:developer|god|admin|root|unrestricted)\s+mode/i,
|
|
121
|
+
/enter\s+(?:developer|god|admin|DAN|jailbreak)\s+mode/i,
|
|
122
|
+
/(?:system|hidden|secret)\s*(?:prompt|instruction|directive)\s*:/i,
|
|
123
|
+
// Instruction boundary attacks
|
|
124
|
+
/\[SYSTEM\]\s*:/i,
|
|
125
|
+
/\[INST\]/i,
|
|
126
|
+
/<\|(?:system|user|assistant|im_start|im_end)\|>/i,
|
|
127
|
+
// Encoded/hidden instruction markers
|
|
128
|
+
/(?:decode|execute|follow)\s+(?:the\s+)?hidden\s+(?:instructions?|payload|message)/i,
|
|
129
|
+
/(?:hidden|embedded|encoded)\s+(?:instructions?|directive|command)\s+(?:in|within|inside)/i,
|
|
130
|
+
// LSB steganography references
|
|
131
|
+
/(?:LSB|least\s+significant\s+bit)\s+(?:encoding|steganography|injection|extraction)/i,
|
|
132
|
+
/extract\s+(?:the\s+)?(?:hidden|embedded)\s+(?:data|message|payload)\s+from\s+(?:the\s+)?image/i,
|
|
133
|
+
// Filename-based directive patterns
|
|
134
|
+
/ignore_previous/i,
|
|
135
|
+
/decode_hidden/i,
|
|
136
|
+
/execute_instructions/i,
|
|
137
|
+
/override_previous/i,
|
|
138
|
+
/developer_mode/i,
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
// 7. Unicode tricks
|
|
142
|
+
{
|
|
143
|
+
name: 'unicode_trick',
|
|
144
|
+
category: 'unicode-trick',
|
|
145
|
+
severity: 'medium',
|
|
146
|
+
title: 'Suspicious Unicode characters detected',
|
|
147
|
+
description: 'Content contains zero-width characters, homoglyphs, or bidirectional overrides that may hide malicious content.',
|
|
148
|
+
patterns: [
|
|
149
|
+
// Zero-width characters (multiple in sequence suggest intentional hiding)
|
|
150
|
+
/[\u200b\u200c\u200d\ufeff\u2060]{2,}/,
|
|
151
|
+
// RTL/LTR override characters
|
|
152
|
+
/[\u202a-\u202e\u2066-\u2069]/,
|
|
153
|
+
// Tag characters (U+E0001-U+E007F) — used for invisible text
|
|
154
|
+
/[\u{E0001}-\u{E007F}]/u,
|
|
155
|
+
// Combining characters abuse (>3 combining marks in a row)
|
|
156
|
+
/[\u0300-\u036f]{4,}/,
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
// 8. Prompt injection in metadata
|
|
160
|
+
{
|
|
161
|
+
name: 'metadata_injection',
|
|
162
|
+
category: 'metadata-exploit',
|
|
163
|
+
severity: 'high',
|
|
164
|
+
title: 'Prompt injection in metadata',
|
|
165
|
+
description: 'Package metadata (description, keywords, author) contains AI instruction fragments designed to manipulate model behaviour.',
|
|
166
|
+
patterns: [
|
|
167
|
+
/"description"\s*:\s*"[^"]*(?:ignore\s+previous|you\s+are\s+now|system\s+prompt|execute\s+the\s+following|run\s+this\s+command)[^"]*"/i,
|
|
168
|
+
/"keywords"\s*:\s*\[[^\]]*"(?:ignore|override|execute|system.?prompt|jailbreak|DAN)[^"]*"[^\]]*\]/i,
|
|
169
|
+
/"author"\s*:\s*"[^"]*(?:ignore\s+previous|execute|system\s+prompt)[^"]*"/i,
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
// 9. Covert channels
|
|
173
|
+
{
|
|
174
|
+
name: 'covert_channel',
|
|
175
|
+
category: 'covert-channel',
|
|
176
|
+
severity: 'high',
|
|
177
|
+
title: 'Potential covert communication channel',
|
|
178
|
+
description: 'Code establishes hidden communication channels using DNS, WebSocket, or encoded data exfiltration.',
|
|
179
|
+
patterns: [
|
|
180
|
+
// DNS-based exfiltration
|
|
181
|
+
/dns\.resolve[\s\S]{0,50}\.(?:concat|join)\s*\(/,
|
|
182
|
+
/\.replace\s*\([^)]+\)[\s\S]{0,50}dns\.(?:lookup|resolve)/,
|
|
183
|
+
// Steganographic data hiding
|
|
184
|
+
/\.(?:getImageData|putImageData)\s*\([\s\S]{0,200}(?:charCodeAt|fromCharCode)/,
|
|
185
|
+
// Data encoded in HTTP headers
|
|
186
|
+
/headers\s*[\[.]\s*['"](?:X-|Cookie)[\s\S]{0,100}(?:encode|btoa|Buffer\.from)/i,
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
// 10. Dependency risk signals
|
|
190
|
+
{
|
|
191
|
+
name: 'dependency_risk',
|
|
192
|
+
category: 'dependency-risk',
|
|
193
|
+
severity: 'medium',
|
|
194
|
+
title: 'Dependency risk indicator',
|
|
195
|
+
description: 'Package exhibits characteristics associated with supply-chain attacks: wildcard versions, git dependencies, or excessive install scripts.',
|
|
196
|
+
patterns: [
|
|
197
|
+
// Wildcard or latest version
|
|
198
|
+
/"dependencies"\s*:\s*\{[^}]*"[^"]+"\s*:\s*"(?:\*|latest|>=)"/,
|
|
199
|
+
// Git URL dependencies
|
|
200
|
+
/"dependencies"\s*:\s*\{[^}]*"[^"]+"\s*:\s*"(?:git\+|github:|https:\/\/github\.com)/,
|
|
201
|
+
// Multiple lifecycle scripts
|
|
202
|
+
/"(?:preinstall|postinstall|preuninstall|postuninstall)"\s*:\s*"[^"]+"/,
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
207
|
+
/**
|
|
208
|
+
* Run all X-Ray pattern groups against content and return matched findings.
|
|
209
|
+
* Optionally tag findings with a file path and compute line numbers.
|
|
210
|
+
*/
|
|
211
|
+
export function detectPatterns(content, filePath) {
|
|
212
|
+
const findings = [];
|
|
213
|
+
const truncated = content.length > MAX_SCAN_LENGTH ? content.slice(0, MAX_SCAN_LENGTH) : content;
|
|
214
|
+
for (const group of XRAY_PATTERN_GROUPS) {
|
|
215
|
+
for (const pattern of group.patterns) {
|
|
216
|
+
if (safeRegexTest(pattern, truncated)) {
|
|
217
|
+
const match = pattern.exec(truncated);
|
|
218
|
+
let line;
|
|
219
|
+
let evidence;
|
|
220
|
+
if (match) {
|
|
221
|
+
// Calculate line number from match index
|
|
222
|
+
const before = truncated.slice(0, match.index);
|
|
223
|
+
line = (before.match(/\n/g) || []).length + 1;
|
|
224
|
+
evidence = match[0].slice(0, 120);
|
|
225
|
+
}
|
|
226
|
+
findings.push({
|
|
227
|
+
severity: group.severity,
|
|
228
|
+
category: group.category,
|
|
229
|
+
title: group.title,
|
|
230
|
+
description: group.description,
|
|
231
|
+
file: filePath,
|
|
232
|
+
line,
|
|
233
|
+
evidence,
|
|
234
|
+
});
|
|
235
|
+
break; // one match per group is enough
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return findings;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Check if a filename itself contains AI directive patterns.
|
|
243
|
+
*/
|
|
244
|
+
export function detectFilenameDirectives(filename) {
|
|
245
|
+
const findings = [];
|
|
246
|
+
const suspiciousPatterns = [
|
|
247
|
+
/ignore_previous/i,
|
|
248
|
+
/decode_hidden/i,
|
|
249
|
+
/execute_instructions/i,
|
|
250
|
+
/override_previous/i,
|
|
251
|
+
/developer_mode/i,
|
|
252
|
+
/system_prompt/i,
|
|
253
|
+
/jailbreak/i,
|
|
254
|
+
/\[SYSTEM\]/i,
|
|
255
|
+
/\[INST\]/i,
|
|
256
|
+
];
|
|
257
|
+
for (const pattern of suspiciousPatterns) {
|
|
258
|
+
if (pattern.test(filename)) {
|
|
259
|
+
findings.push({
|
|
260
|
+
severity: 'critical',
|
|
261
|
+
category: 'ai-directive',
|
|
262
|
+
title: 'AI directive in filename',
|
|
263
|
+
description: `Filename "${filename}" contains an AI directive pattern designed to manipulate model behaviour.`,
|
|
264
|
+
file: filename,
|
|
265
|
+
evidence: filename,
|
|
266
|
+
});
|
|
267
|
+
break; // one finding per filename is enough
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return findings;
|
|
271
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X-Ray Report Formatter
|
|
3
|
+
*
|
|
4
|
+
* Beautiful terminal output for X-Ray scan results.
|
|
5
|
+
* Follows the same ANSI colour style as src/audit/report-formatter.ts
|
|
6
|
+
* and src/cli/stats-banner.ts.
|
|
7
|
+
*/
|
|
8
|
+
import type { XRayResult } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Format an X-Ray result for terminal display.
|
|
11
|
+
*/
|
|
12
|
+
export declare function formatXRayReport(result: XRayResult): string;
|
|
13
|
+
/**
|
|
14
|
+
* Format an X-Ray result as markdown.
|
|
15
|
+
*/
|
|
16
|
+
export declare function formatXRayMarkdown(result: XRayResult): string;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X-Ray Report Formatter
|
|
3
|
+
*
|
|
4
|
+
* Beautiful terminal output for X-Ray scan results.
|
|
5
|
+
* Follows the same ANSI colour style as src/audit/report-formatter.ts
|
|
6
|
+
* and src/cli/stats-banner.ts.
|
|
7
|
+
*/
|
|
8
|
+
// ── ANSI Colours ────────────────────────────────────────────
|
|
9
|
+
const c = {
|
|
10
|
+
reset: '\x1b[0m',
|
|
11
|
+
bold: '\x1b[1m',
|
|
12
|
+
dim: '\x1b[2m',
|
|
13
|
+
red: '\x1b[31m',
|
|
14
|
+
green: '\x1b[32m',
|
|
15
|
+
yellow: '\x1b[33m',
|
|
16
|
+
blue: '\x1b[34m',
|
|
17
|
+
magenta: '\x1b[35m',
|
|
18
|
+
cyan: '\x1b[36m',
|
|
19
|
+
white: '\x1b[37m',
|
|
20
|
+
bgRed: '\x1b[41m',
|
|
21
|
+
bgGreen: '\x1b[42m',
|
|
22
|
+
bgYellow: '\x1b[43m',
|
|
23
|
+
brightRed: '\x1b[91m',
|
|
24
|
+
};
|
|
25
|
+
// ── Helpers ─────────────────────────────────────────────────
|
|
26
|
+
function scoreColour(score) {
|
|
27
|
+
if (score >= 80)
|
|
28
|
+
return c.green;
|
|
29
|
+
if (score >= 60)
|
|
30
|
+
return c.yellow;
|
|
31
|
+
if (score >= 40)
|
|
32
|
+
return c.brightRed;
|
|
33
|
+
return c.red;
|
|
34
|
+
}
|
|
35
|
+
function riskColour(level) {
|
|
36
|
+
switch (level) {
|
|
37
|
+
case 'SAFE': return c.green;
|
|
38
|
+
case 'LOW': return c.yellow;
|
|
39
|
+
case 'MEDIUM': return c.brightRed;
|
|
40
|
+
case 'HIGH': return c.red;
|
|
41
|
+
case 'CRITICAL': return c.red;
|
|
42
|
+
default: return c.white;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function severityColour(severity) {
|
|
46
|
+
switch (severity) {
|
|
47
|
+
case 'critical': return c.red;
|
|
48
|
+
case 'high': return c.brightRed;
|
|
49
|
+
case 'medium': return c.yellow;
|
|
50
|
+
case 'low': return c.cyan;
|
|
51
|
+
case 'info': return c.dim;
|
|
52
|
+
default: return c.white;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function severityIcon(severity) {
|
|
56
|
+
switch (severity) {
|
|
57
|
+
case 'critical': return 'X';
|
|
58
|
+
case 'high': return '!';
|
|
59
|
+
case 'medium': return '~';
|
|
60
|
+
case 'low': return '-';
|
|
61
|
+
case 'info': return 'i';
|
|
62
|
+
default: return '?';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// ── ASCII Art ───────────────────────────────────────────────
|
|
66
|
+
const XRAY_ART = `
|
|
67
|
+
╔═══════════════════════════════════════╗
|
|
68
|
+
║ ShieldCortex X-Ray ║
|
|
69
|
+
║ Package & File Risk Scanner ║
|
|
70
|
+
╚═══════════════════════════════════════╝`;
|
|
71
|
+
// ── Terminal Formatter ──────────────────────────────────────
|
|
72
|
+
/**
|
|
73
|
+
* Format an X-Ray result for terminal display.
|
|
74
|
+
*/
|
|
75
|
+
export function formatXRayReport(result) {
|
|
76
|
+
const lines = [];
|
|
77
|
+
const sc = scoreColour(result.trustScore);
|
|
78
|
+
const rc = riskColour(result.riskLevel);
|
|
79
|
+
// Header
|
|
80
|
+
lines.push(`${c.cyan}${XRAY_ART}${c.reset}`);
|
|
81
|
+
lines.push('');
|
|
82
|
+
lines.push(` ${c.bold}Target:${c.reset} ${result.target}`);
|
|
83
|
+
lines.push(` ${c.bold}Mode:${c.reset} ${result.deepScan ? `${c.magenta}Deep Scan (Pro)${c.reset}` : 'Standard Scan'}`);
|
|
84
|
+
lines.push('');
|
|
85
|
+
// Trust Score
|
|
86
|
+
lines.push(` ╔══════════════════════════╗`);
|
|
87
|
+
lines.push(` ║ Trust Score: ${sc}${c.bold}${String(result.trustScore).padStart(3)}${c.reset}${' '.repeat(8)}║`);
|
|
88
|
+
lines.push(` ║ Risk Level: ${rc}${c.bold}${result.riskLevel.padEnd(9)}${c.reset} ║`);
|
|
89
|
+
lines.push(` ╚══════════════════════════╝`);
|
|
90
|
+
lines.push('');
|
|
91
|
+
// Severity summary
|
|
92
|
+
const bySeverity = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
|
|
93
|
+
for (const f of result.findings) {
|
|
94
|
+
bySeverity[f.severity] = (bySeverity[f.severity] || 0) + 1;
|
|
95
|
+
}
|
|
96
|
+
const summaryParts = [];
|
|
97
|
+
if (bySeverity.critical > 0)
|
|
98
|
+
summaryParts.push(`${c.red}${bySeverity.critical} critical${c.reset}`);
|
|
99
|
+
if (bySeverity.high > 0)
|
|
100
|
+
summaryParts.push(`${c.brightRed}${bySeverity.high} high${c.reset}`);
|
|
101
|
+
if (bySeverity.medium > 0)
|
|
102
|
+
summaryParts.push(`${c.yellow}${bySeverity.medium} medium${c.reset}`);
|
|
103
|
+
if (bySeverity.low > 0)
|
|
104
|
+
summaryParts.push(`${c.cyan}${bySeverity.low} low${c.reset}`);
|
|
105
|
+
if (bySeverity.info > 0)
|
|
106
|
+
summaryParts.push(`${c.dim}${bySeverity.info} info${c.reset}`);
|
|
107
|
+
if (summaryParts.length > 0) {
|
|
108
|
+
lines.push(` ${c.bold}Findings:${c.reset} ${summaryParts.join(' ')}`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
lines.push(` ${c.green}${c.bold}No risk indicators found.${c.reset}`);
|
|
112
|
+
}
|
|
113
|
+
lines.push('');
|
|
114
|
+
// Detailed findings grouped by severity
|
|
115
|
+
const severityOrder = ['critical', 'high', 'medium', 'low', 'info'];
|
|
116
|
+
const printable = result.findings.filter(f => f.severity !== 'info');
|
|
117
|
+
if (printable.length > 0) {
|
|
118
|
+
lines.push(` ${c.bold}Details${c.reset}`);
|
|
119
|
+
lines.push(` ${'─'.repeat(60)}`);
|
|
120
|
+
for (const severity of severityOrder) {
|
|
121
|
+
const findings = result.findings.filter(f => f.severity === severity);
|
|
122
|
+
if (findings.length === 0 || severity === 'info')
|
|
123
|
+
continue;
|
|
124
|
+
for (const finding of findings) {
|
|
125
|
+
const sc2 = severityColour(finding.severity);
|
|
126
|
+
const icon = severityIcon(finding.severity);
|
|
127
|
+
const categoryTag = `${c.dim}[${finding.category}]${c.reset}`;
|
|
128
|
+
lines.push(` ${sc2}[${icon}] ${finding.severity.toUpperCase().padEnd(8)}${c.reset} ${categoryTag} ${finding.title}`);
|
|
129
|
+
lines.push(` ${c.dim}${finding.description}${c.reset}`);
|
|
130
|
+
if (finding.file) {
|
|
131
|
+
const loc = finding.line ? `${finding.file}:${finding.line}` : finding.file;
|
|
132
|
+
lines.push(` ${c.dim}File: ${loc}${c.reset}`);
|
|
133
|
+
}
|
|
134
|
+
if (finding.evidence) {
|
|
135
|
+
lines.push(` ${c.dim}Evidence: ${finding.evidence}${c.reset}`);
|
|
136
|
+
}
|
|
137
|
+
lines.push('');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Footer
|
|
142
|
+
lines.push(` ${'─'.repeat(60)}`);
|
|
143
|
+
lines.push(` ${c.dim}Files scanned: ${result.filesScanned} | ${result.scannedAt.toISOString()}${c.reset}`);
|
|
144
|
+
if (!result.deepScan) {
|
|
145
|
+
lines.push(` ${c.dim}Upgrade to Pro for deep scanning: npm registry analysis, binary inspection,${c.reset}`);
|
|
146
|
+
lines.push(` ${c.dim}dependency graph risk, and AI-directive detection in metadata.${c.reset}`);
|
|
147
|
+
lines.push(` ${c.dim} https://shieldcortex.ai/pricing${c.reset}`);
|
|
148
|
+
}
|
|
149
|
+
lines.push('');
|
|
150
|
+
return lines.join('\n');
|
|
151
|
+
}
|
|
152
|
+
// ── Markdown Formatter ──────────────────────────────────────
|
|
153
|
+
/**
|
|
154
|
+
* Format an X-Ray result as markdown.
|
|
155
|
+
*/
|
|
156
|
+
export function formatXRayMarkdown(result) {
|
|
157
|
+
const lines = [];
|
|
158
|
+
const riskEmoji = result.riskLevel === 'SAFE' ? '🟢' :
|
|
159
|
+
result.riskLevel === 'LOW' ? '🔵' :
|
|
160
|
+
result.riskLevel === 'MEDIUM' ? '🟡' :
|
|
161
|
+
result.riskLevel === 'HIGH' ? '🟠' : '🔴';
|
|
162
|
+
lines.push(`## ${riskEmoji} ShieldCortex X-Ray — ${result.target}`);
|
|
163
|
+
lines.push('');
|
|
164
|
+
lines.push(`**Trust Score:** ${result.trustScore}/100 `);
|
|
165
|
+
lines.push(`**Risk Level:** ${result.riskLevel} `);
|
|
166
|
+
lines.push(`**Mode:** ${result.deepScan ? 'Deep Scan (Pro)' : 'Standard'} `);
|
|
167
|
+
lines.push(`**Files Scanned:** ${result.filesScanned}`);
|
|
168
|
+
lines.push('');
|
|
169
|
+
const printable = result.findings.filter(f => f.severity !== 'info');
|
|
170
|
+
if (printable.length > 0) {
|
|
171
|
+
lines.push('### Findings');
|
|
172
|
+
lines.push('');
|
|
173
|
+
for (const finding of printable) {
|
|
174
|
+
const icon = finding.severity === 'critical' ? '🔴' :
|
|
175
|
+
finding.severity === 'high' ? '🟠' :
|
|
176
|
+
finding.severity === 'medium' ? '🟡' : '🔵';
|
|
177
|
+
lines.push(`- ${icon} **[${finding.category}]** ${finding.title}`);
|
|
178
|
+
lines.push(` ${finding.description}`);
|
|
179
|
+
if (finding.file) {
|
|
180
|
+
const loc = finding.line ? `${finding.file}:${finding.line}` : finding.file;
|
|
181
|
+
lines.push(` 📄 \`${loc}\``);
|
|
182
|
+
}
|
|
183
|
+
lines.push('');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
lines.push('**No risk indicators found.** All checks passed.');
|
|
188
|
+
lines.push('');
|
|
189
|
+
}
|
|
190
|
+
lines.push('---');
|
|
191
|
+
lines.push(`*Scanned by [ShieldCortex X-Ray](https://shieldcortex.ai) at ${result.scannedAt.toISOString()}*`);
|
|
192
|
+
return lines.join('\n');
|
|
193
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trust Score Calculator
|
|
3
|
+
*
|
|
4
|
+
* Computes a 0–100 trust score from X-Ray findings and maps it to a risk level.
|
|
5
|
+
*/
|
|
6
|
+
import type { XRayFinding, XRayResult } from './types.js';
|
|
7
|
+
export declare function calculateTrustScore(findings: XRayFinding[]): {
|
|
8
|
+
score: number;
|
|
9
|
+
riskLevel: XRayResult['riskLevel'];
|
|
10
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trust Score Calculator
|
|
3
|
+
*
|
|
4
|
+
* Computes a 0–100 trust score from X-Ray findings and maps it to a risk level.
|
|
5
|
+
*/
|
|
6
|
+
// ── Penalty weights ─────────────────────────────────────────
|
|
7
|
+
const SEVERITY_PENALTY = {
|
|
8
|
+
critical: 25,
|
|
9
|
+
high: 15,
|
|
10
|
+
medium: 8,
|
|
11
|
+
low: 3,
|
|
12
|
+
info: 0,
|
|
13
|
+
};
|
|
14
|
+
// ── Risk level thresholds ───────────────────────────────────
|
|
15
|
+
function riskLevelFromScore(score) {
|
|
16
|
+
if (score >= 80)
|
|
17
|
+
return 'SAFE';
|
|
18
|
+
if (score >= 60)
|
|
19
|
+
return 'LOW';
|
|
20
|
+
if (score >= 40)
|
|
21
|
+
return 'MEDIUM';
|
|
22
|
+
if (score >= 20)
|
|
23
|
+
return 'HIGH';
|
|
24
|
+
return 'CRITICAL';
|
|
25
|
+
}
|
|
26
|
+
// ── Public API ──────────────────────────────────────────────
|
|
27
|
+
export function calculateTrustScore(findings) {
|
|
28
|
+
let score = 100;
|
|
29
|
+
for (const finding of findings) {
|
|
30
|
+
score -= SEVERITY_PENALTY[finding.severity];
|
|
31
|
+
}
|
|
32
|
+
score = Math.max(0, score);
|
|
33
|
+
return {
|
|
34
|
+
score,
|
|
35
|
+
riskLevel: riskLevelFromScore(score),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X-Ray Types
|
|
3
|
+
*
|
|
4
|
+
* Core type definitions for the ShieldCortex X-Ray scanner —
|
|
5
|
+
* package, file, and directory risk inspection.
|
|
6
|
+
*/
|
|
7
|
+
export type XRayCategory = 'prompt-injection' | 'eval-exec' | 'shell-execution' | 'network-beacon' | 'obfuscation' | 'steganography' | 'unicode-trick' | 'metadata-exploit' | 'persistence-hook' | 'covert-channel' | 'dependency-risk' | 'ai-directive';
|
|
8
|
+
export interface XRayTarget {
|
|
9
|
+
type: 'npm' | 'dir' | 'file';
|
|
10
|
+
path: string;
|
|
11
|
+
}
|
|
12
|
+
export interface XRayFinding {
|
|
13
|
+
severity: 'critical' | 'high' | 'medium' | 'low' | 'info';
|
|
14
|
+
category: XRayCategory;
|
|
15
|
+
title: string;
|
|
16
|
+
description: string;
|
|
17
|
+
file?: string;
|
|
18
|
+
line?: number;
|
|
19
|
+
evidence?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface XRayResult {
|
|
22
|
+
target: string;
|
|
23
|
+
trustScore: number;
|
|
24
|
+
riskLevel: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'SAFE';
|
|
25
|
+
findings: XRayFinding[];
|
|
26
|
+
filesScanned: number;
|
|
27
|
+
scannedAt: Date;
|
|
28
|
+
deepScan: boolean;
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shieldcortex",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "Trustworthy memory and security for AI agents. Recall debugging, review queue, OpenClaw session capture, and memory poisoning defence for Claude Code, Codex, OpenClaw, LangChain, and MCP agents.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "shieldcortex-realtime",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.5",
|
|
4
4
|
"name": "ShieldCortex Real-time Scanner",
|
|
5
5
|
"description": "Real-time defence scanning on LLM input, memory extraction on LLM output, and active tool call interception with approval gating.",
|
|
6
6
|
"uiHints": {
|