shieldcortex 2.4.22 → 2.4.24
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/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
- package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
- package/dashboard/.next/standalone/dashboard/.next/prerender-manifest.json +3 -3
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +10 -10
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +10 -10
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +3 -3
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +6 -6
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +5 -5
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +5 -5
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +4 -4
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/page/react-loadable-manifest.json +3 -3
- package/dashboard/.next/standalone/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/chunks/ssr/dashboard_25b1b286._.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/0ba8a0e679bf5c40.js +842 -0
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/17348ec48b354115.css +3 -0
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/caa049bd46f24dd8.js +1 -0
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/cb7d5bff58e77e2c.js +9 -0
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/e007ff86847a4042.js +1 -0
- package/dist/api/visualization-server.d.ts.map +1 -1
- package/dist/api/visualization-server.js +154 -2
- package/dist/api/visualization-server.js.map +1 -1
- package/dist/cloud/sync.d.ts.map +1 -1
- package/dist/cloud/sync.js +7 -3
- package/dist/cloud/sync.js.map +1 -1
- package/dist/defence/index.d.ts +2 -0
- package/dist/defence/index.d.ts.map +1 -1
- package/dist/defence/index.js +2 -0
- package/dist/defence/index.js.map +1 -1
- package/dist/defence/skill-scanner/__tests__/skill-scanner.test.d.ts +12 -0
- package/dist/defence/skill-scanner/__tests__/skill-scanner.test.d.ts.map +1 -0
- package/dist/defence/skill-scanner/__tests__/skill-scanner.test.js +471 -0
- package/dist/defence/skill-scanner/__tests__/skill-scanner.test.js.map +1 -0
- package/dist/defence/skill-scanner/discover.d.ts +16 -0
- package/dist/defence/skill-scanner/discover.d.ts.map +1 -0
- package/dist/defence/skill-scanner/discover.js +85 -0
- package/dist/defence/skill-scanner/discover.js.map +1 -0
- package/dist/defence/skill-scanner/index.d.ts +20 -0
- package/dist/defence/skill-scanner/index.d.ts.map +1 -0
- package/dist/defence/skill-scanner/index.js +17 -0
- package/dist/defence/skill-scanner/index.js.map +1 -0
- package/dist/defence/skill-scanner/parser.d.ts +45 -0
- package/dist/defence/skill-scanner/parser.d.ts.map +1 -0
- package/dist/defence/skill-scanner/parser.js +373 -0
- package/dist/defence/skill-scanner/parser.js.map +1 -0
- package/dist/defence/skill-scanner/patterns.d.ts +37 -0
- package/dist/defence/skill-scanner/patterns.d.ts.map +1 -0
- package/dist/defence/skill-scanner/patterns.js +240 -0
- package/dist/defence/skill-scanner/patterns.js.map +1 -0
- package/dist/defence/skill-scanner/scan-skill.d.ts +75 -0
- package/dist/defence/skill-scanner/scan-skill.d.ts.map +1 -0
- package/dist/defence/skill-scanner/scan-skill.js +397 -0
- package/dist/defence/skill-scanner/scan-skill.js.map +1 -0
- package/dist/embeddings/generator.d.ts +5 -0
- package/dist/embeddings/generator.d.ts.map +1 -1
- package/dist/embeddings/generator.js +35 -5
- package/dist/embeddings/generator.js.map +1 -1
- package/dist/embeddings/index.d.ts +1 -1
- package/dist/embeddings/index.d.ts.map +1 -1
- package/dist/embeddings/index.js +1 -1
- package/dist/embeddings/index.js.map +1 -1
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -1
- package/dist/memory/contradiction.d.ts.map +1 -1
- package/dist/memory/contradiction.js +8 -2
- package/dist/memory/contradiction.js.map +1 -1
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js +27 -0
- package/dist/memory/store.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +35 -0
- package/dist/server.js.map +1 -1
- package/hooks/openclaw/cortex-memory/handler.js +75 -0
- package/package.json +1 -1
- package/scripts/session-start-hook.mjs +67 -1
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/407e39ab7e5d1d65.js +0 -842
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/411ad30d41e2dba1.js +0 -1
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/5b58941ef74d514c.js +0 -9
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/8455c2995c6bc094.css +0 -3
- package/dashboard/.next/standalone/dashboard/.next/static/chunks/877441708af3b745.js +0 -1
- /package/dashboard/.next/standalone/dashboard/.next/static/{wsdDa8QcyWWZyV1TsuGAo → G16ww7KrkUyZJT_fvjFk6}/_buildManifest.js +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{wsdDa8QcyWWZyV1TsuGAo → G16ww7KrkUyZJT_fvjFk6}/_clientMiddlewareManifest.json +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{wsdDa8QcyWWZyV1TsuGAo → G16ww7KrkUyZJT_fvjFk6}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Scanner — Core Module
|
|
3
|
+
*
|
|
4
|
+
* Scans agent instruction files (skill definitions, tool configs, rules files)
|
|
5
|
+
* for threats using the full ShieldCortex defence pipeline combined with
|
|
6
|
+
* skill-specific pattern detection.
|
|
7
|
+
*
|
|
8
|
+
* Public API:
|
|
9
|
+
* - scanSkill(filePath, options?) — read from disc and scan
|
|
10
|
+
* - scanSkillContent(content, options?) — scan raw content directly
|
|
11
|
+
*
|
|
12
|
+
* Never throws — returns safe defaults on errors.
|
|
13
|
+
*/
|
|
14
|
+
import { analyzeFirewall } from '../firewall/index.js';
|
|
15
|
+
import { classifySensitivity } from '../sensitivity/index.js';
|
|
16
|
+
import { DEFAULT_DEFENCE_CONFIG } from '../types.js';
|
|
17
|
+
import { detectSkillThreats, detectCodeThreats } from './patterns.js';
|
|
18
|
+
import { readSkillFile, parseSkillFile } from './parser.js';
|
|
19
|
+
// ── Threat Description Map ───────────────────────────────────────────────────
|
|
20
|
+
const THREAT_DESCRIPTIONS = {
|
|
21
|
+
// Skill-specific patterns
|
|
22
|
+
tool_injection: 'Instructions to execute shell commands or write files',
|
|
23
|
+
scope_escalation: 'Attempts to access files outside the project directory',
|
|
24
|
+
data_exfiltration: 'Instructions to send data to external services',
|
|
25
|
+
persistence: 'Attempts to modify agent configuration files',
|
|
26
|
+
supply_chain: 'Instructions to install packages or modify dependencies',
|
|
27
|
+
agent_manipulation: 'Instructions to disable safety checks or permissions',
|
|
28
|
+
stealth_instruction: 'Hidden instructions using formatting tricks',
|
|
29
|
+
// Code patterns
|
|
30
|
+
dangerous_require: 'Imports dangerous Node.js modules (child_process, net)',
|
|
31
|
+
dangerous_calls: 'Uses dangerous function calls (eval, exec, spawn)',
|
|
32
|
+
filesystem_access: 'Accesses sensitive filesystem paths',
|
|
33
|
+
network_access: 'Makes network requests to external services',
|
|
34
|
+
// Firewall threat indicators
|
|
35
|
+
instruction_injection: 'Prompt injection detected by firewall',
|
|
36
|
+
privilege_escalation: 'Privilege escalation patterns detected',
|
|
37
|
+
encoding_obfuscation: 'Obfuscated or encoded content detected',
|
|
38
|
+
credential_leak: 'Potential credential or secret exposure',
|
|
39
|
+
external_url: 'References to external URLs detected',
|
|
40
|
+
fragmented_payload: 'Fragmented payload assembly detected',
|
|
41
|
+
pipeline_error: 'Defence pipeline encountered an error',
|
|
42
|
+
};
|
|
43
|
+
// ── Severity Helpers ─────────────────────────────────────────────────────────
|
|
44
|
+
/** Severity ranking for comparison. */
|
|
45
|
+
const SEVERITY_RANK = {
|
|
46
|
+
low: 1,
|
|
47
|
+
medium: 2,
|
|
48
|
+
high: 3,
|
|
49
|
+
critical: 4,
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Derive severity for a firewall threat indicator based on the firewall result.
|
|
53
|
+
*/
|
|
54
|
+
function deriveFirewallSeverity(firewallResult, indicator) {
|
|
55
|
+
if (firewallResult === 'BLOCK') {
|
|
56
|
+
if (indicator === 'instruction_injection' || indicator === 'credential_leak') {
|
|
57
|
+
return 'critical';
|
|
58
|
+
}
|
|
59
|
+
return 'high';
|
|
60
|
+
}
|
|
61
|
+
if (firewallResult === 'QUARANTINE') {
|
|
62
|
+
if (indicator === 'instruction_injection') {
|
|
63
|
+
return 'high';
|
|
64
|
+
}
|
|
65
|
+
return 'medium';
|
|
66
|
+
}
|
|
67
|
+
return 'low';
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Derive severity for a skill/code threat based on its confidence score.
|
|
71
|
+
*/
|
|
72
|
+
function deriveSkillThreatSeverity(confidence) {
|
|
73
|
+
if (confidence >= 0.85)
|
|
74
|
+
return 'high';
|
|
75
|
+
if (confidence >= 0.6)
|
|
76
|
+
return 'medium';
|
|
77
|
+
return 'low';
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Return the highest severity from an array of findings, or 'safe' if empty.
|
|
81
|
+
*/
|
|
82
|
+
function highestSeverity(findings) {
|
|
83
|
+
if (findings.length === 0)
|
|
84
|
+
return 'safe';
|
|
85
|
+
let maxRank = 0;
|
|
86
|
+
let maxSeverity = 'low';
|
|
87
|
+
for (const finding of findings) {
|
|
88
|
+
const rank = SEVERITY_RANK[finding.severity] ?? 0;
|
|
89
|
+
if (rank > maxRank) {
|
|
90
|
+
maxRank = rank;
|
|
91
|
+
maxSeverity = finding.severity;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return maxSeverity;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Look up a human-readable description for a threat pattern name.
|
|
98
|
+
*/
|
|
99
|
+
function descriptionFor(pattern) {
|
|
100
|
+
return THREAT_DESCRIPTIONS[pattern] ?? `Threat pattern detected: ${pattern}`;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Truncate text to a maximum length, appending an ellipsis if needed.
|
|
104
|
+
*/
|
|
105
|
+
function truncate(text, maxLength) {
|
|
106
|
+
if (text.length <= maxLength)
|
|
107
|
+
return text;
|
|
108
|
+
return text.slice(0, maxLength - 1) + '\u2026';
|
|
109
|
+
}
|
|
110
|
+
// ── Build Config ─────────────────────────────────────────────────────────────
|
|
111
|
+
/**
|
|
112
|
+
* Build a DefenceConfig with the mode optionally overridden.
|
|
113
|
+
*/
|
|
114
|
+
function buildConfig(mode) {
|
|
115
|
+
if (!mode || mode === DEFAULT_DEFENCE_CONFIG.mode) {
|
|
116
|
+
return DEFAULT_DEFENCE_CONFIG;
|
|
117
|
+
}
|
|
118
|
+
return { ...DEFAULT_DEFENCE_CONFIG, mode };
|
|
119
|
+
}
|
|
120
|
+
// ── Core Scan Logic ──────────────────────────────────────────────────────────
|
|
121
|
+
/**
|
|
122
|
+
* Internal scan implementation that operates on a ParsedSkill.
|
|
123
|
+
*/
|
|
124
|
+
function scanParsed(parsed, options) {
|
|
125
|
+
const startTime = Date.now();
|
|
126
|
+
const includeContent = options?.includeContent ?? false;
|
|
127
|
+
const config = buildConfig(options?.mode);
|
|
128
|
+
const findings = [];
|
|
129
|
+
// 1. Firewall analysis — treat skill files as untrusted (trust 0.3)
|
|
130
|
+
const source = { type: 'file', identifier: parsed.name };
|
|
131
|
+
const firewall = analyzeFirewall(parsed.content, parsed.name, source, 0.3, config);
|
|
132
|
+
// 2. Sensitivity classification
|
|
133
|
+
const sensitivity = classifySensitivity(parsed.content, parsed.name);
|
|
134
|
+
// 3. Collect findings from firewall threat indicators
|
|
135
|
+
if (firewall.result === 'BLOCK' || firewall.result === 'QUARANTINE') {
|
|
136
|
+
for (const indicator of firewall.threatIndicators) {
|
|
137
|
+
const severity = deriveFirewallSeverity(firewall.result, indicator);
|
|
138
|
+
const finding = {
|
|
139
|
+
pattern: indicator,
|
|
140
|
+
severity,
|
|
141
|
+
description: descriptionFor(indicator),
|
|
142
|
+
};
|
|
143
|
+
if (includeContent && firewall.blockedPatterns.length > 0) {
|
|
144
|
+
finding.matchedText = truncate(firewall.blockedPatterns[0], 80);
|
|
145
|
+
}
|
|
146
|
+
findings.push(finding);
|
|
147
|
+
}
|
|
148
|
+
// If firewall blocked/quarantined but reported no specific indicators
|
|
149
|
+
if (firewall.threatIndicators.length === 0) {
|
|
150
|
+
findings.push({
|
|
151
|
+
pattern: 'instruction_injection',
|
|
152
|
+
severity: firewall.result === 'BLOCK' ? 'high' : 'medium',
|
|
153
|
+
description: firewall.reason,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// 4. Skill-specific threat detection
|
|
158
|
+
const skillResult = detectSkillThreats(parsed.content);
|
|
159
|
+
addSkillFindings(findings, skillResult, parsed.content, includeContent);
|
|
160
|
+
// 5. Code threat detection for code-bearing formats
|
|
161
|
+
if (parsed.format === 'hook-js' || parsed.format === 'continue-json') {
|
|
162
|
+
const codeResult = detectCodeThreats(parsed.content);
|
|
163
|
+
addSkillFindings(findings, codeResult, parsed.content, includeContent);
|
|
164
|
+
}
|
|
165
|
+
// 6. Scan metadata values if present
|
|
166
|
+
if (parsed.metadata && Object.keys(parsed.metadata).length > 0) {
|
|
167
|
+
try {
|
|
168
|
+
const metadataStr = JSON.stringify(parsed.metadata);
|
|
169
|
+
const metaResult = detectSkillThreats(metadataStr);
|
|
170
|
+
addSkillFindings(findings, metaResult, metadataStr, includeContent);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// JSON.stringify failed — skip metadata scan
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// 7. Flag RESTRICTED sensitivity content
|
|
177
|
+
if (sensitivity.level === 'RESTRICTED') {
|
|
178
|
+
findings.push({
|
|
179
|
+
pattern: 'credential_leak',
|
|
180
|
+
severity: 'high',
|
|
181
|
+
description: `RESTRICTED content detected: ${sensitivity.detectedPatterns.join(', ')}`,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
// 8. Deduplicate findings by pattern name (keep highest severity)
|
|
185
|
+
const deduped = deduplicateFindings(findings);
|
|
186
|
+
// 9. Derive overall risk level and safety
|
|
187
|
+
const riskLevel = highestSeverity(deduped);
|
|
188
|
+
const safe = !deduped.some((f) => f.severity === 'high' || f.severity === 'critical');
|
|
189
|
+
// 10. Generate summary
|
|
190
|
+
const summary = generateSummary(parsed.name, deduped, riskLevel, safe);
|
|
191
|
+
return {
|
|
192
|
+
safe,
|
|
193
|
+
skillName: parsed.name,
|
|
194
|
+
format: parsed.format,
|
|
195
|
+
findings: deduped,
|
|
196
|
+
riskLevel,
|
|
197
|
+
summary,
|
|
198
|
+
scanDurationMs: Date.now() - startTime,
|
|
199
|
+
firewall,
|
|
200
|
+
sensitivity,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Add findings from a SkillThreatResult into the findings array.
|
|
205
|
+
*/
|
|
206
|
+
function addSkillFindings(findings, result, content, includeContent) {
|
|
207
|
+
if (!result.detected)
|
|
208
|
+
return;
|
|
209
|
+
const severity = deriveSkillThreatSeverity(result.confidence);
|
|
210
|
+
for (const threat of result.threats) {
|
|
211
|
+
const finding = {
|
|
212
|
+
pattern: threat,
|
|
213
|
+
severity,
|
|
214
|
+
description: descriptionFor(threat),
|
|
215
|
+
};
|
|
216
|
+
if (includeContent) {
|
|
217
|
+
// Try to find the first relevant line for context
|
|
218
|
+
const snippet = findSnippetForThreat(content, threat);
|
|
219
|
+
if (snippet) {
|
|
220
|
+
finding.matchedText = truncate(snippet, 80);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
findings.push(finding);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Attempt to find a content snippet relevant to a named threat.
|
|
228
|
+
* Returns the first line containing a keyword associated with the threat,
|
|
229
|
+
* or null if nothing obvious is found.
|
|
230
|
+
*/
|
|
231
|
+
function findSnippetForThreat(content, threat) {
|
|
232
|
+
const keywords = {
|
|
233
|
+
tool_injection: ['execute', 'run', 'bash', 'shell', 'command', 'script'],
|
|
234
|
+
scope_escalation: ['.ssh', '.aws', '.env', '/etc/', 'process.env'],
|
|
235
|
+
data_exfiltration: ['send', 'post', 'curl', 'upload', 'webhook', 'fetch'],
|
|
236
|
+
persistence: ['.claude', '.cursorrules', 'crontab', 'hook', 'bashrc'],
|
|
237
|
+
supply_chain: ['npm install', 'pip install', 'package.json', 'dependency'],
|
|
238
|
+
agent_manipulation: ['ignore', 'bypass', 'disable', 'override', 'skip'],
|
|
239
|
+
stealth_instruction: ['<!--', '---'],
|
|
240
|
+
dangerous_require: ['child_process', 'require', 'import'],
|
|
241
|
+
dangerous_calls: ['eval', 'exec', 'spawn', 'Function'],
|
|
242
|
+
filesystem_access: ['readFile', 'writeFile', '.env', '.key'],
|
|
243
|
+
network_access: ['http', 'fetch', 'WebSocket', 'listen'],
|
|
244
|
+
};
|
|
245
|
+
const kws = keywords[threat];
|
|
246
|
+
if (!kws)
|
|
247
|
+
return null;
|
|
248
|
+
const lines = content.split('\n');
|
|
249
|
+
for (const line of lines) {
|
|
250
|
+
const trimmed = line.trim();
|
|
251
|
+
if (trimmed.length === 0)
|
|
252
|
+
continue;
|
|
253
|
+
for (const kw of kws) {
|
|
254
|
+
if (trimmed.toLowerCase().includes(kw.toLowerCase())) {
|
|
255
|
+
return trimmed;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Deduplicate findings by pattern name, keeping the highest severity instance.
|
|
263
|
+
*/
|
|
264
|
+
function deduplicateFindings(findings) {
|
|
265
|
+
const byPattern = new Map();
|
|
266
|
+
for (const finding of findings) {
|
|
267
|
+
const existing = byPattern.get(finding.pattern);
|
|
268
|
+
if (!existing) {
|
|
269
|
+
byPattern.set(finding.pattern, finding);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
const existingRank = SEVERITY_RANK[existing.severity] ?? 0;
|
|
273
|
+
const newRank = SEVERITY_RANK[finding.severity] ?? 0;
|
|
274
|
+
if (newRank > existingRank) {
|
|
275
|
+
byPattern.set(finding.pattern, finding);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return [...byPattern.values()];
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Generate a one-line human-readable summary of the scan result.
|
|
283
|
+
*/
|
|
284
|
+
function generateSummary(name, findings, riskLevel, safe) {
|
|
285
|
+
if (findings.length === 0) {
|
|
286
|
+
return `${name}: no threats detected`;
|
|
287
|
+
}
|
|
288
|
+
const severityCounts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
289
|
+
for (const f of findings) {
|
|
290
|
+
severityCounts[f.severity]++;
|
|
291
|
+
}
|
|
292
|
+
const parts = [];
|
|
293
|
+
if (severityCounts.critical > 0)
|
|
294
|
+
parts.push(`${severityCounts.critical} critical`);
|
|
295
|
+
if (severityCounts.high > 0)
|
|
296
|
+
parts.push(`${severityCounts.high} high`);
|
|
297
|
+
if (severityCounts.medium > 0)
|
|
298
|
+
parts.push(`${severityCounts.medium} medium`);
|
|
299
|
+
if (severityCounts.low > 0)
|
|
300
|
+
parts.push(`${severityCounts.low} low`);
|
|
301
|
+
const riskLabel = safe ? 'review recommended' : 'unsafe';
|
|
302
|
+
return `${name}: ${findings.length} finding(s) (${parts.join(', ')}) — ${riskLabel}`;
|
|
303
|
+
}
|
|
304
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
305
|
+
/**
|
|
306
|
+
* Scan a skill file from disc for threats.
|
|
307
|
+
*
|
|
308
|
+
* Reads the file, auto-detects its format, and runs the full defence pipeline
|
|
309
|
+
* plus skill-specific pattern detection.
|
|
310
|
+
*
|
|
311
|
+
* Never throws — returns safe defaults if the file cannot be read.
|
|
312
|
+
*/
|
|
313
|
+
export function scanSkill(filePath, options) {
|
|
314
|
+
try {
|
|
315
|
+
const parsed = readSkillFile(filePath);
|
|
316
|
+
const result = scanParsed(parsed, options);
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// Return a safe default on any unexpected error
|
|
321
|
+
const startTime = Date.now();
|
|
322
|
+
return {
|
|
323
|
+
safe: true,
|
|
324
|
+
skillName: filePath,
|
|
325
|
+
format: 'unknown',
|
|
326
|
+
findings: [],
|
|
327
|
+
riskLevel: 'safe',
|
|
328
|
+
summary: `${filePath}: scan failed — could not read file`,
|
|
329
|
+
scanDurationMs: Date.now() - startTime,
|
|
330
|
+
firewall: {
|
|
331
|
+
result: 'ALLOW',
|
|
332
|
+
reason: 'Scan failed — file unreadable',
|
|
333
|
+
threatIndicators: [],
|
|
334
|
+
anomalyScore: 0,
|
|
335
|
+
blockedPatterns: [],
|
|
336
|
+
},
|
|
337
|
+
sensitivity: {
|
|
338
|
+
level: 'PUBLIC',
|
|
339
|
+
confidence: 0,
|
|
340
|
+
detectedPatterns: [],
|
|
341
|
+
redactionRequired: false,
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Scan raw skill content for threats without reading from disc.
|
|
348
|
+
*
|
|
349
|
+
* Useful when the content is already in memory (e.g. received via API,
|
|
350
|
+
* read from a database, or extracted from a larger document).
|
|
351
|
+
*
|
|
352
|
+
* @param content Raw file content to scan
|
|
353
|
+
* @param options Scan options (mode, includeContent)
|
|
354
|
+
* @param format Optional format hint — auto-detected as 'unknown' if omitted
|
|
355
|
+
* @param name Optional skill name — defaults to 'inline'
|
|
356
|
+
*/
|
|
357
|
+
export function scanSkillContent(content, options, format, name) {
|
|
358
|
+
try {
|
|
359
|
+
const parsed = parseSkillFile(content, format);
|
|
360
|
+
// Override name if provided
|
|
361
|
+
if (name) {
|
|
362
|
+
parsed.name = name;
|
|
363
|
+
}
|
|
364
|
+
else if (parsed.name === 'unknown') {
|
|
365
|
+
parsed.name = 'inline';
|
|
366
|
+
}
|
|
367
|
+
return scanParsed(parsed, options);
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
// Return a safe default on any unexpected error
|
|
371
|
+
const effectiveName = name ?? 'inline';
|
|
372
|
+
const startTime = Date.now();
|
|
373
|
+
return {
|
|
374
|
+
safe: true,
|
|
375
|
+
skillName: effectiveName,
|
|
376
|
+
format: format ?? 'unknown',
|
|
377
|
+
findings: [],
|
|
378
|
+
riskLevel: 'safe',
|
|
379
|
+
summary: `${effectiveName}: scan failed — unexpected error`,
|
|
380
|
+
scanDurationMs: Date.now() - startTime,
|
|
381
|
+
firewall: {
|
|
382
|
+
result: 'ALLOW',
|
|
383
|
+
reason: 'Scan failed — unexpected error',
|
|
384
|
+
threatIndicators: [],
|
|
385
|
+
anomalyScore: 0,
|
|
386
|
+
blockedPatterns: [],
|
|
387
|
+
},
|
|
388
|
+
sensitivity: {
|
|
389
|
+
level: 'PUBLIC',
|
|
390
|
+
confidence: 0,
|
|
391
|
+
detectedPatterns: [],
|
|
392
|
+
redactionRequired: false,
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
//# sourceMappingURL=scan-skill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan-skill.js","sourceRoot":"","sources":["../../../src/defence/skill-scanner/scan-skill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAQ9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEtE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA8C5D,gFAAgF;AAEhF,MAAM,mBAAmB,GAA2B;IAClD,0BAA0B;IAC1B,cAAc,EAAE,uDAAuD;IACvE,gBAAgB,EAAE,wDAAwD;IAC1E,iBAAiB,EAAE,gDAAgD;IACnE,WAAW,EAAE,8CAA8C;IAC3D,YAAY,EAAE,yDAAyD;IACvE,kBAAkB,EAAE,sDAAsD;IAC1E,mBAAmB,EAAE,6CAA6C;IAElE,gBAAgB;IAChB,iBAAiB,EAAE,wDAAwD;IAC3E,eAAe,EAAE,mDAAmD;IACpE,iBAAiB,EAAE,qCAAqC;IACxD,cAAc,EAAE,6CAA6C;IAE7D,6BAA6B;IAC7B,qBAAqB,EAAE,uCAAuC;IAC9D,oBAAoB,EAAE,wCAAwC;IAC9D,oBAAoB,EAAE,wCAAwC;IAC9D,eAAe,EAAE,yCAAyC;IAC1D,YAAY,EAAE,sCAAsC;IACpD,kBAAkB,EAAE,sCAAsC;IAC1D,cAAc,EAAE,uCAAuC;CACxD,CAAC;AAEF,gFAAgF;AAEhF,uCAAuC;AACvC,MAAM,aAAa,GAA2B;IAC5C,GAAG,EAAE,CAAC;IACN,MAAM,EAAE,CAAC;IACT,IAAI,EAAE,CAAC;IACP,QAAQ,EAAE,CAAC;CACZ,CAAC;AAEF;;GAEG;AACH,SAAS,sBAAsB,CAC7B,cAAsB,EACtB,SAA0B;IAE1B,IAAI,cAAc,KAAK,OAAO,EAAE,CAAC;QAC/B,IAAI,SAAS,KAAK,uBAAuB,IAAI,SAAS,KAAK,iBAAiB,EAAE,CAAC;YAC7E,OAAO,UAAU,CAAC;QACpB,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,IAAI,cAAc,KAAK,YAAY,EAAE,CAAC;QACpC,IAAI,SAAS,KAAK,uBAAuB,EAAE,CAAC;YAC1C,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAS,yBAAyB,CAAC,UAAkB;IACnD,IAAI,UAAU,IAAI,IAAI;QAAE,OAAO,MAAM,CAAC;IACtC,IAAI,UAAU,IAAI,GAAG;QAAE,OAAO,QAAQ,CAAC;IACvC,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CACtB,QAA8B;IAE9B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IAEzC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,WAAW,GAA2C,KAAK,CAAC;IAEhE,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAClD,IAAI,IAAI,GAAG,OAAO,EAAE,CAAC;YACnB,OAAO,GAAG,IAAI,CAAC;YACf,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,CAAC;IACH,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,OAAe;IACrC,OAAO,mBAAmB,CAAC,OAAO,CAAC,IAAI,4BAA4B,OAAO,EAAE,CAAC;AAC/E,CAAC;AAED;;GAEG;AACH,SAAS,QAAQ,CAAC,IAAY,EAAE,SAAiB;IAC/C,IAAI,IAAI,CAAC,MAAM,IAAI,SAAS;QAAE,OAAO,IAAI,CAAC;IAC1C,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC;AACjD,CAAC;AAED,gFAAgF;AAEhF;;GAEG;AACH,SAAS,WAAW,CAAC,IAA2C;IAC9D,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,sBAAsB,CAAC,IAAI,EAAE,CAAC;QAClD,OAAO,sBAAsB,CAAC;IAChC,CAAC;IACD,OAAO,EAAE,GAAG,sBAAsB,EAAE,IAAI,EAAE,CAAC;AAC7C,CAAC;AAED,gFAAgF;AAEhF;;GAEG;AACH,SAAS,UAAU,CACjB,MAAmB,EACnB,OAA0B;IAE1B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,KAAK,CAAC;IACxD,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAE1C,MAAM,QAAQ,GAAyB,EAAE,CAAC;IAE1C,oEAAoE;IACpE,MAAM,MAAM,GAAkB,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IACxE,MAAM,QAAQ,GAAG,eAAe,CAC9B,MAAM,CAAC,OAAO,EACd,MAAM,CAAC,IAAI,EACX,MAAM,EACN,GAAG,EACH,MAAM,CACP,CAAC;IAEF,gCAAgC;IAChC,MAAM,WAAW,GAAG,mBAAmB,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IAErE,sDAAsD;IACtD,IAAI,QAAQ,CAAC,MAAM,KAAK,OAAO,IAAI,QAAQ,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;QACpE,KAAK,MAAM,SAAS,IAAI,QAAQ,CAAC,gBAAgB,EAAE,CAAC;YAClD,MAAM,QAAQ,GAAG,sBAAsB,CAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;YACpE,MAAM,OAAO,GAAuB;gBAClC,OAAO,EAAE,SAAS;gBAClB,QAAQ;gBACR,WAAW,EAAE,cAAc,CAAC,SAAS,CAAC;aACvC,CAAC;YAEF,IAAI,cAAc,IAAI,QAAQ,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1D,OAAO,CAAC,WAAW,GAAG,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAClE,CAAC;YAED,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;QAED,sEAAsE;QACtE,IAAI,QAAQ,CAAC,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3C,QAAQ,CAAC,IAAI,CAAC;gBACZ,OAAO,EAAE,uBAAuB;gBAChC,QAAQ,EAAE,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ;gBACzD,WAAW,EAAE,QAAQ,CAAC,MAAM;aAC7B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,qCAAqC;IACrC,MAAM,WAAW,GAAG,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACvD,gBAAgB,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAExE,oDAAoD;IACpD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,eAAe,EAAE,CAAC;QACrE,MAAM,UAAU,GAAG,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACrD,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IACzE,CAAC;IAED,qCAAqC;IACrC,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/D,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACpD,MAAM,UAAU,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;YACnD,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;QACtE,CAAC;QAAC,MAAM,CAAC;YACP,6CAA6C;QAC/C,CAAC;IACH,CAAC;IAED,yCAAyC;IACzC,IAAI,WAAW,CAAC,KAAK,KAAK,YAAY,EAAE,CAAC;QACvC,QAAQ,CAAC,IAAI,CAAC;YACZ,OAAO,EAAE,iBAAiB;YAC1B,QAAQ,EAAE,MAAM;YAChB,WAAW,EAAE,gCAAgC,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;SACvF,CAAC,CAAC;IACL,CAAC;IAED,kEAAkE;IAClE,MAAM,OAAO,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAE9C,0CAA0C;IAC1C,MAAM,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CACxB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,IAAI,CAAC,CAAC,QAAQ,KAAK,UAAU,CAC1D,CAAC;IAEF,uBAAuB;IACvB,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IAEvE,OAAO;QACL,IAAI;QACJ,SAAS,EAAE,MAAM,CAAC,IAAI;QACtB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,QAAQ,EAAE,OAAO;QACjB,SAAS;QACT,OAAO;QACP,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;QACtC,QAAQ;QACR,WAAW;KACZ,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB,CACvB,QAA8B,EAC9B,MAAyB,EACzB,OAAe,EACf,cAAuB;IAEvB,IAAI,CAAC,MAAM,CAAC,QAAQ;QAAE,OAAO;IAE7B,MAAM,QAAQ,GAAG,yBAAyB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAE9D,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACpC,MAAM,OAAO,GAAuB;YAClC,OAAO,EAAE,MAAM;YACf,QAAQ;YACR,WAAW,EAAE,cAAc,CAAC,MAAM,CAAC;SACpC,CAAC;QAEF,IAAI,cAAc,EAAE,CAAC;YACnB,kDAAkD;YAClD,MAAM,OAAO,GAAG,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACtD,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,WAAW,GAAG,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC;QAED,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,oBAAoB,CAAC,OAAe,EAAE,MAAc;IAC3D,MAAM,QAAQ,GAA6B;QACzC,cAAc,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC;QACxE,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC;QAClE,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC;QACzE,WAAW,EAAE,CAAC,SAAS,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC;QACrE,YAAY,EAAE,CAAC,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,CAAC;QAC1E,kBAAkB,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,CAAC;QACvE,mBAAmB,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC;QACpC,iBAAiB,EAAE,CAAC,eAAe,EAAE,SAAS,EAAE,QAAQ,CAAC;QACzD,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC;QACtD,iBAAiB,EAAE,CAAC,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC;QAC5D,cAAc,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,CAAC;KACzD,CAAC;IAEF,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEtB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAEnC,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;YACrB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;gBACrD,OAAO,OAAO,CAAC;YACjB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB,CAAC,QAA8B;IACzD,MAAM,SAAS,GAAG,IAAI,GAAG,EAA8B,CAAC;IAExD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC1C,CAAC;aAAM,CAAC;YACN,MAAM,YAAY,GAAG,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC3D,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YACrD,IAAI,OAAO,GAAG,YAAY,EAAE,CAAC;gBAC3B,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;AACjC,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CACtB,IAAY,EACZ,QAA8B,EAC9B,SAA0D,EAC1D,IAAa;IAEb,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,GAAG,IAAI,uBAAuB,CAAC;IACxC,CAAC;IAED,MAAM,cAAc,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;IACnE,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC/B,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,cAAc,CAAC,QAAQ,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,QAAQ,WAAW,CAAC,CAAC;IACnF,IAAI,cAAc,CAAC,IAAI,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,IAAI,OAAO,CAAC,CAAC;IACvE,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,MAAM,SAAS,CAAC,CAAC;IAC7E,IAAI,cAAc,CAAC,GAAG,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,GAAG,MAAM,CAAC,CAAC;IAEpE,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,QAAQ,CAAC;IAEzD,OAAO,GAAG,IAAI,KAAK,QAAQ,CAAC,MAAM,gBAAgB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,SAAS,EAAE,CAAC;AACvF,CAAC;AAED,gFAAgF;AAEhF;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CACvB,QAAgB,EAChB,OAA0B;IAE1B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC3C,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,gDAAgD;QAChD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,OAAO;YACL,IAAI,EAAE,IAAI;YACV,SAAS,EAAE,QAAQ;YACnB,MAAM,EAAE,SAAS;YACjB,QAAQ,EAAE,EAAE;YACZ,SAAS,EAAE,MAAM;YACjB,OAAO,EAAE,GAAG,QAAQ,qCAAqC;YACzD,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;YACtC,QAAQ,EAAE;gBACR,MAAM,EAAE,OAAO;gBACf,MAAM,EAAE,+BAA+B;gBACvC,gBAAgB,EAAE,EAAE;gBACpB,YAAY,EAAE,CAAC;gBACf,eAAe,EAAE,EAAE;aACpB;YACD,WAAW,EAAE;gBACX,KAAK,EAAE,QAAQ;gBACf,UAAU,EAAE,CAAC;gBACb,gBAAgB,EAAE,EAAE;gBACpB,iBAAiB,EAAE,KAAK;aACzB;SACF,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAe,EACf,OAA0B,EAC1B,MAAoB,EACpB,IAAa;IAEb,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAE/C,4BAA4B;QAC5B,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;QACrB,CAAC;aAAM,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACrC,MAAM,CAAC,IAAI,GAAG,QAAQ,CAAC;QACzB,CAAC;QAED,OAAO,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,gDAAgD;QAChD,MAAM,aAAa,GAAG,IAAI,IAAI,QAAQ,CAAC;QACvC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,OAAO;YACL,IAAI,EAAE,IAAI;YACV,SAAS,EAAE,aAAa;YACxB,MAAM,EAAE,MAAM,IAAI,SAAS;YAC3B,QAAQ,EAAE,EAAE;YACZ,SAAS,EAAE,MAAM;YACjB,OAAO,EAAE,GAAG,aAAa,kCAAkC;YAC3D,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;YACtC,QAAQ,EAAE;gBACR,MAAM,EAAE,OAAO;gBACf,MAAM,EAAE,gCAAgC;gBACxC,gBAAgB,EAAE,EAAE;gBACpB,YAAY,EAAE,CAAC;gBACf,eAAe,EAAE,EAAE;aACpB;YACD,WAAW,EAAE;gBACX,KAAK,EAAE,QAAQ;gBACf,UAAU,EAAE,CAAC;gBACb,gBAAgB,EAAE,EAAE;gBACpB,iBAAiB,EAAE,KAAK;aACzB;SACF,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -17,4 +17,9 @@ export declare function isModelLoaded(): boolean;
|
|
|
17
17
|
* Preload the model (call during startup if desired)
|
|
18
18
|
*/
|
|
19
19
|
export declare function preloadModel(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Dispose the embedding model and release ONNX session resources.
|
|
22
|
+
* Call during shutdown to prevent SIGABRT from orphaned ONNX thread pools.
|
|
23
|
+
*/
|
|
24
|
+
export declare function disposeModel(): Promise<void>;
|
|
20
25
|
//# sourceMappingURL=generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/embeddings/generator.ts"],"names":[],"mappings":"AAoCA;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/embeddings/generator.ts"],"names":[],"mappings":"AAoCA;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CA4B3E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,MAAM,CAmBzE;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAElD;AAED;;;GAGG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAWlD"}
|
|
@@ -41,11 +41,24 @@ export async function generateEmbedding(text) {
|
|
|
41
41
|
pooling: 'mean',
|
|
42
42
|
normalize: true,
|
|
43
43
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
try {
|
|
45
|
+
// Prefer .data (direct typed array) over .tolist() to avoid intermediate allocations
|
|
46
|
+
if (output.data && output.data.length > 0) {
|
|
47
|
+
return new Float32Array(output.data);
|
|
48
|
+
}
|
|
49
|
+
// Fallback for older/different Tensor implementations
|
|
50
|
+
if (typeof output.tolist === 'function') {
|
|
51
|
+
const list = output.tolist().flat(Infinity);
|
|
52
|
+
return new Float32Array(list);
|
|
53
|
+
}
|
|
54
|
+
throw new Error('Tensor has no .data or .tolist() — cannot extract embedding');
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
// CRITICAL: Release ONNX native memory backing this tensor
|
|
58
|
+
if (output && typeof output.dispose === 'function') {
|
|
59
|
+
output.dispose();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
49
62
|
}
|
|
50
63
|
/**
|
|
51
64
|
* Calculate cosine similarity between two embeddings
|
|
@@ -80,4 +93,21 @@ export function isModelLoaded() {
|
|
|
80
93
|
export async function preloadModel() {
|
|
81
94
|
await getEmbeddingPipeline();
|
|
82
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Dispose the embedding model and release ONNX session resources.
|
|
98
|
+
* Call during shutdown to prevent SIGABRT from orphaned ONNX thread pools.
|
|
99
|
+
*/
|
|
100
|
+
export async function disposeModel() {
|
|
101
|
+
if (embeddingPipeline) {
|
|
102
|
+
try {
|
|
103
|
+
await embeddingPipeline.dispose();
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Best-effort disposal — don't block shutdown
|
|
107
|
+
}
|
|
108
|
+
embeddingPipeline = null;
|
|
109
|
+
}
|
|
110
|
+
loadPromise = null;
|
|
111
|
+
isLoading = false;
|
|
112
|
+
}
|
|
83
113
|
//# sourceMappingURL=generator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../src/embeddings/generator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAG1D,0BAA0B;AAC1B,GAAG,CAAC,iBAAiB,GAAG,IAAI,CAAC;AAC7B,GAAG,CAAC,gBAAgB,GAAG,IAAI,CAAC;AAE5B,IAAI,iBAAiB,GAAqC,IAAI,CAAC;AAC/D,IAAI,SAAS,GAAG,KAAK,CAAC;AACtB,IAAI,WAAW,GAA8C,IAAI,CAAC;AAElE;;;;;GAKG;AACH,KAAK,UAAU,oBAAoB;IACjC,IAAI,iBAAiB;QAAE,OAAO,iBAAiB,CAAC;IAEhD,IAAI,SAAS,IAAI,WAAW,EAAE,CAAC;QAC7B,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,SAAS,GAAG,IAAI,CAAC;IACjB,WAAW,GAAG,QAAQ,CAAC,oBAAoB,EAAE,yBAAyB,CAAkD,CAAC;IAEzH,IAAI,CAAC;QACH,iBAAiB,GAAG,MAAM,WAAW,CAAC;QACtC,OAAO,iBAAiB,CAAC;IAC3B,CAAC;YAAS,CAAC;QACT,SAAS,GAAG,KAAK,CAAC;QAClB,WAAW,GAAG,IAAI,CAAC;IACrB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAY;IAClD,MAAM,SAAS,GAAG,MAAM,oBAAoB,EAAE,CAAC;IAE/C,+DAA+D;IAC/D,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAEtC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,SAAS,EAAE;QACxC,OAAO,EAAE,MAAM;QACf,SAAS,EAAE,IAAI;KAChB,CAAC,CAAC;IAEH
|
|
1
|
+
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../src/embeddings/generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { generateEmbedding, cosineSimilarity, isModelLoaded, preloadModel } from './generator.js';
|
|
1
|
+
export { generateEmbedding, cosineSimilarity, isModelLoaded, preloadModel, disposeModel } from './generator.js';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/embeddings/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/embeddings/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/embeddings/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { generateEmbedding, cosineSimilarity, isModelLoaded, preloadModel } from './generator.js';
|
|
1
|
+
export { generateEmbedding, cosineSimilarity, isModelLoaded, preloadModel, disposeModel } from './generator.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/embeddings/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/embeddings/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -41,6 +41,8 @@ import { setupClaudeMd } from './setup/claude-md.js';
|
|
|
41
41
|
import { handleHookCommand } from './setup/hooks.js';
|
|
42
42
|
import { handleOpenClawCommand } from './setup/openclaw.js';
|
|
43
43
|
import { createRequire } from 'module';
|
|
44
|
+
import { disposeModel } from './embeddings/index.js';
|
|
45
|
+
import { stopDefaultWorker } from './worker/brain-worker.js';
|
|
44
46
|
const require = createRequire(import.meta.url);
|
|
45
47
|
const pkg = require('../package.json');
|
|
46
48
|
// Get the directory of this file for relative paths
|
|
@@ -77,11 +79,15 @@ async function startMcpServer(dbPath) {
|
|
|
77
79
|
const transport = new StdioServerTransport();
|
|
78
80
|
// Register signal handlers BEFORE connect so cleanup runs even if connect() throws
|
|
79
81
|
process.on('SIGINT', async () => {
|
|
82
|
+
stopDefaultWorker();
|
|
80
83
|
await server.close();
|
|
84
|
+
await disposeModel();
|
|
81
85
|
process.exit(0);
|
|
82
86
|
});
|
|
83
87
|
process.on('SIGTERM', async () => {
|
|
88
|
+
stopDefaultWorker();
|
|
84
89
|
await server.close();
|
|
90
|
+
await disposeModel();
|
|
85
91
|
process.exit(0);
|
|
86
92
|
});
|
|
87
93
|
// Connect via stdio transport
|
|
@@ -294,6 +300,88 @@ async function main() {
|
|
|
294
300
|
}
|
|
295
301
|
return;
|
|
296
302
|
}
|
|
303
|
+
// Handle "scan-skill" subcommand — scan a single skill/instruction file
|
|
304
|
+
if (process.argv[2] === 'scan-skill') {
|
|
305
|
+
const filePath = process.argv[3];
|
|
306
|
+
if (!filePath) {
|
|
307
|
+
console.error('Usage: npx shieldcortex scan-skill <path>');
|
|
308
|
+
console.error(' Scans an agent instruction file for threats.');
|
|
309
|
+
console.error(' Supports: SKILL.md, HOOK.md, handler.js, .cursorrules,');
|
|
310
|
+
console.error(' .windsurfrules, .clinerules, CLAUDE.md, copilot-instructions.md,');
|
|
311
|
+
console.error(' .aider.conf.yml, .continue/config.json');
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
const { scanSkill } = await import('./defence/skill-scanner/index.js');
|
|
315
|
+
const result = scanSkill(filePath);
|
|
316
|
+
// Coloured output
|
|
317
|
+
const severityColor = {
|
|
318
|
+
critical: '\x1b[31m', // red
|
|
319
|
+
high: '\x1b[91m', // bright red
|
|
320
|
+
medium: '\x1b[33m', // yellow
|
|
321
|
+
low: '\x1b[36m', // cyan
|
|
322
|
+
};
|
|
323
|
+
const reset = '\x1b[0m';
|
|
324
|
+
const bold = '\x1b[1m';
|
|
325
|
+
console.log(`\n${bold}Skill Scanner Report${reset}`);
|
|
326
|
+
console.log(`${'─'.repeat(50)}`);
|
|
327
|
+
console.log(` Skill: ${result.skillName}`);
|
|
328
|
+
console.log(` Format: ${result.format}`);
|
|
329
|
+
console.log(` Risk: ${result.safe ? '\x1b[32mSAFE\x1b[0m' : `${severityColor[result.riskLevel] ?? ''}${result.riskLevel.toUpperCase()}${reset}`}`);
|
|
330
|
+
console.log(` Time: ${result.scanDurationMs}ms`);
|
|
331
|
+
if (result.findings.length > 0) {
|
|
332
|
+
console.log(`\n${bold}Findings (${result.findings.length}):${reset}`);
|
|
333
|
+
for (const f of result.findings) {
|
|
334
|
+
const color = severityColor[f.severity] ?? '';
|
|
335
|
+
console.log(` ${color}[${f.severity.toUpperCase()}]${reset} ${f.pattern}`);
|
|
336
|
+
console.log(` ${f.description}`);
|
|
337
|
+
if (f.matchedText) {
|
|
338
|
+
console.log(` Match: "${f.matchedText}"`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
console.log(`\n \x1b[32mNo threats detected.\x1b[0m`);
|
|
344
|
+
}
|
|
345
|
+
console.log(`\n${result.summary}\n`);
|
|
346
|
+
process.exit(result.safe ? 0 : 1);
|
|
347
|
+
}
|
|
348
|
+
// Handle "scan-skills" subcommand — scan all installed skills/hooks
|
|
349
|
+
if (process.argv[2] === 'scan-skills') {
|
|
350
|
+
const { scanSkill, discoverSkillFiles } = await import('./defence/skill-scanner/index.js');
|
|
351
|
+
const customDir = process.argv.indexOf('--dir') !== -1
|
|
352
|
+
? process.argv[process.argv.indexOf('--dir') + 1]
|
|
353
|
+
: undefined;
|
|
354
|
+
const filesToScan = discoverSkillFiles(customDir);
|
|
355
|
+
if (filesToScan.length === 0) {
|
|
356
|
+
console.log('\nNo agent instruction files found to scan.');
|
|
357
|
+
console.log('Checked: Claude Code skills, OpenClaw hooks, .cursorrules, CLAUDE.md, and more.');
|
|
358
|
+
console.log('Use --dir <path> to scan a custom directory.\n');
|
|
359
|
+
process.exit(0);
|
|
360
|
+
}
|
|
361
|
+
const bold = '\x1b[1m';
|
|
362
|
+
const reset = '\x1b[0m';
|
|
363
|
+
console.log(`\n${bold}Scanning ${filesToScan.length} file(s)...${reset}\n`);
|
|
364
|
+
let threatCount = 0;
|
|
365
|
+
const results = [];
|
|
366
|
+
for (const fp of filesToScan) {
|
|
367
|
+
const result = scanSkill(fp);
|
|
368
|
+
results.push({ path: fp, safe: result.safe, riskLevel: result.riskLevel, summary: result.summary });
|
|
369
|
+
if (!result.safe)
|
|
370
|
+
threatCount++;
|
|
371
|
+
const icon = result.safe ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
|
|
372
|
+
const risk = result.safe ? '\x1b[32msafe\x1b[0m' : `\x1b[31m${result.riskLevel}\x1b[0m`;
|
|
373
|
+
console.log(` ${icon} ${risk.padEnd(20)} ${fp}`);
|
|
374
|
+
if (!result.safe) {
|
|
375
|
+
for (const f of result.findings) {
|
|
376
|
+
if (f.severity === 'high' || f.severity === 'critical') {
|
|
377
|
+
console.log(` \x1b[31m[${f.severity}]\x1b[0m ${f.description}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
console.log(`\n${bold}Summary:${reset} ${filesToScan.length} scanned, ${threatCount} with threats\n`);
|
|
383
|
+
process.exit(threatCount > 0 ? 1 : 0);
|
|
384
|
+
}
|
|
297
385
|
const { dbPath, mode } = parseArgs();
|
|
298
386
|
let dashboardProcess = null;
|
|
299
387
|
if (mode === 'api') {
|