openclaw-sc 5.38.0

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.
Files changed (48) hide show
  1. package/.env.example +13 -0
  2. package/INSTALL.md +13 -0
  3. package/LICENSE +233 -0
  4. package/README.md +123 -0
  5. package/SECURITY.md +27 -0
  6. package/index.js +3479 -0
  7. package/lib/code-review-shared.js +164 -0
  8. package/lib/config.js +164 -0
  9. package/lib/constants.js +438 -0
  10. package/lib/decomposer.js +896 -0
  11. package/lib/dialog-recall.js +389 -0
  12. package/lib/env.js +59 -0
  13. package/lib/level-rules.js +72 -0
  14. package/lib/log-manager.js +258 -0
  15. package/lib/preemption.js +278 -0
  16. package/lib/priority-calc.js +134 -0
  17. package/lib/prompt-injection.js +748 -0
  18. package/lib/route-evidence.js +701 -0
  19. package/lib/shared-fs.js +244 -0
  20. package/lib/steward-rules.js +648 -0
  21. package/lib/task-center.js +602 -0
  22. package/lib/task-chain.js +713 -0
  23. package/lib/task-checker.js +317 -0
  24. package/lib/task-profiles.js +255 -0
  25. package/lib/tcell.js +411 -0
  26. package/lib/tool-auto-discover.js +522 -0
  27. package/lib/tool-enrich-subagent.js +375 -0
  28. package/lib/tool-handlers.js +178 -0
  29. package/lib/tool-selector.js +459 -0
  30. package/openclaw.plugin.json +55 -0
  31. package/package.json +63 -0
  32. package/security.js +141 -0
  33. package/tools/bridge.js +3288 -0
  34. package/tools/dashboard/tk-dashboard.py +262 -0
  35. package/tools/hippocampus-multi-search.js +1038 -0
  36. package/tools/hippocampus-sqlite.js +738 -0
  37. package/tools/hippocampus-store.js +262 -0
  38. package/tools/mcp-tools.config.json +653 -0
  39. package/tools/pipeline-engine.js +667 -0
  40. package/tools/sidecar/_check_tools.py +34 -0
  41. package/tools/sidecar/sidecar-server.cjs +1360 -0
  42. package/tools/sidecar/subagent-runner.cjs +1471 -0
  43. package/tools/system-tools.js +619 -0
  44. package/vector/README.md +100 -0
  45. package/vector/usearch-bridge.js +639 -0
  46. package/vector/usearch-http.js +74 -0
  47. package/vector/usearch-serve.js +156 -0
  48. package/workers/worker.js +1419 -0
package/security.js ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * 🦞 sc v5.37.0 — 共享安全模块 (ESM)
3
+ *
4
+ * 统一路径白名单校验,供 Worker 线程、MCP Server、主插件三方共用。
5
+ * 避免 MCP 直调工具绕过 Worker 层 validatePath 的安全漏洞(H-001/H-002)。
6
+ */
7
+
8
+ import { realpath } from "fs/promises";
9
+ import { resolve, normalize, relative, isAbsolute, dirname, basename, join } from "path";
10
+ import { homedir } from "os";
11
+
12
+ // 🔧 Windows子agent兼容:homedir()在某些子进程环境中可能返回C:\root(因为HOME=/root)
13
+ // 优先用USERPROFILE(Windows最可靠),回退homedir()
14
+ function getOpenClawRoot() {
15
+ const home = process.env.USERPROFILE || homedir();
16
+ return resolve(home, ".openclaw");
17
+ }
18
+
19
+ // 🟡 异步初始化:realpath 解析 ~/.openclaw 真实路径
20
+ // 后续所有 validatePath 调用都等这个 Promise 完成
21
+ const ALLOWED_ROOTS_PROMISE = (async () => {
22
+ const base = getOpenClawRoot();
23
+ try {
24
+ return [await realpath(base)];
25
+ } catch (err) {
26
+ // 🔧 空catch修复: 记录解析失败原因,降级为 resolve
27
+ if (typeof console !== 'undefined' && console.warn) {
28
+ console.warn(`[security] realpath 解析 ~/.openclaw 失败: ${err?.message || '未知错误'},降级为 resolve`);
29
+ }
30
+ return [resolve(base)];
31
+ }
32
+ })();
33
+
34
+ // 🟢 TTL 缓存刷新机制(BUG-D1 修复)
35
+ // ALLOWED_ROOTS 在模块加载时只解析一次,沙箱目录移动/重命名后不刷新
36
+ // 解决方案:每 60 秒重新解析一次,同时提供 refreshAllowedRoots() 导出函数供外部手动刷新
37
+ let _cachedRoots = null;
38
+ let _cachedRootsTime = 0;
39
+ const ALLOWED_ROOTS_TTL = 60000; // 60 秒 TTL
40
+
41
+ /**
42
+ * 获取允许的根目录列表(带 TTL 缓存)
43
+ * 缓存有效期 60 秒,超时后自动重新解析
44
+ * @returns {Promise<string[]>}
45
+ */
46
+ async function getAllowedRoots() {
47
+ const now = Date.now();
48
+ if (_cachedRoots && (now - _cachedRootsTime) < ALLOWED_ROOTS_TTL) {
49
+ return _cachedRoots;
50
+ }
51
+ // 缓存过期或未初始化,重新解析
52
+ const base = getOpenClawRoot();
53
+ let roots;
54
+ try {
55
+ roots = [await realpath(base)];
56
+ } catch (err) {
57
+ // 🔧 空catch修复: 记录失败原因并降级
58
+ if (typeof console !== 'undefined' && console.warn) {
59
+ console.warn(`[security] getAllowedRoots realpath 失败: ${err?.message || '未知错误'},降级为 resolve`);
60
+ }
61
+ roots = [resolve(base)];
62
+ }
63
+ _cachedRoots = roots;
64
+ _cachedRootsTime = now;
65
+ return roots;
66
+ }
67
+
68
+ /**
69
+ * 手动刷新白名单根目录缓存
70
+ * 当沙箱目录被移动/重命名后,调用此函数强制重新解析
71
+ * @returns {Promise<string[]>}
72
+ */
73
+ export async function refreshAllowedRoots() {
74
+ _cachedRoots = null;
75
+ _cachedRootsTime = 0;
76
+ return getAllowedRoots();
77
+ }
78
+
79
+ /**
80
+ * 路径白名单校验
81
+ * @param {string} filePath - 待校验的文件路径
82
+ * @returns {Promise<string>} 校验通过后的真实路径
83
+ * @throws {Error} BAD_REQUEST / ACCESS_DENIED
84
+ */
85
+ export async function validatePath(filePath) {
86
+ if (!filePath) throw Object.assign(new Error("路径不能为空"), { code: "BAD_REQUEST" });
87
+
88
+ const isWin = process.platform === "win32";
89
+ const norm = (p) => (isWin ? p.toLowerCase() : p);
90
+
91
+ // 使用 TTL 缓存的根目录列表(而非只解析一次的 ALLOWED_ROOTS_PROMISE)
92
+ const ALLOWED_ROOTS = await getAllowedRoots();
93
+
94
+ let resolved;
95
+ try {
96
+ resolved = await realpath(filePath);
97
+ } catch (err) {
98
+ if (err.code === "ENOENT") {
99
+ // 🔧 H-001 Fix: 逐级解析已存在的父目录真实路径,防止符号链接绕过
100
+ // 原实现 resolve(normalize(filePath)) 不解析符号链接,可通过symlink写入系统任意目录
101
+ const parentDir = dirname(filePath);
102
+ let parentReal;
103
+ try {
104
+ parentReal = await realpath(parentDir);
105
+ } catch (parentErr) {
106
+ // 🔧 空catch修复: 父目录realpath失败时记录并降级
107
+ if (typeof console !== 'undefined' && console.warn) {
108
+ console.warn(`[security] validatePath 父目录 realpath 失败: ${parentErr?.message || '未知错误'}`);
109
+ }
110
+ parentReal = resolve(parentDir);
111
+ }
112
+ resolved = join(parentReal, basename(filePath));
113
+ } else {
114
+ throw Object.assign(
115
+ new Error(`路径解析失败: ${err.message}`),
116
+ { code: "ACCESS_DENIED" }
117
+ );
118
+ }
119
+ }
120
+
121
+ const allowed = ALLOWED_ROOTS.some((root) => {
122
+ const normRoot = norm(root);
123
+ const normResolved = norm(resolved);
124
+ if (isWin) {
125
+ return normResolved.startsWith(normRoot + "\\") || normResolved === normRoot;
126
+ }
127
+ const rel = relative(root, resolved);
128
+ return !rel.startsWith("..") && !isAbsolute(rel);
129
+ });
130
+
131
+ if (!allowed) {
132
+ throw Object.assign(
133
+ new Error(`路径不在允许范围内: ${filePath}`),
134
+ { code: "ACCESS_DENIED" }
135
+ );
136
+ }
137
+
138
+ return resolved;
139
+ }
140
+
141
+ export default { validatePath, refreshAllowedRoots };