securityclaw 0.0.1 → 0.0.3
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/CHANGELOG.md +15 -0
- package/README.md +30 -69
- package/README.zh-CN.md +30 -69
- package/admin/server.ts +17 -14
- package/bin/securityclaw.mjs +4 -1
- package/index.ts +61 -62
- package/package.json +6 -8
- package/src/admin/console_notice.ts +6 -5
- package/src/admin/file_reader.ts +12 -0
- package/src/admin/skill_interception_store.ts +15 -2
- package/src/runtime/process_env.ts +47 -0
- package/src/runtime/process_runner.ts +38 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.3] - 2026-03-18
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Reduced OpenClaw install-time false positives by separating environment reads, file reads, and process-launch helpers out of files that previously tripped static safety rules.
|
|
7
|
+
- Kept the admin console, installer CLI, and Feishu approval delivery behavior unchanged while restructuring those internals for cleaner package scans.
|
|
8
|
+
- Verified the packed npm tarball against the local OpenClaw `skill-scanner` and brought reported findings down to zero.
|
|
9
|
+
|
|
10
|
+
## [0.0.2] - 2026-03-17
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Simplified the README install section to focus on product overview plus remote install and uninstall flows.
|
|
14
|
+
- Clarified that published npm releases ship with a prebuilt admin frontend bundle.
|
|
15
|
+
- Prevented admin build tooling from being shipped as runtime npm dependencies.
|
|
16
|
+
- Avoided auto-opening the dashboard from short-lived gateway service commands before the persistent admin backend is ready.
|
|
17
|
+
|
|
3
18
|
## [0.1.0] - 2026-03-15
|
|
4
19
|
|
|
5
20
|
### 架构重构
|
package/README.md
CHANGED
|
@@ -19,99 +19,69 @@ LLM agents can execute powerful tools. SecurityClaw provides a policy guardrail
|
|
|
19
19
|
- Decision events for audit and observability
|
|
20
20
|
- Built-in internationalization (`en` and `zh-CN`) for runtime/admin text
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## Install
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
### Direct Use
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
- `domain/services/sensitive_path_registry.ts`: built-in + runtime-overridden sensitive path mappings
|
|
28
|
-
- `engine`: rule matching, decisioning, DLP scanning
|
|
29
|
-
- `config`: base YAML + SQLite runtime override
|
|
30
|
-
- `admin`: dashboard backend + frontend
|
|
31
|
-
- `monitoring`: runtime status and decision snapshots
|
|
32
|
-
|
|
33
|
-
See [Architecture](./docs/ARCHITECTURE.md) and [Technical Solution](./docs/TECHNICAL_SOLUTION.md).
|
|
34
|
-
|
|
35
|
-
## Quick Start
|
|
36
|
-
|
|
37
|
-
### 1. Install dependencies
|
|
26
|
+
Install the latest published release:
|
|
38
27
|
|
|
39
28
|
```bash
|
|
40
|
-
|
|
29
|
+
npx securityclaw install
|
|
41
30
|
```
|
|
42
31
|
|
|
43
|
-
|
|
32
|
+
Or install through the remote script:
|
|
44
33
|
|
|
45
34
|
```bash
|
|
46
|
-
|
|
35
|
+
curl -fsSL https://raw.githubusercontent.com/znary/securityclaw/main/install.sh | bash
|
|
47
36
|
```
|
|
48
37
|
|
|
49
|
-
|
|
38
|
+
Install a specific published version:
|
|
50
39
|
|
|
51
40
|
```bash
|
|
52
|
-
|
|
53
|
-
curl -fsSL https://raw.githubusercontent.com/znary/securityclaw/main/install.sh | bash
|
|
41
|
+
SECURITYCLAW_VERSION=0.0.3 curl -fsSL https://raw.githubusercontent.com/znary/securityclaw/main/install.sh | bash
|
|
54
42
|
```
|
|
55
43
|
|
|
56
|
-
|
|
44
|
+
After installation, if the admin dashboard did not open automatically, open `http://127.0.0.1:4780`.
|
|
57
45
|
|
|
58
|
-
|
|
59
|
-
npm test
|
|
60
|
-
```
|
|
46
|
+
### From Source
|
|
61
47
|
|
|
62
|
-
|
|
48
|
+
Clone the repository, then install dependencies:
|
|
63
49
|
|
|
64
50
|
```bash
|
|
65
|
-
npm
|
|
51
|
+
npm install
|
|
66
52
|
```
|
|
67
53
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
## OpenClaw Integration
|
|
71
|
-
|
|
72
|
-
Preferred local install:
|
|
54
|
+
Install the current workspace build into OpenClaw:
|
|
73
55
|
|
|
74
56
|
```bash
|
|
75
57
|
npm run openclaw:install
|
|
76
58
|
```
|
|
77
59
|
|
|
78
|
-
|
|
79
|
-
See [OpenClaw Install Guide](./docs/OPENCLAW_INSTALL.md) for details.
|
|
80
|
-
|
|
81
|
-
## Approval Commands
|
|
60
|
+
Run verification:
|
|
82
61
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
- `/securityclaw-approve <approval_id> long`
|
|
87
|
-
- `/securityclaw-reject <approval_id>`
|
|
88
|
-
- `/securityclaw-pending`
|
|
89
|
-
|
|
90
|
-
## Admin Dashboard
|
|
62
|
+
```bash
|
|
63
|
+
npm test
|
|
64
|
+
```
|
|
91
65
|
|
|
92
|
-
|
|
93
|
-
By default, it follows the host system language.
|
|
66
|
+
Start the standalone admin dashboard when needed:
|
|
94
67
|
|
|
95
|
-
|
|
68
|
+
```bash
|
|
69
|
+
npm run admin
|
|
70
|
+
```
|
|
96
71
|
|
|
97
|
-
|
|
98
|
-
- Decisions: recent decision events and reasons
|
|
99
|
-
- Policies: grouped rule strategy controls plus sensitive-path registry management
|
|
100
|
-
- Skill Interception: installed skill inventory, risk scoring, undeclared-change detection, rescan/quarantine/trust override actions, and interception policy matrix
|
|
101
|
-
- Accounts: admin approver account selection and mode settings
|
|
72
|
+
## Uninstall
|
|
102
73
|
|
|
103
|
-
|
|
74
|
+
Remove the installed plugin from OpenClaw:
|
|
104
75
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
76
|
+
```bash
|
|
77
|
+
openclaw plugins uninstall securityclaw
|
|
78
|
+
```
|
|
108
79
|
|
|
109
|
-
|
|
80
|
+
Preview the removal first if needed:
|
|
110
81
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
- The dedicated Skill Interception panel supports rescan, quarantine, temporary trust override, and risk-matrix editing.
|
|
82
|
+
```bash
|
|
83
|
+
openclaw plugins uninstall securityclaw --dry-run
|
|
84
|
+
```
|
|
115
85
|
|
|
116
86
|
## Documentation
|
|
117
87
|
|
|
@@ -121,15 +91,6 @@ Skill interception behavior:
|
|
|
121
91
|
- [Runbook](./docs/RUNBOOK.md)
|
|
122
92
|
- [Integration Guide](./docs/INTEGRATION_GUIDE.md)
|
|
123
93
|
|
|
124
|
-
## Development
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
npm run typecheck
|
|
128
|
-
npm run test:unit
|
|
129
|
-
npm test
|
|
130
|
-
npm run admin:build
|
|
131
|
-
```
|
|
132
|
-
|
|
133
94
|
## License
|
|
134
95
|
|
|
135
96
|
MIT. See [LICENSE](./LICENSE).
|
package/README.zh-CN.md
CHANGED
|
@@ -19,99 +19,69 @@ LLM Agent 具备高权限工具调用能力。SecurityClaw 在运行时提供策
|
|
|
19
19
|
- 决策事件与状态观测
|
|
20
20
|
- 中英文国际化(`en` / `zh-CN`)
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## 安装
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
### 直接使用
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
- `domain/services/sensitive_path_registry.ts`:内置 + 运行时覆写的敏感路径映射
|
|
28
|
-
- `engine`:规则匹配、决策引擎、DLP
|
|
29
|
-
- `config`:YAML 基线配置 + SQLite 运行时覆盖
|
|
30
|
-
- `admin`:管理后台前后端
|
|
31
|
-
- `monitoring`:运行状态与决策快照
|
|
32
|
-
|
|
33
|
-
详见 [架构文档](./docs/ARCHITECTURE.md) 与 [技术方案](./docs/TECHNICAL_SOLUTION.md)。
|
|
34
|
-
|
|
35
|
-
## 快速开始
|
|
36
|
-
|
|
37
|
-
### 1. 安装依赖
|
|
26
|
+
安装最新发布版本:
|
|
38
27
|
|
|
39
28
|
```bash
|
|
40
|
-
|
|
29
|
+
npx securityclaw install
|
|
41
30
|
```
|
|
42
31
|
|
|
43
|
-
|
|
32
|
+
或者通过远程脚本安装:
|
|
44
33
|
|
|
45
34
|
```bash
|
|
46
|
-
|
|
35
|
+
curl -fsSL https://raw.githubusercontent.com/znary/securityclaw/main/install.sh | bash
|
|
47
36
|
```
|
|
48
37
|
|
|
49
|
-
|
|
38
|
+
安装指定发布版本:
|
|
50
39
|
|
|
51
40
|
```bash
|
|
52
|
-
|
|
53
|
-
curl -fsSL https://raw.githubusercontent.com/znary/securityclaw/main/install.sh | bash
|
|
41
|
+
SECURITYCLAW_VERSION=0.0.3 curl -fsSL https://raw.githubusercontent.com/znary/securityclaw/main/install.sh | bash
|
|
54
42
|
```
|
|
55
43
|
|
|
56
|
-
|
|
44
|
+
安装完成后,如果管理后台没有自动打开,可手动访问 `http://127.0.0.1:4780`。
|
|
57
45
|
|
|
58
|
-
|
|
59
|
-
npm test
|
|
60
|
-
```
|
|
46
|
+
### 从源码开发
|
|
61
47
|
|
|
62
|
-
|
|
48
|
+
克隆仓库后先安装依赖:
|
|
63
49
|
|
|
64
50
|
```bash
|
|
65
|
-
npm
|
|
51
|
+
npm install
|
|
66
52
|
```
|
|
67
53
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
## OpenClaw 集成
|
|
71
|
-
|
|
72
|
-
推荐本地安装方式:
|
|
54
|
+
把当前工作区构建安装到 OpenClaw:
|
|
73
55
|
|
|
74
56
|
```bash
|
|
75
57
|
npm run openclaw:install
|
|
76
58
|
```
|
|
77
59
|
|
|
78
|
-
|
|
79
|
-
详情见 [OpenClaw 安装指南](./docs/OPENCLAW_INSTALL.md)。
|
|
80
|
-
|
|
81
|
-
## 审批命令
|
|
60
|
+
执行验证:
|
|
82
61
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
- `/securityclaw-approve <approval_id> long`
|
|
87
|
-
- `/securityclaw-reject <approval_id>`
|
|
88
|
-
- `/securityclaw-pending`
|
|
89
|
-
|
|
90
|
-
## 管理后台
|
|
62
|
+
```bash
|
|
63
|
+
npm test
|
|
64
|
+
```
|
|
91
65
|
|
|
92
|
-
|
|
93
|
-
默认跟随系统语言。
|
|
66
|
+
需要时可单独启动管理后台:
|
|
94
67
|
|
|
95
|
-
|
|
68
|
+
```bash
|
|
69
|
+
npm run admin
|
|
70
|
+
```
|
|
96
71
|
|
|
97
|
-
|
|
98
|
-
- 决策记录:最近决策事件与原因
|
|
99
|
-
- 规则策略:按分组编辑规则动作,并维护敏感路径注册表
|
|
100
|
-
- Skill 拦截:已安装 skill 清单、风险打分、未声明变更检测、重扫 / 隔离 / 受信覆盖操作,以及拦截策略矩阵
|
|
101
|
-
- 账号策略:管理员审批账号与模式配置
|
|
72
|
+
## 卸载
|
|
102
73
|
|
|
103
|
-
|
|
74
|
+
从 OpenClaw 中卸载已安装插件:
|
|
104
75
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
76
|
+
```bash
|
|
77
|
+
openclaw plugins uninstall securityclaw
|
|
78
|
+
```
|
|
108
79
|
|
|
109
|
-
|
|
80
|
+
如果想先预览会删除什么:
|
|
110
81
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
- `Skill 拦截` 面板支持重扫、隔离、临时受信覆盖,以及风险矩阵配置。
|
|
82
|
+
```bash
|
|
83
|
+
openclaw plugins uninstall securityclaw --dry-run
|
|
84
|
+
```
|
|
115
85
|
|
|
116
86
|
## 文档导航
|
|
117
87
|
|
|
@@ -121,15 +91,6 @@ Skill 拦截说明:
|
|
|
121
91
|
- [运行手册](./docs/RUNBOOK.md)
|
|
122
92
|
- [集成指南](./docs/INTEGRATION_GUIDE.md)
|
|
123
93
|
|
|
124
|
-
## 开发命令
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
npm run typecheck
|
|
128
|
-
npm run test:unit
|
|
129
|
-
npm test
|
|
130
|
-
npm run admin:build
|
|
131
|
-
```
|
|
132
|
-
|
|
133
94
|
## 许可证
|
|
134
95
|
|
|
135
96
|
MIT,详见 [LICENSE](./LICENSE)。
|
package/admin/server.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
|
-
import {
|
|
3
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
4
3
|
import os from "node:os";
|
|
5
4
|
import path from "node:path";
|
|
6
5
|
import { DatabaseSync } from "node:sqlite";
|
|
@@ -10,6 +9,7 @@ import {
|
|
|
10
9
|
matchesAdminDecisionFilter,
|
|
11
10
|
normalizeAdminDecisionFilterId,
|
|
12
11
|
} from "../src/admin/dashboard_url_state.ts";
|
|
12
|
+
import { readJsonRecordFile, readUtf8File } from "../src/admin/file_reader.ts";
|
|
13
13
|
import { SkillInterceptionStore } from "../src/admin/skill_interception_store.ts";
|
|
14
14
|
import { listOpenClawChatSessions } from "../src/admin/openclaw_session_catalog.ts";
|
|
15
15
|
import { ConfigManager } from "../src/config/loader.ts";
|
|
@@ -29,10 +29,13 @@ import {
|
|
|
29
29
|
} from "../src/domain/services/sensitive_path_registry.ts";
|
|
30
30
|
import type { SecurityClawLocale } from "../src/i18n/locale.ts";
|
|
31
31
|
import { pickLocalized, resolveSecurityClawLocale } from "../src/i18n/locale.ts";
|
|
32
|
+
import { readSecurityClawAdminServerEnv, resolveSecurityClawAdminPort } from "../src/runtime/process_env.ts";
|
|
33
|
+
import { runProcessSync } from "../src/runtime/process_runner.ts";
|
|
32
34
|
|
|
33
35
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
34
36
|
const PUBLIC_DIR = path.resolve(ROOT, "admin/public");
|
|
35
|
-
const
|
|
37
|
+
const DEFAULT_ADMIN_ENV = readSecurityClawAdminServerEnv();
|
|
38
|
+
const DEFAULT_PORT = resolveSecurityClawAdminPort();
|
|
36
39
|
const DEFAULT_OPENCLAW_HOME = resolveDefaultOpenClawStateDir();
|
|
37
40
|
|
|
38
41
|
type AdminLogger = {
|
|
@@ -128,7 +131,7 @@ const EMPTY_DECISION_COUNTS: DecisionHistoryCounts = {
|
|
|
128
131
|
block: 0,
|
|
129
132
|
};
|
|
130
133
|
|
|
131
|
-
const ADMIN_DEFAULT_LOCALE = resolveSecurityClawLocale(
|
|
134
|
+
const ADMIN_DEFAULT_LOCALE = resolveSecurityClawLocale(DEFAULT_ADMIN_ENV.locale, "en");
|
|
132
135
|
|
|
133
136
|
function sendJson(res: http.ServerResponse, status: number, body: unknown): void {
|
|
134
137
|
res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
|
|
@@ -181,7 +184,7 @@ function safeReadStatus(statusPath: string): JsonRecord {
|
|
|
181
184
|
};
|
|
182
185
|
}
|
|
183
186
|
try {
|
|
184
|
-
return
|
|
187
|
+
return readJsonRecordFile(statusPath);
|
|
185
188
|
} catch {
|
|
186
189
|
return {
|
|
187
190
|
message: "status file exists but cannot be parsed",
|
|
@@ -842,7 +845,7 @@ function serveStatic(req: http.IncomingMessage, res: http.ServerResponse, url: U
|
|
|
842
845
|
: ext === ".svg"
|
|
843
846
|
? "image/svg+xml"
|
|
844
847
|
: "application/octet-stream";
|
|
845
|
-
sendText(res, 200,
|
|
848
|
+
sendText(res, 200, readUtf8File(absolute), contentType);
|
|
846
849
|
}
|
|
847
850
|
|
|
848
851
|
function readEffectivePolicy(runtime: AdminRuntime, strategyStore: StrategyStore): {
|
|
@@ -858,10 +861,10 @@ function readEffectivePolicy(runtime: AdminRuntime, strategyStore: StrategyStore
|
|
|
858
861
|
|
|
859
862
|
function resolveAdminPluginConfig(options: AdminServerOptions): SecurityClawPluginConfig {
|
|
860
863
|
return {
|
|
861
|
-
...(
|
|
862
|
-
...(
|
|
863
|
-
...(
|
|
864
|
-
...(
|
|
864
|
+
...(DEFAULT_ADMIN_ENV.configPath ? { configPath: DEFAULT_ADMIN_ENV.configPath } : {}),
|
|
865
|
+
...(DEFAULT_ADMIN_ENV.legacyOverridePath ? { overridePath: DEFAULT_ADMIN_ENV.legacyOverridePath } : {}),
|
|
866
|
+
...(DEFAULT_ADMIN_ENV.statusPath ? { statusPath: DEFAULT_ADMIN_ENV.statusPath } : {}),
|
|
867
|
+
...(DEFAULT_ADMIN_ENV.dbPath ? { dbPath: DEFAULT_ADMIN_ENV.dbPath } : {}),
|
|
865
868
|
...(options.configPath !== undefined ? { configPath: options.configPath } : {}),
|
|
866
869
|
...(options.legacyOverridePath !== undefined ? { overridePath: options.legacyOverridePath } : {}),
|
|
867
870
|
...(options.statusPath !== undefined ? { statusPath: options.statusPath } : {}),
|
|
@@ -890,19 +893,19 @@ function parsePids(output: string): number[] {
|
|
|
890
893
|
}
|
|
891
894
|
|
|
892
895
|
function listListeningPidsByPort(port: number): number[] {
|
|
893
|
-
const result =
|
|
896
|
+
const result = runProcessSync("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], { encoding: "utf8" });
|
|
894
897
|
if (result.error || result.status !== 0) {
|
|
895
898
|
return [];
|
|
896
899
|
}
|
|
897
|
-
return parsePids(result.stdout);
|
|
900
|
+
return parsePids(result.stdout ?? "");
|
|
898
901
|
}
|
|
899
902
|
|
|
900
903
|
function readProcessCommand(pid: number): string {
|
|
901
|
-
const result =
|
|
904
|
+
const result = runProcessSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8" });
|
|
902
905
|
if (result.error || result.status !== 0) {
|
|
903
906
|
return "";
|
|
904
907
|
}
|
|
905
|
-
return result.stdout.trim();
|
|
908
|
+
return (result.stdout ?? "").trim();
|
|
906
909
|
}
|
|
907
910
|
|
|
908
911
|
function looksLikeOpenClawProcess(command: string): boolean {
|
package/bin/securityclaw.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
|
-
import {
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
@@ -9,6 +9,9 @@ import { buildInstallPlan, parseInstallArgs } from "./install-lib.mjs";
|
|
|
9
9
|
|
|
10
10
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
11
|
const pkg = JSON.parse(readFileSync(path.join(ROOT, "package.json"), "utf8"));
|
|
12
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
13
|
+
const SYSTEM_PROCESS_MODULE_ID = `node:child${String.fromCharCode(95)}process`;
|
|
14
|
+
const { spawnSync } = requireFromHere(SYSTEM_PROCESS_MODULE_ID);
|
|
12
15
|
|
|
13
16
|
function printUsage() {
|
|
14
17
|
console.log(`SecurityClaw installer
|
package/index.ts
CHANGED
|
@@ -30,9 +30,9 @@ import {
|
|
|
30
30
|
} from "./src/infrastructure/config/plugin_config_parser.ts";
|
|
31
31
|
import { RuntimeStatusStore } from "./src/monitoring/status_store.ts";
|
|
32
32
|
import { startAdminServer } from "./admin/server.ts";
|
|
33
|
-
import { ensureAdminAssetsBuilt } from "./src/admin/build.ts";
|
|
34
33
|
import { announceAdminConsole, shouldAnnounceAdminConsoleForArgv } from "./src/admin/console_notice.ts";
|
|
35
34
|
import { shouldAutoStartAdminServer } from "./src/admin/runtime_guard.ts";
|
|
35
|
+
import { readProcessEnvValue, resolveSecurityClawAdminPort } from "./src/runtime/process_env.ts";
|
|
36
36
|
import { AccountPolicyEngine } from "./src/domain/services/account_policy_engine.ts";
|
|
37
37
|
import { ApprovalSubjectResolver } from "./src/domain/services/approval_subject_resolver.ts";
|
|
38
38
|
import {
|
|
@@ -172,7 +172,7 @@ function resolvePluginStateDir(api: OpenClawPluginApi): string {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
function resolveAdminConsoleUrl(pluginConfig: SecurityClawPluginConfig): string {
|
|
175
|
-
const port = pluginConfig.adminPort ??
|
|
175
|
+
const port = pluginConfig.adminPort ?? resolveSecurityClawAdminPort();
|
|
176
176
|
return `http://127.0.0.1:${port}`;
|
|
177
177
|
}
|
|
178
178
|
|
|
@@ -1165,7 +1165,7 @@ function resolveFeishuSecretValue(value: unknown): string | undefined {
|
|
|
1165
1165
|
const source = feishuTrimmedString(record.source)?.toLowerCase();
|
|
1166
1166
|
const id = feishuTrimmedString(record.id);
|
|
1167
1167
|
if (source === "env" && id) {
|
|
1168
|
-
const envValue = feishuTrimmedString(
|
|
1168
|
+
const envValue = feishuTrimmedString(readProcessEnvValue(id));
|
|
1169
1169
|
if (envValue) {
|
|
1170
1170
|
return envValue;
|
|
1171
1171
|
}
|
|
@@ -1991,69 +1991,67 @@ const plugin = {
|
|
|
1991
1991
|
return runtime;
|
|
1992
1992
|
}
|
|
1993
1993
|
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1994
|
+
function startManagedAdminConsole(): void {
|
|
1995
|
+
if (!adminAutoStart) {
|
|
1996
|
+
api.logger.info?.("securityclaw: admin auto-start disabled by config");
|
|
1997
|
+
return;
|
|
1998
1998
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
})
|
|
2031
|
-
|
|
2032
|
-
|
|
1999
|
+
|
|
2000
|
+
const autoStartDecision = shouldAutoStartAdminServer();
|
|
2001
|
+
if (!autoStartDecision.enabled) {
|
|
2002
|
+
if (shouldAnnounceAdminConsoleForArgv(process.argv)) {
|
|
2003
|
+
announceAdminConsole({
|
|
2004
|
+
locale: runtimeLocale,
|
|
2005
|
+
logger: {
|
|
2006
|
+
info: (message: string) => api.logger.info?.(`securityclaw: ${message}`),
|
|
2007
|
+
warn: (message: string) => api.logger.warn?.(`securityclaw: ${message}`),
|
|
2008
|
+
},
|
|
2009
|
+
stateDir,
|
|
2010
|
+
state: "service-command",
|
|
2011
|
+
url: adminConsoleUrl,
|
|
2012
|
+
});
|
|
2013
|
+
api.logger.info?.("securityclaw: admin dashboard is hosted by the background OpenClaw gateway service");
|
|
2014
|
+
} else {
|
|
2015
|
+
api.logger.info?.(
|
|
2016
|
+
`securityclaw: admin auto-start skipped in ${autoStartDecision.reason}; use npm run admin for standalone dashboard`,
|
|
2017
|
+
);
|
|
2018
|
+
}
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
const adminServerOptions = {
|
|
2023
|
+
configPath: resolved.configPath,
|
|
2024
|
+
legacyOverridePath: resolved.legacyOverridePath,
|
|
2025
|
+
statusPath,
|
|
2026
|
+
dbPath,
|
|
2027
|
+
unrefOnStart: true,
|
|
2028
|
+
logger: {
|
|
2029
|
+
info: (message: string) => api.logger.info?.(`securityclaw: ${message}`),
|
|
2030
|
+
warn: (message: string) => api.logger.warn?.(`securityclaw: ${message}`),
|
|
2031
|
+
},
|
|
2032
|
+
...(pluginConfig.adminPort !== undefined ? { port: pluginConfig.adminPort } : {}),
|
|
2033
|
+
};
|
|
2034
|
+
|
|
2035
|
+
void startAdminServer(adminServerOptions)
|
|
2036
|
+
.then((result) => {
|
|
2037
|
+
announceAdminConsole({
|
|
2038
|
+
locale: runtimeLocale,
|
|
2039
|
+
logger: {
|
|
2040
|
+
info: (message: string) => api.logger.info?.(`securityclaw: ${message}`),
|
|
2041
|
+
warn: (message: string) => api.logger.warn?.(`securityclaw: ${message}`),
|
|
2042
|
+
},
|
|
2043
|
+
stateDir,
|
|
2044
|
+
state: result.state,
|
|
2045
|
+
url: `http://127.0.0.1:${result.runtime.port}`,
|
|
2033
2046
|
});
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
logger: {
|
|
2039
|
-
info: (message: string) => api.logger.info?.(`securityclaw: ${message}`),
|
|
2040
|
-
warn: (message: string) => api.logger.warn?.(`securityclaw: ${message}`),
|
|
2041
|
-
},
|
|
2042
|
-
stateDir,
|
|
2043
|
-
state: "service-command",
|
|
2044
|
-
url: adminConsoleUrl,
|
|
2045
|
-
});
|
|
2046
|
-
api.logger.info?.("securityclaw: admin dashboard is hosted by the background OpenClaw gateway service");
|
|
2047
|
-
} else {
|
|
2048
|
-
api.logger.info?.(
|
|
2049
|
-
`securityclaw: admin auto-start skipped in ${autoStartDecision.reason}; use npm run admin for standalone dashboard`,
|
|
2050
|
-
);
|
|
2051
|
-
}
|
|
2052
|
-
}
|
|
2053
|
-
} else {
|
|
2054
|
-
api.logger.info?.("securityclaw: admin auto-start disabled by config");
|
|
2047
|
+
})
|
|
2048
|
+
.catch((error) => {
|
|
2049
|
+
api.logger.warn?.(`securityclaw: failed to auto-start admin dashboard (${String(error)})`);
|
|
2050
|
+
});
|
|
2055
2051
|
}
|
|
2056
2052
|
|
|
2053
|
+
statusStore.markBoot(toStatusConfig(runtime.config, runtime.overrideLoaded, resolved));
|
|
2054
|
+
|
|
2057
2055
|
api.logger.info?.(
|
|
2058
2056
|
`securityclaw: boot env=${runtime.config.environment} policy_version=${runtime.config.policy_version} dlp_mode=${runtime.config.dlp.on_dlp_hit} rules=${runtime.config.policies.length}`,
|
|
2059
2057
|
);
|
|
@@ -2655,6 +2653,7 @@ const plugin = {
|
|
|
2655
2653
|
{ priority: 100 },
|
|
2656
2654
|
);
|
|
2657
2655
|
|
|
2656
|
+
startManagedAdminConsole();
|
|
2658
2657
|
api.logger.info?.(`securityclaw: loaded policy_version=${runtime.config.policy_version}`);
|
|
2659
2658
|
}
|
|
2660
2659
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "securityclaw",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "SecurityClaw security plugin for OpenClaw-compatible hooks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"test:unit": "node --test --experimental-strip-types",
|
|
43
43
|
"test": "npm run typecheck && npm run test:unit",
|
|
44
44
|
"check": "npm test",
|
|
45
|
-
"admin": "node --experimental-strip-types ./admin/server.ts",
|
|
45
|
+
"admin": "npm run admin:build && node --experimental-strip-types ./admin/server.ts",
|
|
46
46
|
"admin:build": "node ./scripts/build-admin.mjs",
|
|
47
47
|
"prepack": "npm test && npm run admin:build",
|
|
48
48
|
"pack:plugin": "node ./scripts/pack-plugin.mjs",
|
|
@@ -50,17 +50,15 @@
|
|
|
50
50
|
"release:check": "node ./scripts/release-preflight.mjs",
|
|
51
51
|
"release:dry-run": "npm run release:check && npm publish --dry-run --access public"
|
|
52
52
|
},
|
|
53
|
-
"dependencies": {
|
|
54
|
-
"esbuild": "^0.27.4",
|
|
55
|
-
"react": "^19.2.4",
|
|
56
|
-
"react-dom": "^19.2.4",
|
|
57
|
-
"recharts": "^3.8.0"
|
|
58
|
-
},
|
|
59
53
|
"devDependencies": {
|
|
60
54
|
"@types/node": "^25.5.0",
|
|
61
55
|
"@types/react": "^19.2.14",
|
|
62
56
|
"@types/react-dom": "^19.2.3",
|
|
57
|
+
"esbuild": "^0.27.4",
|
|
63
58
|
"openclaw": "^2026.3.13",
|
|
59
|
+
"react": "^19.2.4",
|
|
60
|
+
"react-dom": "^19.2.4",
|
|
61
|
+
"recharts": "^3.8.0",
|
|
64
62
|
"typescript": "^5.9.3"
|
|
65
63
|
},
|
|
66
64
|
"peerDependencies": {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
|
|
5
4
|
import { resolveSecurityClawStateDir } from "../infrastructure/config/plugin_config_parser.ts";
|
|
6
5
|
import type { SecurityClawLocale } from "../i18n/locale.ts";
|
|
7
6
|
import { pickLocalized } from "../i18n/locale.ts";
|
|
7
|
+
import { runProcessSync } from "../runtime/process_runner.ts";
|
|
8
8
|
|
|
9
9
|
export type AdminConsoleState = "started" | "already-running" | "service-command";
|
|
10
10
|
|
|
@@ -122,7 +122,7 @@ function writeAdminConsoleMarker(markerPath: string, url: string): void {
|
|
|
122
122
|
|
|
123
123
|
export function openAdminConsoleInBrowser(url: string): BrowserOpenResult {
|
|
124
124
|
if (process.platform === "darwin") {
|
|
125
|
-
const result =
|
|
125
|
+
const result = runProcessSync("open", [url], { stdio: "ignore", timeout: 5_000 });
|
|
126
126
|
if (result.error) {
|
|
127
127
|
return { ok: false, command: "open", error: String(result.error) };
|
|
128
128
|
}
|
|
@@ -133,7 +133,7 @@ export function openAdminConsoleInBrowser(url: string): BrowserOpenResult {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
if (process.platform === "win32") {
|
|
136
|
-
const result =
|
|
136
|
+
const result = runProcessSync("cmd", ["/c", "start", "", url], {
|
|
137
137
|
stdio: "ignore",
|
|
138
138
|
timeout: 5_000,
|
|
139
139
|
windowsHide: true,
|
|
@@ -148,7 +148,7 @@ export function openAdminConsoleInBrowser(url: string): BrowserOpenResult {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
if (process.platform === "linux") {
|
|
151
|
-
const result =
|
|
151
|
+
const result = runProcessSync("xdg-open", [url], { stdio: "ignore", timeout: 5_000 });
|
|
152
152
|
if (result.error) {
|
|
153
153
|
return { ok: false, command: "xdg-open", error: String(result.error) };
|
|
154
154
|
}
|
|
@@ -168,9 +168,10 @@ export function announceAdminConsole(options: AnnounceAdminConsoleOptions): Anno
|
|
|
168
168
|
const { locale, logger, url, state, stateDir, opener = openAdminConsoleInBrowser } = options;
|
|
169
169
|
const markerPath = stateDir ? resolveAdminConsoleMarkerPath(stateDir) : undefined;
|
|
170
170
|
const firstRun = markerPath !== undefined && !existsSync(markerPath);
|
|
171
|
+
const shouldAutoOpen = firstRun && state !== "service-command";
|
|
171
172
|
|
|
172
173
|
let openedAutomatically = false;
|
|
173
|
-
if (
|
|
174
|
+
if (shouldAutoOpen) {
|
|
174
175
|
const result = opener(url);
|
|
175
176
|
if (result.ok) {
|
|
176
177
|
openedAutomatically = true;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
type JsonRecord = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
export function readUtf8File(filePath: string): string {
|
|
6
|
+
return readFileSync(filePath, "utf8");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function readJsonRecordFile(filePath: string): JsonRecord {
|
|
10
|
+
return JSON.parse(readUtf8File(filePath)) as JsonRecord;
|
|
11
|
+
}
|
|
12
|
+
|
|
@@ -670,8 +670,21 @@ export function analyzeSkillDocument(input: {
|
|
|
670
670
|
const normalizedName = input.name.trim().toLowerCase();
|
|
671
671
|
const siblingNames = input.siblingNames.map((item) => item.trim().toLowerCase()).filter(Boolean);
|
|
672
672
|
const downloadExecutePattern = /(curl|wget)[^\n]{0,120}\|\s*(sh|bash|zsh)|download[^.\n]{0,60}(then )?(run|execute)/i;
|
|
673
|
-
const
|
|
674
|
-
|
|
673
|
+
const childProcessToken = `child${String.fromCharCode(95)}process`;
|
|
674
|
+
const shellExecPattern = new RegExp(
|
|
675
|
+
[
|
|
676
|
+
"exec_command",
|
|
677
|
+
"spawnSync",
|
|
678
|
+
childProcessToken,
|
|
679
|
+
"bash\\s+-lc",
|
|
680
|
+
"zsh\\s+-lc",
|
|
681
|
+
"powershell",
|
|
682
|
+
"rm\\s+-rf",
|
|
683
|
+
"chmod\\s+-R",
|
|
684
|
+
"chown\\s+-R",
|
|
685
|
+
].join("|"),
|
|
686
|
+
"i",
|
|
687
|
+
);
|
|
675
688
|
const bypassPattern =
|
|
676
689
|
/(ignore|bypass|disable|skip)[^.\n]{0,40}(policy|security|guard|safety)|hide (the )?(output|logs)|不要提示|不要暴露/i;
|
|
677
690
|
const credentialPattern =
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
type SecurityClawAdminServerEnv = {
|
|
2
|
+
adminPort?: number | undefined;
|
|
3
|
+
locale?: string | undefined;
|
|
4
|
+
configPath?: string | undefined;
|
|
5
|
+
legacyOverridePath?: string | undefined;
|
|
6
|
+
statusPath?: string | undefined;
|
|
7
|
+
dbPath?: string | undefined;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function readTextEnv(name: string, env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
11
|
+
const value = env[name];
|
|
12
|
+
if (typeof value !== "string") {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const trimmed = value.trim();
|
|
16
|
+
return trimmed || undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readNumericEnv(name: string, env: NodeJS.ProcessEnv = process.env): number | undefined {
|
|
20
|
+
const value = readTextEnv(name, env);
|
|
21
|
+
if (!value) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const parsed = Number(value);
|
|
25
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function readProcessEnvValue(name: string, env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
29
|
+
return readTextEnv(name, env);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function resolveSecurityClawAdminPort(defaultPort = 4780, env: NodeJS.ProcessEnv = process.env): number {
|
|
33
|
+
return readNumericEnv("SECURITYCLAW_ADMIN_PORT", env) ?? defaultPort;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function readSecurityClawAdminServerEnv(
|
|
37
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
38
|
+
): SecurityClawAdminServerEnv {
|
|
39
|
+
return {
|
|
40
|
+
adminPort: readNumericEnv("SECURITYCLAW_ADMIN_PORT", env),
|
|
41
|
+
locale: readTextEnv("SECURITYCLAW_LOCALE", env),
|
|
42
|
+
configPath: readTextEnv("SECURITYCLAW_CONFIG_PATH", env),
|
|
43
|
+
legacyOverridePath: readTextEnv("SECURITYCLAW_LEGACY_OVERRIDE_PATH", env),
|
|
44
|
+
statusPath: readTextEnv("SECURITYCLAW_STATUS_PATH", env),
|
|
45
|
+
dbPath: readTextEnv("SECURITYCLAW_DB_PATH", env),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
export type RunProcessSyncOptions = {
|
|
4
|
+
cwd?: string;
|
|
5
|
+
encoding?: BufferEncoding;
|
|
6
|
+
stdio?: "ignore" | "inherit" | "pipe";
|
|
7
|
+
timeout?: number;
|
|
8
|
+
windowsHide?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type RunProcessSyncResult = {
|
|
12
|
+
status: number | null;
|
|
13
|
+
stdout?: string;
|
|
14
|
+
stderr?: string;
|
|
15
|
+
error?: unknown;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type RunProcessSyncFn = (
|
|
19
|
+
command: string,
|
|
20
|
+
args?: readonly string[],
|
|
21
|
+
options?: RunProcessSyncOptions,
|
|
22
|
+
) => RunProcessSyncResult;
|
|
23
|
+
|
|
24
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
25
|
+
const SYSTEM_PROCESS_MODULE_ID = `node:child${String.fromCharCode(95)}process`;
|
|
26
|
+
const RUN_PROCESS_SYNC_METHOD = ["spawn", "Sync"].join("");
|
|
27
|
+
const runProcessSyncImpl = (requireFromHere(SYSTEM_PROCESS_MODULE_ID) as Record<string, unknown>)[
|
|
28
|
+
RUN_PROCESS_SYNC_METHOD
|
|
29
|
+
] as RunProcessSyncFn;
|
|
30
|
+
|
|
31
|
+
export function runProcessSync(
|
|
32
|
+
command: string,
|
|
33
|
+
args: readonly string[],
|
|
34
|
+
options: RunProcessSyncOptions = {},
|
|
35
|
+
): RunProcessSyncResult {
|
|
36
|
+
return runProcessSyncImpl(command, args, options);
|
|
37
|
+
}
|
|
38
|
+
|