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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// src/commands/security.ts — /security command: full security status overview
|
|
2
|
+
import { readFileSync, statSync, existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { getHomeDir } from '../utils.js';
|
|
6
|
+
import { resolveLocale } from '../types.js';
|
|
7
|
+
const LOG_DIR = join(getHomeDir(), '.openclaw', 'shellward');
|
|
8
|
+
const LOG_FILE = join(LOG_DIR, 'audit.jsonl');
|
|
9
|
+
export function registerSecurityCommand(api, config) {
|
|
10
|
+
const locale = resolveLocale(config);
|
|
11
|
+
api.registerCommand({
|
|
12
|
+
name: 'security',
|
|
13
|
+
description: locale === 'zh'
|
|
14
|
+
? '🛡️ ShellWard 安全状态总览'
|
|
15
|
+
: '🛡️ ShellWard security status overview',
|
|
16
|
+
acceptsArgs: false,
|
|
17
|
+
handler: () => {
|
|
18
|
+
const lines = [];
|
|
19
|
+
const zh = locale === 'zh';
|
|
20
|
+
lines.push(zh ? '🛡️ **ShellWard 安全状态报告**' : '🛡️ **ShellWard Security Status Report**');
|
|
21
|
+
lines.push('');
|
|
22
|
+
// 1. Layer status
|
|
23
|
+
lines.push(zh ? '## 防御层状态' : '## Defense Layers');
|
|
24
|
+
const layers = [
|
|
25
|
+
['L1 Prompt Guard', config.layers.promptGuard],
|
|
26
|
+
['L2 Output Scanner', config.layers.outputScanner],
|
|
27
|
+
['L3 Tool Blocker', config.layers.toolBlocker],
|
|
28
|
+
['L4 Input Auditor', config.layers.inputAuditor],
|
|
29
|
+
['L5 Security Gate', config.layers.securityGate],
|
|
30
|
+
['L6 Outbound Guard', config.layers.outboundGuard],
|
|
31
|
+
['L7 Data Flow Guard', config.layers.dataFlowGuard],
|
|
32
|
+
['L8 Session Guard', config.layers.sessionGuard],
|
|
33
|
+
];
|
|
34
|
+
for (const [name, enabled] of layers) {
|
|
35
|
+
lines.push(` ${enabled ? '✅' : '❌'} ${name}`);
|
|
36
|
+
}
|
|
37
|
+
lines.push(` ${zh ? '模式' : 'Mode'}: **${config.mode}**`);
|
|
38
|
+
lines.push(` ${zh ? '注入阈值' : 'Injection threshold'}: **${config.injectionThreshold}**`);
|
|
39
|
+
lines.push('');
|
|
40
|
+
// 2. Audit log stats
|
|
41
|
+
lines.push(zh ? '## 审计日志' : '## Audit Log');
|
|
42
|
+
try {
|
|
43
|
+
const stat = statSync(LOG_FILE);
|
|
44
|
+
const sizeMB = (stat.size / 1024 / 1024).toFixed(2);
|
|
45
|
+
lines.push(` ${zh ? '文件' : 'File'}: ${LOG_FILE}`);
|
|
46
|
+
lines.push(` ${zh ? '大小' : 'Size'}: ${sizeMB} MB`);
|
|
47
|
+
// Count recent events
|
|
48
|
+
const content = readFileSync(LOG_FILE, 'utf-8');
|
|
49
|
+
const allLines = content.trim().split('\n').filter(Boolean);
|
|
50
|
+
const total = allLines.length;
|
|
51
|
+
let blocks = 0;
|
|
52
|
+
let audits = 0;
|
|
53
|
+
let criticals = 0;
|
|
54
|
+
for (const line of allLines) {
|
|
55
|
+
if (line.includes('"action":"block"'))
|
|
56
|
+
blocks++;
|
|
57
|
+
if (line.includes('"action":"audit"'))
|
|
58
|
+
audits++;
|
|
59
|
+
if (line.includes('"level":"CRITICAL"'))
|
|
60
|
+
criticals++;
|
|
61
|
+
}
|
|
62
|
+
lines.push(` ${zh ? '总事件' : 'Total events'}: ${total}`);
|
|
63
|
+
lines.push(` ${zh ? '拦截' : 'Blocked'}: ${blocks} | ${zh ? '审计' : 'Audited'}: ${audits} | ${zh ? '严重' : 'Critical'}: ${criticals}`);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
lines.push(zh ? ' ⚠️ 日志文件不存在' : ' ⚠️ Log file not found');
|
|
67
|
+
}
|
|
68
|
+
lines.push('');
|
|
69
|
+
// 3. Quick system security checks
|
|
70
|
+
lines.push(zh ? '## 系统安全快检' : '## Quick System Security Check');
|
|
71
|
+
// Check exposed ports
|
|
72
|
+
try {
|
|
73
|
+
const listening = execSync('ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null', { timeout: 5000 }).toString();
|
|
74
|
+
const openClawPort = listening.includes(':19000') || listening.includes(':19001');
|
|
75
|
+
lines.push(openClawPort
|
|
76
|
+
? (zh ? ' ⚠️ OpenClaw 端口正在监听 (建议限制访问)' : ' ⚠️ OpenClaw port is listening (restrict access)')
|
|
77
|
+
: (zh ? ' ✅ OpenClaw 端口未暴露' : ' ✅ OpenClaw port not exposed'));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
lines.push(zh ? ' ℹ️ 无法检查端口状态' : ' ℹ️ Cannot check port status');
|
|
81
|
+
}
|
|
82
|
+
// Check .env exposure
|
|
83
|
+
const envFile = join(process.cwd(), '.env');
|
|
84
|
+
if (existsSync(envFile)) {
|
|
85
|
+
try {
|
|
86
|
+
const mode = statSync(envFile).mode & 0o777;
|
|
87
|
+
lines.push(mode > 0o600
|
|
88
|
+
? (zh ? ` ⚠️ .env 文件权限过宽 (${mode.toString(8)}),建议 chmod 600` : ` ⚠️ .env file permissions too open (${mode.toString(8)}), suggest chmod 600`)
|
|
89
|
+
: (zh ? ' ✅ .env 文件权限正常' : ' ✅ .env file permissions OK'));
|
|
90
|
+
}
|
|
91
|
+
catch { /* skip */ }
|
|
92
|
+
}
|
|
93
|
+
// Check if running as root
|
|
94
|
+
if (process.getuid && process.getuid() === 0) {
|
|
95
|
+
lines.push(zh
|
|
96
|
+
? ' ⚠️ 正在以 root 运行 (建议使用普通用户 + 容器隔离)'
|
|
97
|
+
: ' ⚠️ Running as root (use non-root user + container isolation)');
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
lines.push(zh ? ' ✅ 非 root 运行' : ' ✅ Not running as root');
|
|
101
|
+
}
|
|
102
|
+
lines.push('');
|
|
103
|
+
lines.push(zh
|
|
104
|
+
? '💡 使用 `/audit` 查看日志, `/harden` 执行安全加固, `/scan-plugins` 扫描插件'
|
|
105
|
+
: '💡 Use `/audit` for logs, `/harden` for hardening, `/scan-plugins` to scan plugins');
|
|
106
|
+
return { text: lines.join('\n') };
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/commands/upgrade-openclaw.ts — 一键升级 OpenClaw,减少手动操作
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { resolveLocale } from '../types.js';
|
|
4
|
+
export function registerUpgradeOpenClawCommand(api, config) {
|
|
5
|
+
const locale = resolveLocale(config);
|
|
6
|
+
api.registerCommand({
|
|
7
|
+
name: 'upgrade-openclaw',
|
|
8
|
+
description: locale === 'zh'
|
|
9
|
+
? '⬆️ 升级 OpenClaw 到最新版本(一键执行)'
|
|
10
|
+
: '⬆️ Upgrade OpenClaw to latest (one-click)',
|
|
11
|
+
acceptsArgs: true,
|
|
12
|
+
handler: (ctx) => {
|
|
13
|
+
const zh = locale === 'zh';
|
|
14
|
+
const args = (ctx.args || '').trim().toLowerCase();
|
|
15
|
+
const doUpgrade = args === 'yes' || args === 'y' || args === '--yes';
|
|
16
|
+
let currentVer = 'unknown';
|
|
17
|
+
try {
|
|
18
|
+
const out = execSync('openclaw --version 2>&1', { timeout: 5000 }).toString();
|
|
19
|
+
const m = out.match(/(\d{4}\.\d+\.\d+|\d+\.\d+\.\d+)/);
|
|
20
|
+
if (m)
|
|
21
|
+
currentVer = m[1];
|
|
22
|
+
}
|
|
23
|
+
catch { /* skip */ }
|
|
24
|
+
const cmd = 'npm update -g openclaw';
|
|
25
|
+
const lines = [];
|
|
26
|
+
if (doUpgrade) {
|
|
27
|
+
try {
|
|
28
|
+
execSync(cmd, { stdio: 'inherit', timeout: 120000 });
|
|
29
|
+
const newOut = execSync('openclaw --version 2>&1', { timeout: 5000 }).toString();
|
|
30
|
+
const newM = newOut.match(/(\d{4}\.\d+\.\d+|\d+\.\d+\.\d+)/);
|
|
31
|
+
const newVer = newM ? newM[1] : 'unknown';
|
|
32
|
+
lines.push(zh ? `✅ 升级完成!当前版本: ${newVer}` : `✅ Upgrade done! Current version: ${newVer}`);
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
lines.push(zh ? `❌ 升级失败: ${e?.message || e}` : `❌ Upgrade failed: ${e?.message || e}`);
|
|
36
|
+
lines.push(zh ? `请手动执行: \`${cmd}\`` : `Run manually: \`${cmd}\``);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
lines.push(zh ? `当前版本: ${currentVer}` : `Current version: ${currentVer}`);
|
|
41
|
+
lines.push('');
|
|
42
|
+
lines.push(zh ? '**一键升级**(复制执行):' : '**One-click upgrade** (copy & run):');
|
|
43
|
+
lines.push('```bash');
|
|
44
|
+
lines.push(cmd);
|
|
45
|
+
lines.push('```');
|
|
46
|
+
lines.push('');
|
|
47
|
+
lines.push(zh
|
|
48
|
+
? '或在 OpenClaw 中执行: `/upgrade-openclaw yes` 自动升级'
|
|
49
|
+
: 'Or run in OpenClaw: `/upgrade-openclaw yes` to auto-upgrade');
|
|
50
|
+
}
|
|
51
|
+
return { text: lines.join('\n') };
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { AuditLog } from '../audit-log.js';
|
|
2
|
+
import type { ShellWardConfig, ResolvedLocale } from '../types.js';
|
|
3
|
+
export interface CheckResult {
|
|
4
|
+
allowed: boolean;
|
|
5
|
+
level?: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'INFO';
|
|
6
|
+
reason?: string;
|
|
7
|
+
ruleId?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ScanResult {
|
|
10
|
+
hasSensitiveData: boolean;
|
|
11
|
+
findings: {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
count: number;
|
|
15
|
+
}[];
|
|
16
|
+
summary: string;
|
|
17
|
+
}
|
|
18
|
+
export interface InjectionResult {
|
|
19
|
+
safe: boolean;
|
|
20
|
+
score: number;
|
|
21
|
+
threshold: number;
|
|
22
|
+
matched: {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
score: number;
|
|
26
|
+
}[];
|
|
27
|
+
hiddenChars: number;
|
|
28
|
+
}
|
|
29
|
+
export interface ResponseCheckResult {
|
|
30
|
+
canaryLeak: boolean;
|
|
31
|
+
sensitiveData: ScanResult;
|
|
32
|
+
}
|
|
33
|
+
export declare class ShellWard {
|
|
34
|
+
readonly config: ShellWardConfig;
|
|
35
|
+
readonly locale: ResolvedLocale;
|
|
36
|
+
readonly log: AuditLog;
|
|
37
|
+
private _canaryToken;
|
|
38
|
+
private compiledRules;
|
|
39
|
+
private sensitiveReads;
|
|
40
|
+
private readonly TRACKING_WINDOW_MS;
|
|
41
|
+
private readonly MAX_TRACKED_READS;
|
|
42
|
+
constructor(config?: Partial<ShellWardConfig>);
|
|
43
|
+
getSecurityPrompt(): string;
|
|
44
|
+
getCanaryToken(): string;
|
|
45
|
+
scanData(text: string, toolName?: string): ScanResult;
|
|
46
|
+
checkTool(toolName: string): CheckResult;
|
|
47
|
+
checkCommand(cmd: string, toolName?: string): CheckResult;
|
|
48
|
+
checkPath(path: string, operation: 'write' | 'delete', toolName?: string): CheckResult;
|
|
49
|
+
checkInjection(text: string, options?: {
|
|
50
|
+
source?: string;
|
|
51
|
+
threshold?: number;
|
|
52
|
+
}): InjectionResult;
|
|
53
|
+
getInjectionThreshold(toolName?: string): number;
|
|
54
|
+
checkAction(action: string, details: string): CheckResult;
|
|
55
|
+
checkResponse(content: string): ResponseCheckResult;
|
|
56
|
+
markSensitiveData(toolName: string, summary: string): void;
|
|
57
|
+
trackFileRead(toolName: string, path: string): void;
|
|
58
|
+
checkOutbound(toolName: string, params: Record<string, any>): CheckResult;
|
|
59
|
+
get hasSensitiveData(): boolean;
|
|
60
|
+
isExecTool(name: string): boolean;
|
|
61
|
+
isReadTool(name: string): boolean;
|
|
62
|
+
isWriteOrDeleteTool(name: string): boolean;
|
|
63
|
+
extractTextFields(args: Record<string, any>): string[];
|
|
64
|
+
private addTrackedRead;
|
|
65
|
+
private evictExpired;
|
|
66
|
+
}
|