evolcore 0.0.12 → 0.0.14
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 +37 -0
- package/bin/codex-managed-hook.mjs +4 -1
- package/bin/ec-safe-output.js +26 -7
- package/bin/install-codex-managed-hooks.mjs +201 -0
- package/dist/agents/claude-runner.js +105 -11
- package/dist/agents/codex-app-server-client.js +176 -7
- package/dist/agents/codex-runner.js +248 -84
- package/dist/agents/ecagent-runner.js +21 -2
- package/dist/agents/gemini-runner.js +9 -4
- package/dist/aun/aid/control-aid.js +36 -13
- package/dist/aun/aid/store.js +13 -7
- package/dist/aun/group-fs-download.js +56 -0
- package/dist/aun/group-identity.js +1 -2
- package/dist/aun/msg/group.js +11 -33
- package/dist/aun/msg/index.js +1 -2
- package/dist/aun/msg/managed-operation.js +176 -55
- package/dist/aun/msg/upload.js +16 -1
- package/dist/aun/outbox.js +31 -2
- package/dist/aun/rpc/client.js +66 -0
- package/dist/aun/rpc/index.js +1 -2
- package/dist/aun/storage/{upload.js → client.js} +11 -2
- package/dist/aun/storage/index.js +1 -2
- package/dist/channels/aun.js +514 -150
- package/dist/channels/daemon.js +2 -0
- package/dist/cli/agent.js +9 -96
- package/dist/cli/aun-commands.js +1 -1
- package/dist/cli/cli-argv.js +10 -0
- package/dist/cli/config.js +9 -17
- package/dist/cli/ctl-command.js +1 -1
- package/dist/cli/daemon-commands.js +39 -24
- package/dist/cli/fs-command.js +65 -19
- package/dist/cli/init.js +77 -8
- package/dist/cli/queue-command.js +42 -0
- package/dist/cli/restart-monitor.js +3 -14
- package/dist/cli/task-context.js +172 -0
- package/dist/cli/trigger-command.js +7 -0
- package/dist/config/access-policy.js +1 -2
- package/dist/config/builtin-roles.js +7 -0
- package/dist/config/config-manager.js +27 -3
- package/dist/config/contact-book-store.js +7 -2
- package/dist/config/peer-role-resolver.js +6 -2
- package/dist/config/role-migration-startup.js +53 -0
- package/dist/config/role-store.js +1 -1
- package/dist/core/auth/agent-delegation.js +1 -1
- package/dist/core/auth/auth-gateway.js +30 -1
- package/dist/core/auth/authorization-audit.js +47 -0
- package/dist/core/auth/operation-authorizer.js +1 -0
- package/dist/core/auth/operation-catalog.js +10 -1
- package/dist/core/bootstrap-messages.js +9 -2
- package/dist/core/bootstrap-service.js +8 -1
- package/dist/core/command/command-handler.js +201 -51
- package/dist/core/command/menu-catalog.js +4 -0
- package/dist/core/command/menu-handler.js +85 -18
- package/dist/core/command/menu-protocol.js +23 -2
- package/dist/core/command/slash-handler.js +58 -10
- package/dist/core/event-catalog.js +8 -0
- package/dist/core/handoff/runtime.js +12 -1
- package/dist/core/handoff/store.js +5 -1
- package/dist/core/interaction-router.js +10 -0
- package/dist/core/message/im-renderer.js +34 -1
- package/dist/core/message/message-bridge.js +68 -13
- package/dist/core/message/message-log.js +2 -0
- package/dist/core/message/message-queue.js +267 -39
- package/dist/core/message/message-utils.js +81 -4
- package/dist/core/message/response-engine.js +242 -48
- package/dist/core/message/stream-debouncer.js +10 -2
- package/dist/core/permission/approval-gateway.js +1 -0
- package/dist/core/permission/ec-command-parser.js +649 -18
- package/dist/core/permission/index.js +1 -1
- package/dist/core/permission/mode.js +15 -0
- package/dist/core/permission/readonly-shell-query.js +148 -8
- package/dist/core/permission/sandbox-runtime.js +21 -0
- package/dist/core/permission/tool-policy.js +266 -56
- package/dist/core/protected-paths.js +107 -2
- package/dist/core/runtime-lock.js +101 -0
- package/dist/core/session/session-manager.js +21 -2
- package/dist/core/session/session-mapper.js +20 -1
- package/dist/eck/detect.js +18 -1
- package/dist/eck/group-rules-sync.js +15 -44
- package/dist/index.js +191 -89
- package/dist/ipc.js +16 -3
- package/dist/paths.js +3 -0
- package/dist/response-system/engines/v1/proactive-flow.js +92 -8
- package/dist/response-system/modes/single-session/index.js +3 -0
- package/dist/trigger/feedback.js +64 -11
- package/dist/trigger/history.js +42 -7
- package/dist/trigger/manager.js +21 -1
- package/dist/trigger/parser.js +14 -2
- package/dist/trigger/scheduler.js +1 -0
- package/dist/trigger/validation.js +67 -6
- package/dist/utils/ecweb-supervisor.js +332 -0
- package/dist/utils/error-utils.js +17 -0
- package/dist/utils/logger.js +37 -4
- package/kits/docs/evolcore/trigger.md +17 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
- package/kits/schemas/role-config.schema.1.json +1 -0
- package/kits/templates/roles/admin.json +7 -0
- package/kits/templates/roles/member.json +8 -1
- package/kits/templates/roles/visitor.json +2 -0
- package/package.json +1 -1
- package/dist/aun/msg/payload-type.js +0 -27
- package/dist/aun/rpc/caller.js +0 -42
- package/dist/aun/rpc/connection.js +0 -25
- package/dist/aun/storage/manage.js +0 -10
- package/dist/config/access-policy-domain.js +0 -18
- package/dist/config/config-batch-get.js +0 -11
- package/dist/config/owner-policy.js +0 -4
- package/dist/config/role-config-v4-startup.js +0 -32
- package/dist/config/role-config-v5-startup.js +0 -27
- package/dist/core/auth/trigger-authorization.js +0 -15
- package/dist/core/interaction-registration.js +0 -10
- package/dist/core/permission/execution-sandbox.js +0 -16
- package/dist/core/relation/peer-key.js +0 -1
- package/dist/core/session/session-key.js +0 -24
- package/dist/eck/baseagent-caps.js +0 -18
- package/dist/eck/rules-loader.js +0 -28
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,43 @@
|
|
|
3
3
|
本文件记录 EvolCore 的重要变更。EvolClaw 版本线的历史记录已归档至
|
|
4
4
|
[`docs/_archive/CHANGELOG-evolclaw.md`](docs/_archive/CHANGELOG-evolclaw.md)。
|
|
5
5
|
|
|
6
|
+
## 0.0.14 (2026-08-24)
|
|
7
|
+
|
|
8
|
+
### Codex 受管运行与安全
|
|
9
|
+
|
|
10
|
+
- 强化 Codex proactive 模式的受管 PreToolUse 校验,Linux 使用隔离运行时,Windows 支持受管 Hook 安装、加载检查和 fail-closed 启动。
|
|
11
|
+
- 隔离会话临时目录并统一运行时锁管理,收紧 Shell、文件访问和受控 EC 命令解析,扩展群文件 ACL 操作及权限校验。
|
|
12
|
+
- 完善工具审批、委派票据和工具结果关联审计,避免未成功发送或未完成授权的操作推进主动模式状态。
|
|
13
|
+
- 为 Claude、Codex、ecagent 的工具预检拒绝补充统一结构化授权审计,记录策略码、请求/任务及会话上下文,便于跨 Runner 追踪拦截原因。
|
|
14
|
+
- 规范 Codex EC 审批命令与委派校验,拒绝无法规范化的命令、复合命令和空字节输入;允许触发器正文安全使用受管临时目录标记。
|
|
15
|
+
|
|
16
|
+
### AUN 消息与授权
|
|
17
|
+
|
|
18
|
+
- 贯通 AUN 消息路由、会话上下文和授权审计关联信息,补齐请求、工具调用和结果之间的 correlation ID。
|
|
19
|
+
- 统一结构化日志字段,改进异常、策略拦截和拒绝结果的可追踪性。
|
|
20
|
+
|
|
21
|
+
## 0.0.13 (2026-08-20)
|
|
22
|
+
|
|
23
|
+
### AUN 消息与文件
|
|
24
|
+
|
|
25
|
+
- 显式区分 AUN 私聊与群聊投递路由,强化入站去重、顺序处理、消息队列持久化和失败恢复。
|
|
26
|
+
- 统一 AUN RPC 与存储客户端,扩展群文件写入与下载能力,收敛文件操作和载荷处理边界。
|
|
27
|
+
- 增加群文件下载票据校验和内容哈希验证,补齐短连接 RPC、存储上传及启动期角色配置迁移能力。
|
|
28
|
+
|
|
29
|
+
### 权限与关系
|
|
30
|
+
|
|
31
|
+
- 收敛角色迁移、权限作用域和最小能力模式,强化 Owner、关系范围与工具执行审计边界。
|
|
32
|
+
- 贯通会话投递元数据和跨会话授权校验,避免托管操作越过当前 Agent、会话或关系范围。
|
|
33
|
+
- 强化受管 `$TMPDIR`、群规则读取和群文件上传的路径隔离,拒绝符号链接、越界目录及不符合权限/所有者要求的路径。
|
|
34
|
+
- 完善菜单授权审计的请求与会话关联信息,扩展只读 Shell 查询、配置批量读取和委派有效期校验。
|
|
35
|
+
|
|
36
|
+
### 会话与运行时
|
|
37
|
+
|
|
38
|
+
- 完善消息撤回隔离、错误回传、菜单交互、重启命令和 Trigger 反馈的上下文校验。
|
|
39
|
+
- 简化 Agent 运行器与启动流程,修正系统重启路径并清理托管会话凭据。
|
|
40
|
+
- 为 Codex app-server 请求增加 request ID 与载荷指纹重放防护,使重复请求复用结果、冲突请求拒绝;会话重试按至多执行一次处理。
|
|
41
|
+
- 统一 EC Web 守护进程的发现、接管、健康检查和退出清理,修正 Linux 进程启动时间计算,并改进 Codex 初始化/升级探测与 Web 消息路由展示。
|
|
42
|
+
|
|
6
43
|
## 0.0.12 (2026-08-19)
|
|
7
44
|
|
|
8
45
|
### AUN 通信
|
|
@@ -45,7 +45,10 @@ function queryDaemon(socketPath, payload) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
async function main() {
|
|
48
|
-
|
|
48
|
+
// Windows managed requirements use one machine-wide command. Resolve the
|
|
49
|
+
// per-daemon named pipe from the inherited environment so multiple EvolCore
|
|
50
|
+
// homes can share the installed hook without rewriting requirements.toml.
|
|
51
|
+
const socketPath = process.argv[2] || process.env.EVOLCORE_INSTANCE_SOCKET;
|
|
49
52
|
let input;
|
|
50
53
|
try {
|
|
51
54
|
input = JSON.parse(fs.readFileSync(0, 'utf8'));
|
package/bin/ec-safe-output.js
CHANGED
|
@@ -82,12 +82,27 @@ function resolveExecutionPaths(payload) {
|
|
|
82
82
|
return { workspace, cwd };
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
function resolveManagedTempRoot() {
|
|
86
|
+
const raw = typeof process.env.TMPDIR === 'string' ? process.env.TMPDIR.trim() : '';
|
|
87
|
+
if (!raw || !path.isAbsolute(raw)) fail('managed TMPDIR is unavailable');
|
|
88
|
+
try {
|
|
89
|
+
const root = fs.realpathSync(raw);
|
|
90
|
+
if (!fs.statSync(root).isDirectory()) fail('managed TMPDIR is not a directory');
|
|
91
|
+
return root;
|
|
92
|
+
} catch {
|
|
93
|
+
fail('managed TMPDIR is unavailable');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
85
97
|
function openOutputFile(payload, paths) {
|
|
86
98
|
if (payload.outputPath === undefined) return undefined;
|
|
87
|
-
if (typeof payload.outputPath !== 'string' || !payload.outputPath
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
99
|
+
if (typeof payload.outputPath !== 'string' || !payload.outputPath) fail('output path is invalid');
|
|
100
|
+
|
|
101
|
+
const isManagedTempOutput = path.isAbsolute(payload.outputPath);
|
|
102
|
+
const outputRoot = isManagedTempOutput ? resolveManagedTempRoot() : paths.workspace;
|
|
103
|
+
const target = isManagedTempOutput
|
|
104
|
+
? path.resolve(payload.outputPath)
|
|
105
|
+
: path.resolve(paths.cwd, payload.outputPath);
|
|
91
106
|
const parent = path.dirname(target);
|
|
92
107
|
let realParent;
|
|
93
108
|
try {
|
|
@@ -97,9 +112,13 @@ function openOutputFile(payload, paths) {
|
|
|
97
112
|
fail('output parent does not exist');
|
|
98
113
|
}
|
|
99
114
|
const canonicalTarget = path.join(realParent, path.basename(target));
|
|
100
|
-
if (!sameOrDescendant(canonicalTarget,
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
if (!sameOrDescendant(canonicalTarget, outputRoot)) {
|
|
116
|
+
fail(isManagedTempOutput ? 'output path escapes managed TMPDIR' : 'output path escapes workspace');
|
|
117
|
+
}
|
|
118
|
+
if (!isManagedTempOutput) {
|
|
119
|
+
const relative = path.relative(paths.workspace, canonicalTarget);
|
|
120
|
+
if (relative === '.git' || relative.startsWith(`.git${path.sep}`)) fail('output path may not target Git metadata');
|
|
121
|
+
}
|
|
103
122
|
|
|
104
123
|
const noFollow = fs.constants.O_NOFOLLOW ?? 0;
|
|
105
124
|
try {
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { execFileSync } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
const markerStart = '# BEGIN EVOLCORE MANAGED CODEX HOOK';
|
|
9
|
+
const markerEnd = '# END EVOLCORE MANAGED CODEX HOOK';
|
|
10
|
+
|
|
11
|
+
function argument(name) {
|
|
12
|
+
const index = process.argv.indexOf(name);
|
|
13
|
+
return index >= 0 ? process.argv[index + 1] : undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function fail(message) {
|
|
17
|
+
process.stderr.write(`[evolcore] ${message}\n`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function tomlString(value) {
|
|
22
|
+
return JSON.stringify(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function windowsCommandArg(value) {
|
|
26
|
+
// Keep filesystem separators intact; tomlString() performs the required
|
|
27
|
+
// TOML escaping after this Windows command-line quoting step.
|
|
28
|
+
return `"${value.replaceAll('"', '\\"')}"`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function removeManagedBlock(source) {
|
|
32
|
+
const expression = new RegExp(`\\n?${markerStart}[\\s\\S]*?${markerEnd}\\n?`, 'g');
|
|
33
|
+
return source.replace(expression, '\n').replace(/\n{3,}/g, '\n\n');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function tableBounds(lines, table) {
|
|
37
|
+
const header = table ? `[${table}]` : null;
|
|
38
|
+
const start = header
|
|
39
|
+
? lines.findIndex(line => line.trim() === header)
|
|
40
|
+
: -1;
|
|
41
|
+
if (table && start < 0) return null;
|
|
42
|
+
const from = table ? start + 1 : 0;
|
|
43
|
+
let to = lines.length;
|
|
44
|
+
for (let index = from; index < lines.length; index += 1) {
|
|
45
|
+
if (/^\s*\[\[?[^\]]+\]\]?\s*(?:#.*)?$/.test(lines[index])) {
|
|
46
|
+
to = index;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { start, from, to };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function setTomlKey(source, table, key, value) {
|
|
54
|
+
const lines = source.split('\n');
|
|
55
|
+
let bounds = tableBounds(lines, table);
|
|
56
|
+
if (!bounds) {
|
|
57
|
+
if (table) {
|
|
58
|
+
if (lines.length && lines.at(-1) !== '') lines.push('');
|
|
59
|
+
lines.push(`[${table}]`, `${key} = ${value}`);
|
|
60
|
+
} else {
|
|
61
|
+
let insertAt = lines.findIndex(line => /^\s*\[/.test(line));
|
|
62
|
+
if (insertAt < 0) insertAt = lines.length;
|
|
63
|
+
lines.splice(insertAt, 0, `${key} = ${value}`);
|
|
64
|
+
}
|
|
65
|
+
return lines.join('\n');
|
|
66
|
+
}
|
|
67
|
+
const keyExpression = new RegExp(`^\\s*${key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\$&')}\\s*=`);
|
|
68
|
+
const existing = lines.slice(bounds.from, bounds.to).findIndex(line => keyExpression.test(line));
|
|
69
|
+
if (existing >= 0) {
|
|
70
|
+
lines[bounds.from + existing] = `${key} = ${value}`;
|
|
71
|
+
} else {
|
|
72
|
+
lines.splice(bounds.from, 0, `${key} = ${value}`);
|
|
73
|
+
}
|
|
74
|
+
return lines.join('\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function atomicReplace(file, temporary) {
|
|
78
|
+
try {
|
|
79
|
+
fs.renameSync(temporary, file);
|
|
80
|
+
return;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error?.code !== 'EEXIST' && error?.code !== 'EPERM') throw error;
|
|
83
|
+
}
|
|
84
|
+
const backup = `${file}.evolcore-backup-${process.pid}-${Date.now()}`;
|
|
85
|
+
fs.renameSync(file, backup);
|
|
86
|
+
try {
|
|
87
|
+
fs.renameSync(temporary, file);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
try { fs.renameSync(backup, file); } catch {}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
try { fs.rmSync(backup, { force: true }); } catch {}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function atomicWrite(file, content) {
|
|
96
|
+
const temporary = `${file}.evolcore-${process.pid}-${Date.now()}.tmp`;
|
|
97
|
+
try {
|
|
98
|
+
fs.writeFileSync(temporary, content, { encoding: 'utf8', mode: 0o440, flag: 'wx' });
|
|
99
|
+
atomicReplace(file, temporary);
|
|
100
|
+
} finally {
|
|
101
|
+
try { fs.rmSync(temporary, { force: true }); } catch {}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function restrictWindowsAcl(target) {
|
|
106
|
+
if (process.platform !== 'win32') return;
|
|
107
|
+
// Keep the hook and requirements administrator-owned while allowing the
|
|
108
|
+
// daemon's normal user token to read and execute them.
|
|
109
|
+
execFileSync('icacls.exe', [
|
|
110
|
+
target,
|
|
111
|
+
'/inheritance:r',
|
|
112
|
+
'/grant:r',
|
|
113
|
+
'*S-1-5-18:(OI)(CI)F',
|
|
114
|
+
'*S-1-5-32-544:(OI)(CI)F',
|
|
115
|
+
'*S-1-5-32-545:(OI)(CI)RX',
|
|
116
|
+
], { stdio: 'ignore' });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function managedHookLauncher(packageRoot) {
|
|
120
|
+
// Keep the machine-wide entrypoint stable across npm upgrades. The app
|
|
121
|
+
// npm upgrades replace the package contents at this stable global path, so
|
|
122
|
+
// this file never needs to be rewritten just because the hook changed.
|
|
123
|
+
const trustedPackageRoot = path.resolve(packageRoot);
|
|
124
|
+
return `#!/usr/bin/env node
|
|
125
|
+
import path from 'node:path';
|
|
126
|
+
import { pathToFileURL } from 'node:url';
|
|
127
|
+
|
|
128
|
+
// EVOLCORE_MANAGED_HOOK_LAUNCHER
|
|
129
|
+
const packageRoot = ${JSON.stringify(trustedPackageRoot)};
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
await import(pathToFileURL(path.join(packageRoot, 'bin', 'codex-managed-hook.mjs')).href);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
process.stderr.write('[evolcore] failed to load the current managed hook: '
|
|
135
|
+
+ (error instanceof Error ? error.message : String(error)) + '\\n');
|
|
136
|
+
process.exit(2);
|
|
137
|
+
}
|
|
138
|
+
`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function main() {
|
|
142
|
+
if (process.platform !== 'win32' && !argument('--requirements')) {
|
|
143
|
+
fail('Codex managed hook provisioning is only supported on Windows.');
|
|
144
|
+
}
|
|
145
|
+
const packageRoot = argument('--package-root');
|
|
146
|
+
const nodePath = argument('--node-path') || process.execPath;
|
|
147
|
+
const requirements = argument('--requirements')
|
|
148
|
+
|| path.join(process.env.ProgramData || path.join(os.homedir(), 'ProgramData'), 'OpenAI', 'Codex', 'requirements.toml');
|
|
149
|
+
if (!packageRoot) fail('--package-root is required.');
|
|
150
|
+
const existingRequirements = fs.existsSync(requirements) ? fs.readFileSync(requirements, 'utf8') : '';
|
|
151
|
+
const targetDirectory = argument('--managed-dir')
|
|
152
|
+
|| path.join(path.dirname(requirements), 'EvolCore', 'hooks');
|
|
153
|
+
const sourceHook = path.join(packageRoot, 'bin', 'codex-managed-hook.mjs');
|
|
154
|
+
if (!fs.statSync(sourceHook).isFile()) fail(`Managed hook source was not found: ${sourceHook}`);
|
|
155
|
+
|
|
156
|
+
fs.mkdirSync(targetDirectory, { recursive: true, mode: 0o700 });
|
|
157
|
+
const hookPath = path.join(targetDirectory, 'pre-tool-use.mjs');
|
|
158
|
+
const temporaryHook = `${hookPath}.evolcore-${process.pid}-${Date.now()}.tmp`;
|
|
159
|
+
try {
|
|
160
|
+
fs.writeFileSync(temporaryHook, managedHookLauncher(packageRoot), { encoding: 'utf8', mode: 0o500, flag: 'wx' });
|
|
161
|
+
fs.chmodSync(temporaryHook, 0o500);
|
|
162
|
+
atomicReplace(hookPath, temporaryHook);
|
|
163
|
+
} finally {
|
|
164
|
+
try { fs.rmSync(temporaryHook, { force: true }); } catch {}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let content = existingRequirements;
|
|
168
|
+
content = removeManagedBlock(content);
|
|
169
|
+
content = setTomlKey(content, null, 'allow_managed_hooks_only', 'true');
|
|
170
|
+
content = setTomlKey(content, 'features', 'hooks', 'true');
|
|
171
|
+
content = setTomlKey(content, 'hooks', 'windows_managed_dir', tomlString(path.resolve(targetDirectory)));
|
|
172
|
+
if (!content.endsWith('\n')) content += '\n';
|
|
173
|
+
const command = `${windowsCommandArg(nodePath)} ${windowsCommandArg(hookPath)}`;
|
|
174
|
+
content += [
|
|
175
|
+
markerStart,
|
|
176
|
+
'[[hooks.PreToolUse]]',
|
|
177
|
+
'matcher = "*"',
|
|
178
|
+
'',
|
|
179
|
+
'[[hooks.PreToolUse.hooks]]',
|
|
180
|
+
'type = "command"',
|
|
181
|
+
`command = ${tomlString(command)}`,
|
|
182
|
+
`command_windows = ${tomlString(command)}`,
|
|
183
|
+
'timeout = 3',
|
|
184
|
+
'statusMessage = "Checking EvolCore session policy"',
|
|
185
|
+
markerEnd,
|
|
186
|
+
'',
|
|
187
|
+
].join('\n');
|
|
188
|
+
fs.mkdirSync(path.dirname(requirements), { recursive: true });
|
|
189
|
+
atomicWrite(requirements, content);
|
|
190
|
+
try { fs.chmodSync(requirements, 0o440); } catch {}
|
|
191
|
+
restrictWindowsAcl(targetDirectory);
|
|
192
|
+
restrictWindowsAcl(hookPath);
|
|
193
|
+
restrictWindowsAcl(requirements);
|
|
194
|
+
process.stdout.write(`${JSON.stringify({ requirements, managedDir: path.resolve(targetDirectory), hookPath })}\n`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
main();
|
|
199
|
+
} catch (error) {
|
|
200
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
201
|
+
}
|
|
@@ -20,11 +20,11 @@ import { getPackageRoot, resolvePaths } from '../paths.js';
|
|
|
20
20
|
import { resolveEffective } from '../config/config-manager.js';
|
|
21
21
|
import { sanitizeSessionTitle } from '../core/session/session-title.js';
|
|
22
22
|
import { resolveClaudeCapabilityRunOptionsForProject } from '../core/capability/capability-manager.js';
|
|
23
|
-
import { normalizePermissionMode } from '../core/permission/mode.js';
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import { buildHClassGuardCommand, ensureClaudeGitWorktreeConfig, prependExecutableDirectory, resolveBubblewrapPath, shouldFailIfClaudeSandboxUnavailable, } from '../core/permission/sandbox-runtime.js';
|
|
23
|
+
import { normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
|
|
24
|
+
import { buildClaudeProtectedFilesystem, containsHClassReference, containsLClassReference, isHClassPath, isLClassPath, isSameOrDescendant, resolveProtectedCandidate, } from '../core/protected-paths.js';
|
|
25
|
+
import { buildHClassGuardCommand, createSandboxInitializationError, isSandboxInitializationFailure, SANDBOX_INITIALIZATION_FAILED, ensureClaudeGitWorktreeConfig, prependExecutableDirectory, resolveBubblewrapPath, shouldFailIfClaudeSandboxUnavailable, } from '../core/permission/sandbox-runtime.js';
|
|
27
26
|
import { buildClaudeUnixSocketAllowlist } from '../core/permission/unix-socket-policy.js';
|
|
27
|
+
import { auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
|
|
28
28
|
import { contextTokensForUsage, usageForContext, isClaudeContextUsageModel, isOneMillionContextModel, realContextWindowForModel, autoCompactWindowForModel } from './runner-types.js';
|
|
29
29
|
export { hasCompact, hasModelSwitcher, hasPermissionController } from './runner-types.js';
|
|
30
30
|
// Built-in tools execute inside the Claude runtime and are covered by the
|
|
@@ -217,6 +217,32 @@ function buildClaudeManagedLockdownSettings(capabilityOptions) {
|
|
|
217
217
|
};
|
|
218
218
|
}
|
|
219
219
|
const CLAUDE_SANDBOX_GLOB_MAGIC = /[*?[\]]/;
|
|
220
|
+
function trustedRuntimeWritePaths(runtimeEnv) {
|
|
221
|
+
const tmp = process.env.TMPDIR?.trim();
|
|
222
|
+
if (!tmp || !path.isAbsolute(tmp))
|
|
223
|
+
return [];
|
|
224
|
+
const root = resolveProtectedCandidate(tmp);
|
|
225
|
+
const candidates = [runtimeEnv?.EVOLCORE_SESSION_RUNTIME_DIR, runtimeEnv?.EVOLCORE_RUNTIME_LOCK_DIR]
|
|
226
|
+
.filter((value) => typeof value === 'string' && path.isAbsolute(value));
|
|
227
|
+
const paths = [];
|
|
228
|
+
for (const candidate of candidates) {
|
|
229
|
+
const resolved = resolveProtectedCandidate(candidate);
|
|
230
|
+
if (!isSameOrDescendant(resolved, root))
|
|
231
|
+
continue;
|
|
232
|
+
try {
|
|
233
|
+
const stat = fs.lstatSync(resolved);
|
|
234
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
235
|
+
continue;
|
|
236
|
+
if (typeof process.getuid === 'function' && stat.uid !== process.getuid())
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
paths.push(resolved, path.join(resolved, '**'));
|
|
243
|
+
}
|
|
244
|
+
return [...new Set(paths)];
|
|
245
|
+
}
|
|
220
246
|
async function assertClaudeSettingSourcesHaveLiteralSandboxPaths(cwd, settingSources, managedSettings) {
|
|
221
247
|
if (process.platform !== 'linux' || settingSources.length === 0)
|
|
222
248
|
return;
|
|
@@ -1759,6 +1785,30 @@ export class AgentRunner {
|
|
|
1759
1785
|
else {
|
|
1760
1786
|
logger.error(`[AgentRunner] Subprocess stream failed (session=${sessionId}) with no captured stderr.`);
|
|
1761
1787
|
}
|
|
1788
|
+
if (isSandboxInitializationFailure(err, buf ?? [])) {
|
|
1789
|
+
const detail = createSandboxInitializationError(err, buf ?? []);
|
|
1790
|
+
await this.permissionContexts.get(sessionId)?.recordExecutionAnomaly?.({
|
|
1791
|
+
code: 'capability_unavailable',
|
|
1792
|
+
severity: 'warning',
|
|
1793
|
+
phase: 'execution',
|
|
1794
|
+
occurredAt: Date.now(),
|
|
1795
|
+
toolName: 'Bash',
|
|
1796
|
+
policyCode: SANDBOX_INITIALIZATION_FAILED,
|
|
1797
|
+
decisionSource: 'infrastructure',
|
|
1798
|
+
agentAid: this.permissionContexts.get(sessionId)?.selfAid,
|
|
1799
|
+
summary: detail.message.slice(0, 512),
|
|
1800
|
+
effect: 'operation_skipped',
|
|
1801
|
+
});
|
|
1802
|
+
yield {
|
|
1803
|
+
type: 'complete',
|
|
1804
|
+
isError: true,
|
|
1805
|
+
subtype: 'infrastructure',
|
|
1806
|
+
terminalReason: SANDBOX_INITIALIZATION_FAILED,
|
|
1807
|
+
errors: [detail.message],
|
|
1808
|
+
queryFinal: true,
|
|
1809
|
+
};
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1762
1812
|
throw err;
|
|
1763
1813
|
}
|
|
1764
1814
|
finally {
|
|
@@ -1856,8 +1906,25 @@ export class AgentRunner {
|
|
|
1856
1906
|
const code = policyCode ?? (preflight?.behavior === 'deny' ? preflight.policyCode : undefined);
|
|
1857
1907
|
if (!code)
|
|
1858
1908
|
return;
|
|
1909
|
+
const ctx = this.permissionContexts.get(sessionId);
|
|
1910
|
+
const summary = summarizeToolInputForAudit(toolName, toolInput).slice(0, 512);
|
|
1911
|
+
auditToolPreflightDenial({
|
|
1912
|
+
toolName,
|
|
1913
|
+
policyCode: code,
|
|
1914
|
+
reason: preflight?.behavior === 'deny' ? (preflight.message ?? 'policy denied') : 'policy denied',
|
|
1915
|
+
summary,
|
|
1916
|
+
sessionId,
|
|
1917
|
+
agentAid: ctx?.selfAid,
|
|
1918
|
+
permissionMode: callPermissionMode,
|
|
1919
|
+
channel: ctx?.channel,
|
|
1920
|
+
actorId: ctx?.userId,
|
|
1921
|
+
role: ctx?.role,
|
|
1922
|
+
selfAid: ctx?.selfAid,
|
|
1923
|
+
requestId: typeof requestId === 'string' ? requestId : undefined,
|
|
1924
|
+
taskId: ctx?.taskId,
|
|
1925
|
+
});
|
|
1859
1926
|
try {
|
|
1860
|
-
await
|
|
1927
|
+
await ctx?.recordExecutionAnomaly?.({
|
|
1861
1928
|
code: 'operation_blocked',
|
|
1862
1929
|
severity: 'warning',
|
|
1863
1930
|
phase: 'execution',
|
|
@@ -1865,7 +1932,10 @@ export class AgentRunner {
|
|
|
1865
1932
|
toolName,
|
|
1866
1933
|
...(typeof requestId === 'string' && requestId ? { requestId } : {}),
|
|
1867
1934
|
policyCode: code,
|
|
1868
|
-
|
|
1935
|
+
decisionSource: 'policy',
|
|
1936
|
+
agentAid: ctx?.selfAid,
|
|
1937
|
+
permissionMode: callPermissionMode,
|
|
1938
|
+
summary,
|
|
1869
1939
|
effect: 'operation_skipped',
|
|
1870
1940
|
});
|
|
1871
1941
|
}
|
|
@@ -1932,6 +2002,7 @@ export class AgentRunner {
|
|
|
1932
2002
|
role: permCtx?.role,
|
|
1933
2003
|
permissionMode: callPermissionMode,
|
|
1934
2004
|
allowHostProcessCommands: true,
|
|
2005
|
+
allowProtectedMetadata: false,
|
|
1935
2006
|
safeOutputHelperPath: path.join(getPackageRoot(), 'bin', 'ec-safe-output.js'),
|
|
1936
2007
|
projectPath,
|
|
1937
2008
|
});
|
|
@@ -1963,10 +2034,11 @@ export class AgentRunner {
|
|
|
1963
2034
|
channel: permCtx?.channel,
|
|
1964
2035
|
peerId: permCtx?.userId,
|
|
1965
2036
|
role: permCtx?.role,
|
|
2037
|
+
allowProtectedMetadata: false,
|
|
1966
2038
|
};
|
|
1967
2039
|
const roResult = checkReadonly(toolName, toolInput, projectPath, readonlyContext);
|
|
1968
2040
|
if (roResult.behavior === 'deny') {
|
|
1969
|
-
await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, 'readonly_mode');
|
|
2041
|
+
await recordPreflightDenial(toolName, toolInput, undefined, input.tool_use_id ?? input.toolUseID, roResult.policyCode ?? 'readonly_mode');
|
|
1970
2042
|
return hookDecision('deny', roResult.message, updatedInput);
|
|
1971
2043
|
}
|
|
1972
2044
|
return hookDecision('allow', undefined, updatedInput);
|
|
@@ -2052,6 +2124,7 @@ export class AgentRunner {
|
|
|
2052
2124
|
role: permCtx?.role,
|
|
2053
2125
|
permissionMode: callPermissionMode,
|
|
2054
2126
|
allowHostProcessCommands: true,
|
|
2127
|
+
allowProtectedMetadata: false,
|
|
2055
2128
|
safeOutputHelperPath: path.join(getPackageRoot(), 'bin', 'ec-safe-output.js'),
|
|
2056
2129
|
projectPath,
|
|
2057
2130
|
});
|
|
@@ -2119,10 +2192,11 @@ export class AgentRunner {
|
|
|
2119
2192
|
channel: permCtx?.channel,
|
|
2120
2193
|
peerId: permCtx?.userId,
|
|
2121
2194
|
role: permCtx?.role,
|
|
2195
|
+
allowProtectedMetadata: false,
|
|
2122
2196
|
};
|
|
2123
2197
|
const roResult = checkReadonly(toolName, input, projectPath, readonlyContext);
|
|
2124
2198
|
if (roResult.behavior === 'deny') {
|
|
2125
|
-
await recordPreflightDenial(toolName, input, undefined, options.toolUseID, 'readonly_mode');
|
|
2199
|
+
await recordPreflightDenial(toolName, input, undefined, options.toolUseID, roResult.policyCode ?? 'readonly_mode');
|
|
2126
2200
|
return { behavior: 'deny', message: roResult.message, decisionClassification: 'user_reject' };
|
|
2127
2201
|
}
|
|
2128
2202
|
return { behavior: 'allow', updatedInput: input, decisionClassification: 'user_permanent' };
|
|
@@ -2213,6 +2287,7 @@ export class AgentRunner {
|
|
|
2213
2287
|
const sandboxUnixSockets = buildClaudeUnixSocketAllowlist(projectPath, {
|
|
2214
2288
|
role: runPermissionContext?.role,
|
|
2215
2289
|
});
|
|
2290
|
+
const runtimeWritePaths = trustedRuntimeWritePaths(runtimeEnv);
|
|
2216
2291
|
await assertClaudeSettingSourcesHaveLiteralSandboxPaths(projectPath, [...settingSources], managedSettings);
|
|
2217
2292
|
const sandboxProjectionDurationMs = Date.now() - sandboxProjectionStartedAt;
|
|
2218
2293
|
if (sandboxProjectionDurationMs > 2_000) {
|
|
@@ -2229,7 +2304,10 @@ export class AgentRunner {
|
|
|
2229
2304
|
allowAllUnixSockets: false,
|
|
2230
2305
|
allowLocalBinding: false,
|
|
2231
2306
|
},
|
|
2232
|
-
filesystem:
|
|
2307
|
+
filesystem: {
|
|
2308
|
+
...sandboxFilesystem,
|
|
2309
|
+
...(runtimeWritePaths.length > 0 ? { allowWrite: runtimeWritePaths } : {}),
|
|
2310
|
+
},
|
|
2233
2311
|
};
|
|
2234
2312
|
})();
|
|
2235
2313
|
if (executionSandbox.state === 'off') {
|
|
@@ -2373,12 +2451,28 @@ export class AgentRunner {
|
|
|
2373
2451
|
logger.info('[AgentRunner] Creating query with images:', images.length, 'first image size:', images[0]?.data?.length ?? 0);
|
|
2374
2452
|
logger.debug('[AgentRunner] Skipping resume for image message to avoid history conflict');
|
|
2375
2453
|
msgStream.push(prompt, images, inputId);
|
|
2376
|
-
|
|
2454
|
+
try {
|
|
2455
|
+
sdkStream = createQuery(msgStream);
|
|
2456
|
+
}
|
|
2457
|
+
catch (error) {
|
|
2458
|
+
const stderr = this.recentStderr.get(sessionId) ?? [];
|
|
2459
|
+
if (isSandboxInitializationFailure(error, stderr))
|
|
2460
|
+
throw createSandboxInitializationError(error, stderr);
|
|
2461
|
+
throw error;
|
|
2462
|
+
}
|
|
2377
2463
|
}
|
|
2378
2464
|
else {
|
|
2379
2465
|
logger.debug('[AgentRunner] Creating query with text only, agentSessionId:', initialClaudeSessionId);
|
|
2380
2466
|
msgStream.push(prompt, undefined, inputId);
|
|
2381
|
-
|
|
2467
|
+
try {
|
|
2468
|
+
sdkStream = createQuery(msgStream, agentSessionId, resumeAt);
|
|
2469
|
+
}
|
|
2470
|
+
catch (error) {
|
|
2471
|
+
const stderr = this.recentStderr.get(sessionId) ?? [];
|
|
2472
|
+
if (isSandboxInitializationFailure(error, stderr))
|
|
2473
|
+
throw createSandboxInitializationError(error, stderr);
|
|
2474
|
+
throw error;
|
|
2475
|
+
}
|
|
2382
2476
|
}
|
|
2383
2477
|
this.activeMessageStreams.set(sessionId, msgStream);
|
|
2384
2478
|
this.activeQueries.set(sessionId, sdkStream);
|