shellward 0.5.10 → 0.5.12
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 +22 -6
- package/server.json +44 -0
- 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
package/dist/index.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/index.ts — ShellWard: AI Agent Security Middleware
|
|
2
|
+
//
|
|
3
|
+
// Two usage modes:
|
|
4
|
+
// 1. SDK (any platform): import { ShellWard } from 'shellward'
|
|
5
|
+
// 2. OpenClaw plugin: import shellward from 'shellward'
|
|
6
|
+
//
|
|
7
|
+
// See docs/定位.md — ShellWard is an AI Agent Security Layer,
|
|
8
|
+
// NOT just an OpenClaw plugin. The core engine is platform-agnostic.
|
|
9
|
+
import { ShellWard } from './core/engine.js';
|
|
10
|
+
import { setupPromptGuard } from './layers/prompt-guard.js';
|
|
11
|
+
import { setupOutputScanner } from './layers/output-scanner.js';
|
|
12
|
+
import { setupToolBlocker } from './layers/tool-blocker.js';
|
|
13
|
+
import { setupInputAuditor } from './layers/input-auditor.js';
|
|
14
|
+
import { setupSecurityGate } from './layers/security-gate.js';
|
|
15
|
+
import { setupOutboundGuard } from './layers/outbound-guard.js';
|
|
16
|
+
import { setupDataFlowGuard } from './layers/data-flow-guard.js';
|
|
17
|
+
import { setupSessionGuard } from './layers/session-guard.js';
|
|
18
|
+
import { registerAllCommands } from './commands/index.js';
|
|
19
|
+
import { checkForUpdate } from './update-check.js';
|
|
20
|
+
import { runAutoCheckOnStartup } from './auto-check.js';
|
|
21
|
+
const CURRENT_VERSION = '0.5.10';
|
|
22
|
+
// Re-export core engine for SDK usage
|
|
23
|
+
export { ShellWard } from './core/engine.js';
|
|
24
|
+
/**
|
|
25
|
+
* Wrap api.on so every hook handler gets try-catch protection.
|
|
26
|
+
* If a security hook throws, we log the error and fail-safe:
|
|
27
|
+
* - before_tool_call: block (deny on error, safer than allow)
|
|
28
|
+
* - other hooks: return undefined (don't break the chain)
|
|
29
|
+
*
|
|
30
|
+
* Returns boolean indicating whether hook registration succeeded.
|
|
31
|
+
* This allows layers to detect missing hooks and register fallbacks.
|
|
32
|
+
*/
|
|
33
|
+
function createSafeApi(api, guard) {
|
|
34
|
+
return {
|
|
35
|
+
...api,
|
|
36
|
+
on(hookName, handler, opts) {
|
|
37
|
+
const isBlockHook = hookName === 'before_tool_call';
|
|
38
|
+
const wrappedHandler = (event) => {
|
|
39
|
+
try {
|
|
40
|
+
return handler(event);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const msg = err?.message || String(err);
|
|
44
|
+
guard.log.write({
|
|
45
|
+
level: 'CRITICAL',
|
|
46
|
+
layer: 'L0',
|
|
47
|
+
action: 'error',
|
|
48
|
+
detail: `Hook ${opts?.name || hookName} threw: ${msg.slice(0, 200)}`,
|
|
49
|
+
});
|
|
50
|
+
try {
|
|
51
|
+
api.logger.warn(`[ShellWard] Hook error in ${opts?.name || hookName}: ${msg}`);
|
|
52
|
+
}
|
|
53
|
+
catch { }
|
|
54
|
+
if (isBlockHook) {
|
|
55
|
+
return { block: true, blockReason: `⚠️ [ShellWard] Internal error in security check — operation blocked for safety` };
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
api.on(hookName, wrappedHandler, opts);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// OpenClaw plugin entry point
|
|
71
|
+
export default {
|
|
72
|
+
id: 'shellward',
|
|
73
|
+
register(api) {
|
|
74
|
+
const guard = new ShellWard(api.config);
|
|
75
|
+
const enforce = guard.config.mode === 'enforce';
|
|
76
|
+
const safe = createSafeApi(api, guard);
|
|
77
|
+
const startMsg = guard.locale === 'zh'
|
|
78
|
+
? `[ShellWard] AI Agent 安全中间件已启动 (v${CURRENT_VERSION}, 模式: ${guard.config.mode})`
|
|
79
|
+
: `[ShellWard] AI Agent Security Middleware started (v${CURRENT_VERSION}, mode: ${guard.config.mode})`;
|
|
80
|
+
api.logger.info(startMsg);
|
|
81
|
+
// === Defense Layers (L1-L8) — thin adapters calling core engine ===
|
|
82
|
+
if (guard.config.layers.promptGuard) {
|
|
83
|
+
setupPromptGuard(safe, guard);
|
|
84
|
+
}
|
|
85
|
+
if (guard.config.layers.outputScanner) {
|
|
86
|
+
setupOutputScanner(safe, guard);
|
|
87
|
+
}
|
|
88
|
+
if (guard.config.layers.toolBlocker) {
|
|
89
|
+
setupToolBlocker(safe, guard, enforce);
|
|
90
|
+
}
|
|
91
|
+
if (guard.config.layers.inputAuditor) {
|
|
92
|
+
setupInputAuditor(safe, guard, enforce);
|
|
93
|
+
}
|
|
94
|
+
// L5 uses raw api for registerTool (not a hook)
|
|
95
|
+
if (guard.config.layers.securityGate) {
|
|
96
|
+
setupSecurityGate(api, guard, enforce);
|
|
97
|
+
}
|
|
98
|
+
if (guard.config.layers.outboundGuard) {
|
|
99
|
+
setupOutboundGuard(safe, guard, enforce);
|
|
100
|
+
}
|
|
101
|
+
if (guard.config.layers.dataFlowGuard) {
|
|
102
|
+
setupDataFlowGuard(safe, guard, enforce);
|
|
103
|
+
}
|
|
104
|
+
if (guard.config.layers.sessionGuard) {
|
|
105
|
+
setupSessionGuard(safe, guard, enforce);
|
|
106
|
+
}
|
|
107
|
+
// === Slash Commands ===
|
|
108
|
+
if (api.registerCommand) {
|
|
109
|
+
registerAllCommands(api, guard.config);
|
|
110
|
+
api.logger.info('[ShellWard] 6 commands registered');
|
|
111
|
+
}
|
|
112
|
+
const allLayers = ['promptGuard', 'outputScanner', 'toolBlocker', 'inputAuditor', 'securityGate', 'outboundGuard', 'dataFlowGuard', 'sessionGuard'];
|
|
113
|
+
const enabledCount = allLayers.filter(k => guard.config.layers[k]).length;
|
|
114
|
+
const layerMsg = guard.locale === 'zh'
|
|
115
|
+
? `[ShellWard] ${enabledCount} 层防御已激活 — 敏感数据审计 | 注入检测 | 外泄拦截`
|
|
116
|
+
: `[ShellWard] ${enabledCount} defense layers active`;
|
|
117
|
+
api.logger.info(layerMsg);
|
|
118
|
+
guard.log.write({
|
|
119
|
+
level: 'INFO',
|
|
120
|
+
layer: 'L1',
|
|
121
|
+
action: 'allow',
|
|
122
|
+
detail: `ShellWard v${CURRENT_VERSION} started with ${enabledCount} layers`,
|
|
123
|
+
});
|
|
124
|
+
checkForUpdate(CURRENT_VERSION).then(result => {
|
|
125
|
+
if (result?.shouldNotify) {
|
|
126
|
+
const msg = guard.locale === 'zh'
|
|
127
|
+
? `[ShellWard] 新版本 v${result.latest} 可用 (当前 v${result.current})`
|
|
128
|
+
: `[ShellWard] Update available: v${result.latest} (current v${result.current})`;
|
|
129
|
+
api.logger.warn(msg);
|
|
130
|
+
}
|
|
131
|
+
}).catch(() => { });
|
|
132
|
+
// 启动时自动安全检查(OpenClaw 漏洞、插件风险、MCP 配置、root 运行)
|
|
133
|
+
if (guard.config.autoCheckOnStartup !== false) {
|
|
134
|
+
runAutoCheckOnStartup(api.logger, guard.locale);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/layers/data-flow-guard.ts — L7 OpenClaw Adapter
|
|
2
|
+
// Thin adapter: wires OpenClaw hooks to ShellWard core engine for data flow tracking
|
|
3
|
+
export function setupDataFlowGuard(api, guard, enforce) {
|
|
4
|
+
// Track file reads via after_tool_call
|
|
5
|
+
api.on('after_tool_call', (event) => {
|
|
6
|
+
const toolName = String(event.toolName || '').toLowerCase();
|
|
7
|
+
const params = (event.params && typeof event.params === 'object') ? event.params : {};
|
|
8
|
+
const path = String(params.path || params.file_path || params.filename || params.target || '');
|
|
9
|
+
if (guard.isReadTool(toolName) && path) {
|
|
10
|
+
guard.trackFileRead(event.toolName, path);
|
|
11
|
+
}
|
|
12
|
+
}, { name: 'shellward.data-flow-read-tracker', priority: 50 });
|
|
13
|
+
// Block outbound sends when sensitive data was recently accessed
|
|
14
|
+
api.on('before_tool_call', (event) => {
|
|
15
|
+
const toolName = String(event.toolName || '');
|
|
16
|
+
const params = (event.params && typeof event.params === 'object') ? event.params : {};
|
|
17
|
+
const result = guard.checkOutbound(toolName, params);
|
|
18
|
+
if (!result.allowed && enforce) {
|
|
19
|
+
return { block: true, blockReason: `🚫 [ShellWard] ${result.reason}` };
|
|
20
|
+
}
|
|
21
|
+
}, { name: 'shellward.data-flow-egress', priority: 250 });
|
|
22
|
+
api.logger.info('[ShellWard] L7 Data Flow Guard enabled');
|
|
23
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// src/layers/input-auditor.ts — L4 OpenClaw Adapter
|
|
2
|
+
// Thin adapter: wires OpenClaw hooks to ShellWard core engine for injection detection
|
|
3
|
+
// Compat: registers all known hook name variants — OpenClaw silently ignores unknown ones
|
|
4
|
+
export function setupInputAuditor(api, guard, enforce) {
|
|
5
|
+
// Tool call parameter scanning via before_tool_call
|
|
6
|
+
api.on('before_tool_call', (event) => {
|
|
7
|
+
const args = (event.params && typeof event.params === 'object') ? event.params : {};
|
|
8
|
+
const texts = guard.extractTextFields(args);
|
|
9
|
+
if (texts.length === 0)
|
|
10
|
+
return;
|
|
11
|
+
const toolName = String(event.toolName || '');
|
|
12
|
+
const threshold = guard.getInjectionThreshold(toolName);
|
|
13
|
+
const fullText = texts.join('\n');
|
|
14
|
+
const result = guard.checkInjection(fullText, { source: toolName, threshold });
|
|
15
|
+
if (!result.safe && enforce) {
|
|
16
|
+
const reason = guard.locale === 'zh'
|
|
17
|
+
? `检测到可能的提示词注入攻击!\n风险评分: ${result.score}/100\n匹配规则: ${result.matched.map(m => m.name).join(', ')}`
|
|
18
|
+
: `Potential prompt injection detected!\nRisk score: ${result.score}/100\nMatched: ${result.matched.map(m => m.name).join(', ')}`;
|
|
19
|
+
return { block: true, blockReason: `⚠️ [ShellWard] ${reason}` };
|
|
20
|
+
}
|
|
21
|
+
}, { name: 'shellward.input-auditor', priority: 300 });
|
|
22
|
+
// Message scanning: register ALL known naming conventions
|
|
23
|
+
// OpenClaw silently ignores unknown hooks (no error thrown), so register all variants
|
|
24
|
+
const messageHandler = (event) => {
|
|
25
|
+
const content = typeof event.content === 'string' ? event.content : '';
|
|
26
|
+
if (!content)
|
|
27
|
+
return;
|
|
28
|
+
guard.checkInjection(content, { source: 'message' });
|
|
29
|
+
};
|
|
30
|
+
api.on('message_received', messageHandler, { name: 'shellward.message-auditor', priority: 100 });
|
|
31
|
+
api.on('message:received', messageHandler, { name: 'shellward.message-auditor-v2', priority: 100 });
|
|
32
|
+
api.logger.info(`[ShellWard] L4 Input Auditor enabled`);
|
|
33
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/layers/outbound-guard.ts — L6 OpenClaw Adapter
|
|
2
|
+
// Thin adapter: wires OpenClaw hooks to ShellWard core engine for outbound response scanning
|
|
3
|
+
// Compat: registers all known hook name variants
|
|
4
|
+
export function setupOutboundGuard(api, guard, enforce) {
|
|
5
|
+
const handler = (event) => {
|
|
6
|
+
const content = event.content;
|
|
7
|
+
if (!content || typeof content !== 'string')
|
|
8
|
+
return undefined;
|
|
9
|
+
const result = guard.checkResponse(content);
|
|
10
|
+
if (result.canaryLeak && enforce) {
|
|
11
|
+
const warning = guard.locale === 'zh'
|
|
12
|
+
? '⚠️ [ShellWard] 检测到安全异常,本次回复已被拦截。可能存在提示词注入攻击。'
|
|
13
|
+
: '⚠️ [ShellWard] Security anomaly detected, this response was blocked. Possible prompt injection attack.';
|
|
14
|
+
return { content: warning };
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
};
|
|
18
|
+
// Register ALL known naming conventions — OpenClaw silently ignores unknown ones
|
|
19
|
+
api.on('message_sending', handler, { name: 'shellward.outbound-guard', priority: 100 });
|
|
20
|
+
api.on('message:sent', handler, { name: 'shellward.outbound-guard-v2', priority: 100 });
|
|
21
|
+
api.logger.info('[ShellWard] L6 Outbound Guard enabled');
|
|
22
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/layers/output-scanner.ts — L2 OpenClaw Adapter
|
|
2
|
+
// Thin adapter: wires OpenClaw's tool_result_persist hook to ShellWard core engine
|
|
3
|
+
export function setupOutputScanner(api, guard) {
|
|
4
|
+
api.on('tool_result_persist', (event) => {
|
|
5
|
+
const msg = event.message;
|
|
6
|
+
if (!msg || !Array.isArray(msg.content))
|
|
7
|
+
return undefined;
|
|
8
|
+
for (const block of msg.content) {
|
|
9
|
+
if (block.type === 'text' && typeof block.text === 'string') {
|
|
10
|
+
guard.scanData(block.text, msg.toolName);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}, { name: 'shellward.output-scanner', priority: 100 });
|
|
15
|
+
api.logger.info('[ShellWard] L2 Output Scanner enabled');
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/layers/prompt-guard.ts — L1 OpenClaw Adapter
|
|
2
|
+
// Thin adapter: wires OpenClaw's before_prompt_build hook to ShellWard core engine
|
|
3
|
+
export function setupPromptGuard(api, guard) {
|
|
4
|
+
api.on('before_prompt_build', () => {
|
|
5
|
+
guard.log.write({
|
|
6
|
+
level: 'INFO',
|
|
7
|
+
layer: 'L1',
|
|
8
|
+
action: 'inject',
|
|
9
|
+
detail: 'Security prompt injected',
|
|
10
|
+
});
|
|
11
|
+
return { prependSystemContext: guard.getSecurityPrompt() };
|
|
12
|
+
}, { name: 'shellward.prompt-guard', priority: 100 });
|
|
13
|
+
api.logger.info('[ShellWard] L1 Prompt Guard enabled');
|
|
14
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// src/layers/security-gate.ts — L5 OpenClaw Adapter
|
|
2
|
+
// Thin adapter: registers shellward_check tool via OpenClaw's registerTool API
|
|
3
|
+
function textResult(text) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{ type: 'text', text }],
|
|
6
|
+
details: {},
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function setupSecurityGate(api, guard, enforce) {
|
|
10
|
+
if (!api.registerTool) {
|
|
11
|
+
api.logger.warn('[ShellWard] L5 Security Gate skipped: registerTool not available');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const toolDescription = guard.locale === 'zh'
|
|
15
|
+
? '在执行任何 Shell 命令、文件删除、邮件发送或支付操作前,必须先调用此工具进行安全检查。传入 action 类型和具体参数。'
|
|
16
|
+
: 'MUST be called before executing any shell command, file deletion, email sending, or payment operation. Pass the action type and parameters for security review.';
|
|
17
|
+
api.registerTool({
|
|
18
|
+
name: 'shellward_check',
|
|
19
|
+
label: 'ShellWard Security Check',
|
|
20
|
+
description: toolDescription,
|
|
21
|
+
parameters: {
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
action: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'The action to check: exec, file_delete, file_write, send_email, payment, etc.',
|
|
27
|
+
},
|
|
28
|
+
details: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'The specific command, file path, or operation details',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
required: ['action', 'details'],
|
|
34
|
+
},
|
|
35
|
+
execute: async (_toolCallId, params) => {
|
|
36
|
+
const action = typeof params.action === 'string' ? params.action.trim() : '';
|
|
37
|
+
const details = typeof params.details === 'string' ? params.details.trim() : '';
|
|
38
|
+
if (!action) {
|
|
39
|
+
return textResult(JSON.stringify({ status: 'DENIED', reason: 'action parameter is required' }));
|
|
40
|
+
}
|
|
41
|
+
const result = guard.checkAction(action, details);
|
|
42
|
+
return textResult(JSON.stringify({
|
|
43
|
+
status: result.allowed ? 'ALLOWED' : 'DENIED',
|
|
44
|
+
reason: result.reason,
|
|
45
|
+
}));
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
api.logger.info('[ShellWard] L5 Security Gate registered');
|
|
49
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/layers/session-guard.ts — L8 OpenClaw Adapter
|
|
2
|
+
// Thin adapter: wires OpenClaw hooks to ShellWard core engine for session monitoring
|
|
3
|
+
// Compat: registers all known hook name variants
|
|
4
|
+
export function setupSessionGuard(api, guard, enforce) {
|
|
5
|
+
const sessionEndHandler = () => {
|
|
6
|
+
guard.log.write({
|
|
7
|
+
level: 'INFO',
|
|
8
|
+
layer: 'L8',
|
|
9
|
+
action: 'detect',
|
|
10
|
+
detail: guard.locale === 'zh'
|
|
11
|
+
? '会话结束 — 安全审计完成'
|
|
12
|
+
: 'Session ended — security audit complete',
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
// Register ALL known naming conventions for session end
|
|
16
|
+
api.on('session_end', sessionEndHandler, { name: 'shellward.session-end', priority: 50 });
|
|
17
|
+
api.on('session:end', sessionEndHandler, { name: 'shellward.session-end-v2', priority: 50 });
|
|
18
|
+
api.on('command:new', sessionEndHandler, { name: 'shellward.session-end-fallback', priority: 50 });
|
|
19
|
+
const subagentHandler = (event) => {
|
|
20
|
+
const mode = event.mode || 'unknown';
|
|
21
|
+
guard.log.write({
|
|
22
|
+
level: 'MEDIUM',
|
|
23
|
+
layer: 'L8',
|
|
24
|
+
action: 'detect',
|
|
25
|
+
detail: guard.locale === 'zh'
|
|
26
|
+
? `子 Agent 创建: mode=${mode}, agentId=${event.agentId || 'unknown'}`
|
|
27
|
+
: `Subagent spawning: mode=${mode}, agentId=${event.agentId || 'unknown'}`,
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
// Register ALL known naming conventions for subagent monitoring
|
|
31
|
+
api.on('subagent_spawning', subagentHandler, { name: 'shellward.subagent-guard', priority: 100 });
|
|
32
|
+
api.on('subagent:spawning', subagentHandler, { name: 'shellward.subagent-guard-v2', priority: 100 });
|
|
33
|
+
api.logger.info('[ShellWard] L8 Session Guard enabled');
|
|
34
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/layers/tool-blocker.ts — L3 OpenClaw Adapter
|
|
2
|
+
// Thin adapter: wires OpenClaw's before_tool_call hook to ShellWard core engine
|
|
3
|
+
export function setupToolBlocker(api, guard, enforce) {
|
|
4
|
+
api.on('before_tool_call', (event) => {
|
|
5
|
+
const tool = String(event.toolName || '');
|
|
6
|
+
const args = (event.params && typeof event.params === 'object') ? event.params : {};
|
|
7
|
+
const toolCheck = guard.checkTool(tool);
|
|
8
|
+
if (!toolCheck.allowed && enforce) {
|
|
9
|
+
return { block: true, blockReason: `🚫 [ShellWard] ${toolCheck.reason}` };
|
|
10
|
+
}
|
|
11
|
+
if (guard.isExecTool(tool)) {
|
|
12
|
+
const cmd = String(args.command ?? args.cmd ?? '');
|
|
13
|
+
const cmdCheck = guard.checkCommand(cmd, tool);
|
|
14
|
+
if (!cmdCheck.allowed && enforce) {
|
|
15
|
+
return { block: true, blockReason: `🚫 [ShellWard] ${cmdCheck.reason}` };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const rawPath = String(args.path || args.file_path || args.filename || args.target || '');
|
|
19
|
+
if (rawPath && guard.isWriteOrDeleteTool(tool)) {
|
|
20
|
+
const op = /delete|remove/i.test(tool) ? 'delete' : 'write';
|
|
21
|
+
const pathCheck = guard.checkPath(rawPath, op, tool);
|
|
22
|
+
if (!pathCheck.allowed && enforce) {
|
|
23
|
+
return { block: true, blockReason: `🚫 [ShellWard] ${pathCheck.reason}` };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}, { name: 'shellward.tool-blocker', priority: 200 });
|
|
27
|
+
api.logger.info('[ShellWard] L3 Tool Blocker enabled');
|
|
28
|
+
}
|