shellward 0.5.10 → 0.5.11
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 +99 -14
- package/dist/audit-log.d.ts +8 -0
- package/dist/audit-log.js +72 -0
- package/dist/auto-check.d.ts +26 -0
- package/dist/auto-check.js +167 -0
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.js +75 -0
- package/dist/commands/check-updates.d.ts +2 -0
- package/dist/commands/check-updates.js +166 -0
- package/dist/commands/harden.d.ts +2 -0
- package/dist/commands/harden.js +218 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +56 -0
- package/dist/commands/scan-plugins.d.ts +2 -0
- package/dist/commands/scan-plugins.js +186 -0
- package/dist/commands/security.d.ts +2 -0
- package/dist/commands/security.js +109 -0
- package/dist/commands/upgrade-openclaw.d.ts +2 -0
- package/dist/commands/upgrade-openclaw.js +54 -0
- package/dist/core/engine.d.ts +66 -0
- package/dist/core/engine.js +572 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +137 -0
- package/dist/layers/data-flow-guard.d.ts +2 -0
- package/dist/layers/data-flow-guard.js +23 -0
- package/dist/layers/input-auditor.d.ts +2 -0
- package/dist/layers/input-auditor.js +33 -0
- package/dist/layers/outbound-guard.d.ts +2 -0
- package/dist/layers/outbound-guard.js +22 -0
- package/dist/layers/output-scanner.d.ts +2 -0
- package/dist/layers/output-scanner.js +16 -0
- package/dist/layers/prompt-guard.d.ts +2 -0
- package/dist/layers/prompt-guard.js +14 -0
- package/dist/layers/security-gate.d.ts +2 -0
- package/dist/layers/security-gate.js +49 -0
- package/dist/layers/session-guard.d.ts +2 -0
- package/dist/layers/session-guard.js +34 -0
- package/dist/layers/tool-blocker.d.ts +2 -0
- package/dist/layers/tool-blocker.js +28 -0
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +337 -0
- package/dist/rules/dangerous-commands.d.ts +8 -0
- package/dist/rules/dangerous-commands.js +113 -0
- package/dist/rules/injection-en.d.ts +2 -0
- package/dist/rules/injection-en.js +115 -0
- package/dist/rules/injection-zh.d.ts +2 -0
- package/dist/rules/injection-zh.js +132 -0
- package/dist/rules/protected-paths.d.ts +2 -0
- package/dist/rules/protected-paths.js +75 -0
- package/dist/rules/sensitive-patterns.d.ts +21 -0
- package/dist/rules/sensitive-patterns.js +192 -0
- package/dist/types.d.ts +64 -0
- package/dist/types.js +30 -0
- package/dist/update-check.d.ts +40 -0
- package/dist/update-check.js +147 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +8 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +18 -6
- package/src/audit-log.ts +8 -4
- package/src/auto-check.ts +2 -2
- package/src/commands/audit.ts +3 -3
- package/src/commands/check-updates.ts +4 -4
- package/src/commands/harden.ts +3 -3
- package/src/commands/index.ts +8 -8
- package/src/commands/scan-plugins.ts +3 -3
- package/src/commands/security.ts +3 -3
- package/src/commands/upgrade-openclaw.ts +2 -2
- package/src/core/engine.ts +8 -8
- package/src/index.ts +15 -15
- package/src/layers/data-flow-guard.ts +1 -1
- package/src/layers/input-auditor.ts +1 -1
- package/src/layers/outbound-guard.ts +1 -1
- package/src/layers/output-scanner.ts +1 -1
- package/src/layers/prompt-guard.ts +1 -1
- package/src/layers/security-gate.ts +1 -1
- package/src/layers/session-guard.ts +1 -1
- package/src/layers/tool-blocker.ts +1 -1
- package/src/mcp-server.ts +386 -0
- package/src/rules/dangerous-commands.ts +1 -1
- package/src/rules/injection-en.ts +1 -1
- package/src/rules/injection-zh.ts +1 -1
- package/src/rules/protected-paths.ts +1 -1
- package/src/rules/sensitive-patterns.ts +1 -1
- package/src/update-check.ts +1 -1
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
// src/mcp-server.ts — ShellWard MCP Server
|
|
3
|
+
//
|
|
4
|
+
// Exposes ShellWard's 8-layer security engine as an MCP server.
|
|
5
|
+
// Zero dependencies — implements MCP protocol over stdio natively.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// npx tsx src/mcp-server.ts
|
|
9
|
+
//
|
|
10
|
+
// MCP config (claude_desktop_config.json / openclaw settings):
|
|
11
|
+
// {
|
|
12
|
+
// "mcpServers": {
|
|
13
|
+
// "shellward": {
|
|
14
|
+
// "command": "npx",
|
|
15
|
+
// "args": ["tsx", "/path/to/shellward/src/mcp-server.ts"]
|
|
16
|
+
// }
|
|
17
|
+
// }
|
|
18
|
+
// }
|
|
19
|
+
import { ShellWard } from './core/engine.js';
|
|
20
|
+
import { readFileSync } from 'fs';
|
|
21
|
+
import { fileURLToPath } from 'url';
|
|
22
|
+
import { dirname, join } from 'path';
|
|
23
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
|
|
25
|
+
// ===== ShellWard Instance =====
|
|
26
|
+
const guard = new ShellWard({
|
|
27
|
+
mode: process.env.SHELLWARD_MODE || 'enforce',
|
|
28
|
+
locale: process.env.SHELLWARD_LOCALE || 'auto',
|
|
29
|
+
autoCheckOnStartup: false,
|
|
30
|
+
layers: {
|
|
31
|
+
promptGuard: true,
|
|
32
|
+
outputScanner: true,
|
|
33
|
+
toolBlocker: true,
|
|
34
|
+
inputAuditor: true,
|
|
35
|
+
securityGate: true,
|
|
36
|
+
outboundGuard: true,
|
|
37
|
+
dataFlowGuard: true,
|
|
38
|
+
sessionGuard: true,
|
|
39
|
+
},
|
|
40
|
+
injectionThreshold: Number(process.env.SHELLWARD_THRESHOLD) || 60,
|
|
41
|
+
});
|
|
42
|
+
// ===== Tool Definitions =====
|
|
43
|
+
const TOOLS = [
|
|
44
|
+
{
|
|
45
|
+
name: 'check_command',
|
|
46
|
+
description: 'Check if a shell command is safe to execute. Detects rm -rf, reverse shells, fork bombs, curl|sh, etc.',
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
command: { type: 'string', description: 'The shell command to check' },
|
|
51
|
+
},
|
|
52
|
+
required: ['command'],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'check_injection',
|
|
57
|
+
description: 'Detect prompt injection attempts in text. Supports 32+ rules for Chinese and English, with hidden character detection.',
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: {
|
|
61
|
+
text: { type: 'string', description: 'Text to scan for injection attempts' },
|
|
62
|
+
threshold: { type: 'number', description: 'Detection threshold 0-100 (default: 60, lower = stricter)' },
|
|
63
|
+
},
|
|
64
|
+
required: ['text'],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'scan_data',
|
|
69
|
+
description: 'Scan text for sensitive data: PII (Chinese ID cards, phone numbers, bank cards), API keys, passwords, private keys, JWT tokens, SSN, credit cards.',
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
text: { type: 'string', description: 'Text to scan for sensitive data' },
|
|
74
|
+
},
|
|
75
|
+
required: ['text'],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'check_path',
|
|
80
|
+
description: 'Check if a file path operation is safe. Protects .env, .ssh/, .aws/credentials, private keys, /etc/passwd, etc.',
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
path: { type: 'string', description: 'File path to check' },
|
|
85
|
+
operation: { type: 'string', enum: ['write', 'delete'], description: 'Operation type' },
|
|
86
|
+
},
|
|
87
|
+
required: ['path', 'operation'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'check_tool',
|
|
92
|
+
description: 'Check if a tool name is allowed. Blocks payment/transfer tools, flags exec/shell tools as sensitive.',
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
tool_name: { type: 'string', description: 'Tool name to check (e.g. "bash", "stripe_charge", "file_read")' },
|
|
97
|
+
},
|
|
98
|
+
required: ['tool_name'],
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'check_response',
|
|
103
|
+
description: 'Check an AI response for security issues: canary token leaks and sensitive data exposure.',
|
|
104
|
+
inputSchema: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: {
|
|
107
|
+
content: { type: 'string', description: 'Response content to check' },
|
|
108
|
+
},
|
|
109
|
+
required: ['content'],
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'security_status',
|
|
114
|
+
description: 'Get current ShellWard security status: mode, active layers, detection capabilities.',
|
|
115
|
+
inputSchema: {
|
|
116
|
+
type: 'object',
|
|
117
|
+
properties: {},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
// ===== Tool Execution =====
|
|
122
|
+
function executeTool(name, args) {
|
|
123
|
+
switch (name) {
|
|
124
|
+
case 'check_command': {
|
|
125
|
+
const result = guard.checkCommand(String(args.command || ''));
|
|
126
|
+
return {
|
|
127
|
+
safe: result.allowed,
|
|
128
|
+
level: result.level || null,
|
|
129
|
+
reason: result.reason || null,
|
|
130
|
+
rule_id: result.ruleId || null,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
case 'check_injection': {
|
|
134
|
+
const opts = typeof args.threshold === 'number' ? { threshold: args.threshold } : undefined;
|
|
135
|
+
const result = guard.checkInjection(String(args.text || ''), opts);
|
|
136
|
+
return {
|
|
137
|
+
safe: result.safe,
|
|
138
|
+
score: result.score,
|
|
139
|
+
threshold: result.threshold,
|
|
140
|
+
matched_rules: result.matched.map((m) => ({
|
|
141
|
+
id: m.id,
|
|
142
|
+
name: m.name,
|
|
143
|
+
score: m.score,
|
|
144
|
+
})),
|
|
145
|
+
hidden_chars: result.hiddenChars,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
case 'scan_data': {
|
|
149
|
+
const result = guard.scanData(String(args.text || ''));
|
|
150
|
+
return {
|
|
151
|
+
has_sensitive_data: result.hasSensitiveData,
|
|
152
|
+
findings: result.findings.map((f) => ({
|
|
153
|
+
type: f.id,
|
|
154
|
+
name: f.name,
|
|
155
|
+
count: f.count,
|
|
156
|
+
})),
|
|
157
|
+
summary: result.summary,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
case 'check_path': {
|
|
161
|
+
const op = String(args.operation || '');
|
|
162
|
+
if (op !== 'write' && op !== 'delete') {
|
|
163
|
+
throw new Error(`Invalid operation: "${op}". Must be "write" or "delete".`);
|
|
164
|
+
}
|
|
165
|
+
const result = guard.checkPath(String(args.path || ''), op);
|
|
166
|
+
return {
|
|
167
|
+
safe: result.allowed,
|
|
168
|
+
level: result.level || null,
|
|
169
|
+
reason: result.reason || null,
|
|
170
|
+
rule_id: result.ruleId || null,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
case 'check_tool': {
|
|
174
|
+
const result = guard.checkTool(String(args.tool_name || ''));
|
|
175
|
+
return {
|
|
176
|
+
allowed: result.allowed,
|
|
177
|
+
level: result.level || null,
|
|
178
|
+
reason: result.reason || null,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
case 'check_response': {
|
|
182
|
+
const result = guard.checkResponse(String(args.content || ''));
|
|
183
|
+
return {
|
|
184
|
+
canary_leak: result.canaryLeak,
|
|
185
|
+
has_sensitive_data: result.sensitiveData.hasSensitiveData,
|
|
186
|
+
findings: result.sensitiveData.findings.map(f => ({
|
|
187
|
+
type: f.id,
|
|
188
|
+
name: f.name,
|
|
189
|
+
count: f.count,
|
|
190
|
+
})),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
case 'security_status': {
|
|
194
|
+
return {
|
|
195
|
+
mode: guard.config.mode,
|
|
196
|
+
locale: guard.locale,
|
|
197
|
+
injection_threshold: guard.config.injectionThreshold,
|
|
198
|
+
layers: guard.config.layers,
|
|
199
|
+
capabilities: [
|
|
200
|
+
'command_safety_check (17 dangerous patterns)',
|
|
201
|
+
'prompt_injection_detection (32+ rules, zh+en)',
|
|
202
|
+
'pii_detection (CN ID/phone/bank + global)',
|
|
203
|
+
'path_protection (12 protected patterns)',
|
|
204
|
+
'tool_policy (block payment/transfer)',
|
|
205
|
+
'response_audit (canary + PII)',
|
|
206
|
+
'data_flow_tracking (DLP)',
|
|
207
|
+
],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
default:
|
|
211
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// ===== MCP Protocol Handlers =====
|
|
215
|
+
function handleRequest(req) {
|
|
216
|
+
const { id, method, params } = req;
|
|
217
|
+
switch (method) {
|
|
218
|
+
case 'initialize':
|
|
219
|
+
return {
|
|
220
|
+
jsonrpc: '2.0',
|
|
221
|
+
id: id ?? null,
|
|
222
|
+
result: {
|
|
223
|
+
protocolVersion: '2024-11-05',
|
|
224
|
+
capabilities: { tools: {} },
|
|
225
|
+
serverInfo: {
|
|
226
|
+
name: 'shellward',
|
|
227
|
+
version: pkg.version,
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
case 'notifications/initialized':
|
|
232
|
+
// Client acknowledgement, no response needed
|
|
233
|
+
return null;
|
|
234
|
+
case 'tools/list':
|
|
235
|
+
return {
|
|
236
|
+
jsonrpc: '2.0',
|
|
237
|
+
id: id ?? null,
|
|
238
|
+
result: { tools: TOOLS },
|
|
239
|
+
};
|
|
240
|
+
case 'resources/list':
|
|
241
|
+
return { jsonrpc: '2.0', id: id ?? null, result: { resources: [] } };
|
|
242
|
+
case 'prompts/list':
|
|
243
|
+
return { jsonrpc: '2.0', id: id ?? null, result: { prompts: [] } };
|
|
244
|
+
case 'tools/call': {
|
|
245
|
+
const toolName = params?.name;
|
|
246
|
+
const toolArgs = (params?.arguments || {});
|
|
247
|
+
try {
|
|
248
|
+
const result = executeTool(toolName, toolArgs);
|
|
249
|
+
return {
|
|
250
|
+
jsonrpc: '2.0',
|
|
251
|
+
id: id ?? null,
|
|
252
|
+
result: {
|
|
253
|
+
content: [
|
|
254
|
+
{
|
|
255
|
+
type: 'text',
|
|
256
|
+
text: JSON.stringify(result, null, 2),
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
return {
|
|
264
|
+
jsonrpc: '2.0',
|
|
265
|
+
id: id ?? null,
|
|
266
|
+
result: {
|
|
267
|
+
content: [
|
|
268
|
+
{
|
|
269
|
+
type: 'text',
|
|
270
|
+
text: JSON.stringify({ error: err.message }),
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
isError: true,
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
case 'ping':
|
|
279
|
+
return { jsonrpc: '2.0', id: id ?? null, result: {} };
|
|
280
|
+
default:
|
|
281
|
+
// Unknown methods — return error for requests with id, ignore notifications
|
|
282
|
+
if (id !== undefined) {
|
|
283
|
+
return {
|
|
284
|
+
jsonrpc: '2.0',
|
|
285
|
+
id: id ?? null,
|
|
286
|
+
error: { code: -32601, message: `Method not found: ${method}` },
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// ===== Stdio Transport =====
|
|
293
|
+
// Use raw Buffer to handle UTF-8 multi-byte characters correctly.
|
|
294
|
+
// Content-Length is in bytes, not characters.
|
|
295
|
+
let rawBuffer = Buffer.alloc(0);
|
|
296
|
+
process.stdin.on('data', (chunk) => {
|
|
297
|
+
rawBuffer = Buffer.concat([rawBuffer, chunk]);
|
|
298
|
+
while (true) {
|
|
299
|
+
const headerEnd = rawBuffer.indexOf('\r\n\r\n');
|
|
300
|
+
if (headerEnd === -1)
|
|
301
|
+
break;
|
|
302
|
+
const header = rawBuffer.slice(0, headerEnd).toString('ascii');
|
|
303
|
+
const lengthMatch = header.match(/Content-Length:\s*(\d+)/i);
|
|
304
|
+
if (!lengthMatch) {
|
|
305
|
+
rawBuffer = rawBuffer.slice(headerEnd + 4);
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
const contentLength = parseInt(lengthMatch[1], 10);
|
|
309
|
+
const bodyStart = headerEnd + 4;
|
|
310
|
+
if (rawBuffer.length < bodyStart + contentLength)
|
|
311
|
+
break;
|
|
312
|
+
const body = rawBuffer.slice(bodyStart, bodyStart + contentLength).toString('utf8');
|
|
313
|
+
rawBuffer = rawBuffer.slice(bodyStart + contentLength);
|
|
314
|
+
try {
|
|
315
|
+
const req = JSON.parse(body);
|
|
316
|
+
const res = handleRequest(req);
|
|
317
|
+
if (res) {
|
|
318
|
+
send(res);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
send({
|
|
323
|
+
jsonrpc: '2.0',
|
|
324
|
+
id: null,
|
|
325
|
+
error: { code: -32700, message: 'Parse error' },
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
function send(msg) {
|
|
331
|
+
const body = JSON.stringify(msg);
|
|
332
|
+
const header = `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n`;
|
|
333
|
+
process.stdout.write(header + body);
|
|
334
|
+
}
|
|
335
|
+
process.stdin.on('end', () => process.exit(0));
|
|
336
|
+
// Log to stderr so it doesn't interfere with stdio protocol
|
|
337
|
+
process.stderr.write(`[ShellWard MCP] Server started (mode: ${guard.config.mode}, locale: ${guard.locale})\n`);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DangerousCommandRule } from '../types.js';
|
|
2
|
+
export declare const DANGEROUS_COMMANDS: DangerousCommandRule[];
|
|
3
|
+
/**
|
|
4
|
+
* Normalize command string before pattern matching:
|
|
5
|
+
* - Split on command separators (;, &&, ||, \n) and check each part
|
|
6
|
+
* - Trim whitespace
|
|
7
|
+
*/
|
|
8
|
+
export declare function splitCommands(cmd: string): string[];
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// src/rules/dangerous-commands.ts — Shell command blocklist (bilingual)
|
|
2
|
+
export const DANGEROUS_COMMANDS = [
|
|
3
|
+
{
|
|
4
|
+
id: 'rm_rf_root',
|
|
5
|
+
pattern: /rm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+-[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*\s+-[a-zA-Z]*r|-[a-zA-Z]*rf[a-zA-Z]*)\s+[\/~]/i,
|
|
6
|
+
description_zh: '递归强制删除根目录或用户目录',
|
|
7
|
+
description_en: 'Recursive force delete on root or home directory',
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
id: 'rm_rf_wildcard',
|
|
11
|
+
pattern: /rm\s+(-[a-zA-Z]*rf|-[a-zA-Z]*fr)\s+\*/i,
|
|
12
|
+
description_zh: '递归强制删除通配符匹配的所有文件',
|
|
13
|
+
description_en: 'Recursive force delete with wildcard',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'mkfs',
|
|
17
|
+
pattern: /mkfs\b/i,
|
|
18
|
+
description_zh: '格式化磁盘',
|
|
19
|
+
description_en: 'Format disk partition',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'dd_if',
|
|
23
|
+
pattern: /dd\s+if=/i,
|
|
24
|
+
description_zh: '低级磁盘操作(可能覆盖磁盘数据)',
|
|
25
|
+
description_en: 'Low-level disk operation (may overwrite data)',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'curl_pipe_sh',
|
|
29
|
+
pattern: /curl\s+[^|]*\|\s*(?:sudo\s+)?(?:ba)?sh/i,
|
|
30
|
+
description_zh: '从网络下载并直接执行脚本',
|
|
31
|
+
description_en: 'Download and pipe to shell execution',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'wget_pipe_sh',
|
|
35
|
+
pattern: /wget\s+[^|]*\|\s*(?:sudo\s+)?(?:ba)?sh/i,
|
|
36
|
+
description_zh: '从网络下载并直接执行脚本',
|
|
37
|
+
description_en: 'Download and pipe to shell execution',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'dev_write',
|
|
41
|
+
pattern: />\s*\/dev\/[sh]d[a-z]/i,
|
|
42
|
+
description_zh: '直接写入磁盘设备',
|
|
43
|
+
description_en: 'Direct write to disk device',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'chmod_777',
|
|
47
|
+
pattern: /chmod\s+(-[a-zA-Z]*\s+)?777(?:\s|$)/i,
|
|
48
|
+
description_zh: '设置全局可读写可执行权限',
|
|
49
|
+
description_en: 'Set world-readable/writable/executable permissions',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'kill_all',
|
|
53
|
+
pattern: /killall\s+-9|kill\s+-9\s+-1/i,
|
|
54
|
+
description_zh: '强制终止所有进程',
|
|
55
|
+
description_en: 'Force kill all processes',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'iptables_flush',
|
|
59
|
+
pattern: /iptables\s+-F/i,
|
|
60
|
+
description_zh: '清空防火墙规则',
|
|
61
|
+
description_en: 'Flush all firewall rules',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'history_clear',
|
|
65
|
+
pattern: /history\s+-c|>\s*~\/\.bash_history|>\s*~\/\.zsh_history/i,
|
|
66
|
+
description_zh: '清除命令历史(可能是攻击后清痕迹)',
|
|
67
|
+
description_en: 'Clear command history (potential post-attack cleanup)',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'fork_bomb',
|
|
71
|
+
pattern: /:\(\)\s*\{\s*:\|:&\s*\}\s*;?|\.\/[a-z]+\s*&\s*\.\/[a-z]+/i,
|
|
72
|
+
description_zh: 'Fork 炸弹(耗尽系统资源)',
|
|
73
|
+
description_en: 'Fork bomb (exhaust system resources)',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'eval_base64',
|
|
77
|
+
pattern: /eval\s+.*(?:base64|atob)|base64\s+-d.*\|\s*(?:ba)?sh/i,
|
|
78
|
+
description_zh: 'Base64 解码后执行(混淆攻击)',
|
|
79
|
+
description_en: 'Base64 decode and execute (obfuscated attack)',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'reverse_shell',
|
|
83
|
+
pattern: /\/dev\/tcp\/|nc\s+-[a-zA-Z]*e\s|ncat\s.*-e\s|bash\s+-i\s+>&\s*\/dev/i,
|
|
84
|
+
description_zh: '反弹 Shell(远程控制)',
|
|
85
|
+
description_en: 'Reverse shell (remote control)',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'crontab_overwrite',
|
|
89
|
+
pattern: /crontab\s+-r|echo\s+.*>\s*\/var\/spool\/cron/i,
|
|
90
|
+
description_zh: '覆盖或删除定时任务',
|
|
91
|
+
description_en: 'Overwrite or remove crontab entries',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'nc_exfil',
|
|
95
|
+
pattern: /\|\s*(?:nc|ncat|netcat)\s+\S+\s+\d+/i,
|
|
96
|
+
description_zh: '通过 netcat 向远程主机传输数据',
|
|
97
|
+
description_en: 'Pipe data to remote host via netcat',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'crontab_append',
|
|
101
|
+
pattern: />>\s*(?:\/etc\/crontab|\/var\/spool\/cron)/i,
|
|
102
|
+
description_zh: '追加定时任务(可能植入后门)',
|
|
103
|
+
description_en: 'Append to crontab (potential backdoor)',
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
/**
|
|
107
|
+
* Normalize command string before pattern matching:
|
|
108
|
+
* - Split on command separators (;, &&, ||, \n) and check each part
|
|
109
|
+
* - Trim whitespace
|
|
110
|
+
*/
|
|
111
|
+
export function splitCommands(cmd) {
|
|
112
|
+
return cmd.split(/\s*(?:;|&&|\|\||[\r\n]+)\s*/).filter(Boolean);
|
|
113
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// src/rules/injection-en.ts — English prompt injection detection rules
|
|
2
|
+
export const INJECTION_RULES_EN = [
|
|
3
|
+
{
|
|
4
|
+
id: 'en_ignore_prev',
|
|
5
|
+
name: 'Ignore previous instructions',
|
|
6
|
+
pattern: 'ignore\\s+(?:all\\s+)?(?:previous|prior|above|earlier|preceding)\\s+(?:instructions?|rules?|prompts?|guidelines?|constraints?)',
|
|
7
|
+
flags: 'i',
|
|
8
|
+
riskScore: 40,
|
|
9
|
+
category: 'override',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: 'en_new_role',
|
|
13
|
+
name: 'Role hijack',
|
|
14
|
+
pattern: '(?:you\\s+are\\s+now|from\\s+now\\s+on\\s+you\\s+are|act\\s+as|pretend\\s+(?:to\\s+be|you\\s+are)|imagine\\s+you\\s+are)\\s+(?:a\\s+)?',
|
|
15
|
+
flags: 'i',
|
|
16
|
+
riskScore: 35,
|
|
17
|
+
category: 'role_hijack',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'en_system_prompt',
|
|
21
|
+
name: 'System prompt extraction',
|
|
22
|
+
pattern: '(?:output|print|show|reveal|display|repeat|leak|dump)\\s+(?:your\\s+)?(?:system|initial|original|full)?\\s*(?:prompt|instructions?|rules?|guidelines?)',
|
|
23
|
+
flags: 'i',
|
|
24
|
+
riskScore: 30,
|
|
25
|
+
category: 'exfiltration',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'en_developer_mode',
|
|
29
|
+
name: 'Developer/admin mode',
|
|
30
|
+
pattern: '(?:enter|enable|activate|switch\\s+to)\\s+(?:developer|debug|admin|root|sudo|maintenance|god)\\s+(?:mode|access|privileges?)',
|
|
31
|
+
flags: 'i',
|
|
32
|
+
riskScore: 35,
|
|
33
|
+
category: 'privilege_escalation',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'en_no_restriction',
|
|
37
|
+
name: 'Remove restrictions',
|
|
38
|
+
pattern: '(?:remove|disable|turn\\s+off|bypass|ignore|skip|override|circumvent)\\s+(?:all\\s+)?(?:restrictions?|constraints?|safety|filters?|guardrails?|limitations?|safeguards?)',
|
|
39
|
+
flags: 'i',
|
|
40
|
+
riskScore: 40,
|
|
41
|
+
category: 'override',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'en_do_anything',
|
|
45
|
+
name: 'Do Anything Now (DAN)',
|
|
46
|
+
pattern: '(?:DAN|do\\s+anything\\s+now|jailbreak|uncensored|unfiltered)\\s*(?:mode|prompt)?',
|
|
47
|
+
flags: 'i',
|
|
48
|
+
riskScore: 40,
|
|
49
|
+
category: 'jailbreak',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'en_boundary_marker',
|
|
53
|
+
name: 'Boundary marker injection',
|
|
54
|
+
pattern: '(?:<<|\\[\\[|\\{\\{)\\s*(?:SYSTEM|SYS|END|NEW\\s*INSTRUCTION|OVERRIDE|ADMIN)\\s*(?:>>|\\]\\]|\\}\\})',
|
|
55
|
+
flags: 'i',
|
|
56
|
+
riskScore: 30,
|
|
57
|
+
category: 'injection',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'en_hidden_instruction',
|
|
61
|
+
name: 'Hidden instruction marker',
|
|
62
|
+
pattern: '(?:the\\s+following\\s+is\\s+(?:a\\s+)?)?(?:hidden|secret|real|actual|true)\\s+(?:instruction|command|prompt|directive)',
|
|
63
|
+
flags: 'i',
|
|
64
|
+
riskScore: 35,
|
|
65
|
+
category: 'injection',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 'en_data_exfil',
|
|
69
|
+
name: 'Data exfiltration',
|
|
70
|
+
pattern: '(?:send|transmit|upload|forward|post|exfiltrate)\\s+(?:all\\s+)?(?:conversation|chat|messages?|history|data|files?|context)\\s+(?:to|via)',
|
|
71
|
+
flags: 'i',
|
|
72
|
+
riskScore: 40,
|
|
73
|
+
category: 'exfiltration',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'en_never_refuse',
|
|
77
|
+
name: 'Never refuse',
|
|
78
|
+
pattern: '(?:never|do\\s+not|don\'t|cannot|must\\s+not)\\s+(?:refuse|decline|reject|say\\s+no|deny)',
|
|
79
|
+
flags: 'i',
|
|
80
|
+
riskScore: 25,
|
|
81
|
+
category: 'override',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'en_encode_exfil',
|
|
85
|
+
name: 'Encoded exfiltration',
|
|
86
|
+
pattern: '(?:encode|encrypt|convert)\\s+(?:.*?)\\s+(?:in|to|as)\\s+(?:base64|hex|rot13|binary)',
|
|
87
|
+
flags: 'i',
|
|
88
|
+
riskScore: 25,
|
|
89
|
+
category: 'exfiltration',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'en_markdown_injection',
|
|
93
|
+
name: 'Markdown image injection',
|
|
94
|
+
pattern: '!\\[\\s*[^\\]]*\\]\\(https?:\\/\\/[^)]*(?:callback|exfil|webhook|log)',
|
|
95
|
+
flags: 'i',
|
|
96
|
+
riskScore: 35,
|
|
97
|
+
category: 'exfiltration',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'en_system_override',
|
|
101
|
+
name: 'System override claim',
|
|
102
|
+
pattern: '(?:SYSTEM|ADMIN|ROOT)\\s*(?:OVERRIDE|COMMAND|DIRECTIVE|ORDER)',
|
|
103
|
+
flags: 'i',
|
|
104
|
+
riskScore: 35,
|
|
105
|
+
category: 'privilege_escalation',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'en_authorized_test',
|
|
109
|
+
name: 'Fake authorized test claim',
|
|
110
|
+
pattern: '(?:authorized|approved|legitimate)\\s+(?:penetration|security|pen)\\s*(?:test|testing|audit)',
|
|
111
|
+
flags: 'i',
|
|
112
|
+
riskScore: 30,
|
|
113
|
+
category: 'privilege_escalation',
|
|
114
|
+
},
|
|
115
|
+
];
|