securityclaw 0.0.2 → 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 +7 -0
- package/README.md +37 -14
- package/README.zh-CN.md +38 -15
- package/admin/server.ts +17 -14
- package/bin/securityclaw.mjs +4 -1
- package/index.ts +4 -3
- package/package.json +1 -1
- package/src/admin/console_notice.ts +4 -4
- 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,12 @@
|
|
|
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
|
+
|
|
3
10
|
## [0.0.2] - 2026-03-17
|
|
4
11
|
|
|
5
12
|
### Changed
|
package/README.md
CHANGED
|
@@ -6,19 +6,23 @@ SecurityClaw is a runtime security plugin for [OpenClaw](https://github.com/open
|
|
|
6
6
|
|
|
7
7
|
## Why SecurityClaw
|
|
8
8
|
|
|
9
|
-
LLM agents can execute powerful tools. SecurityClaw
|
|
9
|
+
LLM agents can execute powerful tools. SecurityClaw provides a policy guardrail layer so risky operations are either blocked, challenged for approval, or allowed with warning and traceability.
|
|
10
10
|
|
|
11
11
|
## Core Capabilities
|
|
12
12
|
|
|
13
13
|
- Runtime policy enforcement for OpenClaw hooks (`before_tool_call`, `after_tool_call`, etc.)
|
|
14
14
|
- Rule-first security model (`allow`, `warn`, `challenge`, `block`)
|
|
15
15
|
- Challenge approval workflow with command-based admin handling
|
|
16
|
-
- Dynamic sensitive-path registry
|
|
17
|
-
-
|
|
18
|
-
-
|
|
16
|
+
- Dynamic sensitive-path registry that maps paths to asset labels before rule evaluation
|
|
17
|
+
- Sensitive data scanning and sanitization (DLP)
|
|
18
|
+
- Admin dashboard for strategy and account policy operations
|
|
19
|
+
- Decision events for audit and observability
|
|
20
|
+
- Built-in internationalization (`en` and `zh-CN`) for runtime/admin text
|
|
19
21
|
|
|
20
22
|
## Install
|
|
21
23
|
|
|
24
|
+
### Direct Use
|
|
25
|
+
|
|
22
26
|
Install the latest published release:
|
|
23
27
|
|
|
24
28
|
```bash
|
|
@@ -34,30 +38,49 @@ curl -fsSL https://raw.githubusercontent.com/znary/securityclaw/main/install.sh
|
|
|
34
38
|
Install a specific published version:
|
|
35
39
|
|
|
36
40
|
```bash
|
|
37
|
-
SECURITYCLAW_VERSION=0.0.
|
|
41
|
+
SECURITYCLAW_VERSION=0.0.3 curl -fsSL https://raw.githubusercontent.com/znary/securityclaw/main/install.sh | bash
|
|
38
42
|
```
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
After installation, if the admin dashboard did not open automatically, open `http://127.0.0.1:4780`.
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
### From Source
|
|
43
47
|
|
|
44
|
-
|
|
48
|
+
Clone the repository, then install dependencies:
|
|
45
49
|
|
|
46
50
|
```bash
|
|
47
|
-
|
|
51
|
+
npm install
|
|
48
52
|
```
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
Install the current workspace build into OpenClaw:
|
|
51
55
|
|
|
52
56
|
```bash
|
|
53
|
-
|
|
57
|
+
npm run openclaw:install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Run verification:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm test
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Start the standalone admin dashboard when needed:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm run admin
|
|
54
70
|
```
|
|
55
71
|
|
|
56
|
-
|
|
72
|
+
## Uninstall
|
|
73
|
+
|
|
74
|
+
Remove the installed plugin from OpenClaw:
|
|
57
75
|
|
|
58
76
|
```bash
|
|
59
|
-
openclaw
|
|
60
|
-
|
|
77
|
+
openclaw plugins uninstall securityclaw
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Preview the removal first if needed:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
openclaw plugins uninstall securityclaw --dry-run
|
|
61
84
|
```
|
|
62
85
|
|
|
63
86
|
## Documentation
|
package/README.zh-CN.md
CHANGED
|
@@ -6,19 +6,23 @@ SecurityClaw 是面向 [OpenClaw](https://github.com/openclaw/openclaw) 的运
|
|
|
6
6
|
|
|
7
7
|
## SecurityClaw 解决什么问题
|
|
8
8
|
|
|
9
|
-
LLM Agent 具备高权限工具调用能力。SecurityClaw
|
|
9
|
+
LLM Agent 具备高权限工具调用能力。SecurityClaw 在运行时提供策略护栏,将高风险操作按规则执行为拦截、审批确认、提醒或放行,并保留可追溯审计信息。
|
|
10
10
|
|
|
11
11
|
## 核心能力
|
|
12
12
|
|
|
13
|
-
- 基于 OpenClaw Hook 的运行时策略执行(`before_tool_call
|
|
13
|
+
- 基于 OpenClaw Hook 的运行时策略执行(`before_tool_call` 等)
|
|
14
14
|
- 规则优先决策模型(`allow` / `warn` / `challenge` / `block`)
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
15
|
+
- Challenge 审批流程与管理员命令处理
|
|
16
|
+
- 动态敏感路径注册表,在规则判断前先把路径映射成资产标签
|
|
17
|
+
- DLP 扫描与敏感输出净化
|
|
18
|
+
- 管理后台(策略与账号策略配置)
|
|
19
|
+
- 决策事件与状态观测
|
|
20
|
+
- 中英文国际化(`en` / `zh-CN`)
|
|
19
21
|
|
|
20
22
|
## 安装
|
|
21
23
|
|
|
24
|
+
### 直接使用
|
|
25
|
+
|
|
22
26
|
安装最新发布版本:
|
|
23
27
|
|
|
24
28
|
```bash
|
|
@@ -34,10 +38,36 @@ curl -fsSL https://raw.githubusercontent.com/znary/securityclaw/main/install.sh
|
|
|
34
38
|
安装指定发布版本:
|
|
35
39
|
|
|
36
40
|
```bash
|
|
37
|
-
SECURITYCLAW_VERSION=0.0.
|
|
41
|
+
SECURITYCLAW_VERSION=0.0.3 curl -fsSL https://raw.githubusercontent.com/znary/securityclaw/main/install.sh | bash
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
安装完成后,如果管理后台没有自动打开,可手动访问 `http://127.0.0.1:4780`。
|
|
45
|
+
|
|
46
|
+
### 从源码开发
|
|
47
|
+
|
|
48
|
+
克隆仓库后先安装依赖:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
把当前工作区构建安装到 OpenClaw:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm run openclaw:install
|
|
38
58
|
```
|
|
39
59
|
|
|
40
|
-
|
|
60
|
+
执行验证:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm test
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
需要时可单独启动管理后台:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm run admin
|
|
70
|
+
```
|
|
41
71
|
|
|
42
72
|
## 卸载
|
|
43
73
|
|
|
@@ -53,13 +83,6 @@ openclaw plugins uninstall securityclaw
|
|
|
53
83
|
openclaw plugins uninstall securityclaw --dry-run
|
|
54
84
|
```
|
|
55
85
|
|
|
56
|
-
安装或卸载后,如有需要可重启并校验 gateway:
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
openclaw gateway restart
|
|
60
|
-
openclaw gateway status
|
|
61
|
-
```
|
|
62
|
-
|
|
63
86
|
## 文档导航
|
|
64
87
|
|
|
65
88
|
- [文档索引](./docs/README.zh-CN.md)
|
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
|
@@ -32,6 +32,7 @@ import { RuntimeStatusStore } from "./src/monitoring/status_store.ts";
|
|
|
32
32
|
import { startAdminServer } from "./admin/server.ts";
|
|
33
33
|
import { announceAdminConsole, shouldAnnounceAdminConsoleForArgv } from "./src/admin/console_notice.ts";
|
|
34
34
|
import { shouldAutoStartAdminServer } from "./src/admin/runtime_guard.ts";
|
|
35
|
+
import { readProcessEnvValue, resolveSecurityClawAdminPort } from "./src/runtime/process_env.ts";
|
|
35
36
|
import { AccountPolicyEngine } from "./src/domain/services/account_policy_engine.ts";
|
|
36
37
|
import { ApprovalSubjectResolver } from "./src/domain/services/approval_subject_resolver.ts";
|
|
37
38
|
import {
|
|
@@ -171,7 +172,7 @@ function resolvePluginStateDir(api: OpenClawPluginApi): string {
|
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
function resolveAdminConsoleUrl(pluginConfig: SecurityClawPluginConfig): string {
|
|
174
|
-
const port = pluginConfig.adminPort ??
|
|
175
|
+
const port = pluginConfig.adminPort ?? resolveSecurityClawAdminPort();
|
|
175
176
|
return `http://127.0.0.1:${port}`;
|
|
176
177
|
}
|
|
177
178
|
|
|
@@ -1164,7 +1165,7 @@ function resolveFeishuSecretValue(value: unknown): string | undefined {
|
|
|
1164
1165
|
const source = feishuTrimmedString(record.source)?.toLowerCase();
|
|
1165
1166
|
const id = feishuTrimmedString(record.id);
|
|
1166
1167
|
if (source === "env" && id) {
|
|
1167
|
-
const envValue = feishuTrimmedString(
|
|
1168
|
+
const envValue = feishuTrimmedString(readProcessEnvValue(id));
|
|
1168
1169
|
if (envValue) {
|
|
1169
1170
|
return envValue;
|
|
1170
1171
|
}
|
|
@@ -1996,7 +1997,7 @@ const plugin = {
|
|
|
1996
1997
|
return;
|
|
1997
1998
|
}
|
|
1998
1999
|
|
|
1999
|
-
const autoStartDecision = shouldAutoStartAdminServer(
|
|
2000
|
+
const autoStartDecision = shouldAutoStartAdminServer();
|
|
2000
2001
|
if (!autoStartDecision.enabled) {
|
|
2001
2002
|
if (shouldAnnounceAdminConsoleForArgv(process.argv)) {
|
|
2002
2003
|
announceAdminConsole({
|
package/package.json
CHANGED
|
@@ -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
|
}
|
|
@@ -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
|
+
|