evolcore 0.0.22 → 0.0.24
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 +74 -14
- package/README.md +4 -0
- package/bin/codex-managed-acl.ps1 +160 -0
- package/bin/codex-managed-hook.mjs +127 -10
- package/bin/ec-safe-output.js +85 -3
- package/bin/install-codex-managed-hooks.mjs +384 -95
- package/dist/agents/baseagent-availability.js +48 -0
- package/dist/agents/baseagent.js +30 -2
- package/dist/agents/claude-runner.js +179 -240
- package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +90 -20
- package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
- package/dist/agents/codex/catalog.js +70 -0
- package/dist/agents/codex/diagnostics.js +369 -0
- package/dist/agents/codex-runner.js +839 -192
- package/dist/agents/ecagent-runner.js +27 -34
- package/dist/agents/gemini-runner.js +62 -8
- package/dist/agents/runner-types.js +1 -1
- package/dist/aun/aid/managed-operation.js +28 -6
- package/dist/aun/aid/store.js +43 -0
- package/dist/aun/menu-client.js +272 -0
- package/dist/aun/msg/group.js +65 -4
- package/dist/aun/msg/index.js +1 -1
- package/dist/aun/msg/managed-operation.js +57 -5
- package/dist/aun/outbox.js +56 -18
- package/dist/aun/rpc/client.js +7 -1
- package/dist/channels/aun.js +178 -77
- package/dist/channels/daemon.js +7 -1
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +10 -4
- package/dist/channels/wechat.js +81 -10
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/agent-command.js +29 -3
- package/dist/cli/agent-create.js +538 -0
- package/dist/cli/agent.js +26 -675
- package/dist/cli/aun-commands.js +108 -24
- package/dist/cli/command-log.js +21 -2
- package/dist/cli/ctl-command.js +2 -0
- package/dist/cli/daemon-commands.js +289 -61
- package/dist/cli/index.js +22 -2
- package/dist/cli/init.js +2 -10
- package/dist/cli/managed-command-guard.js +3 -0
- package/dist/cli/menu-client.js +95 -0
- package/dist/cli/model.js +23 -8
- package/dist/cli/restart-monitor.js +432 -8
- package/dist/cli/stats.js +110 -0
- package/dist/cli/task-context.js +12 -2
- package/dist/cli/trigger-command.js +9 -1
- package/dist/config/builtin-roles.js +2 -0
- package/dist/config/schema-migration.js +14 -2
- package/dist/core/auth/agent-delegation.js +10 -7
- package/dist/core/auth/auth-gateway.js +3 -2
- package/dist/core/auth/authorization-audit.js +75 -18
- package/dist/core/auth/operation-authorizer.js +63 -3
- package/dist/core/auth/operation-catalog.js +14 -2
- package/dist/core/auth/remote-access-store.js +136 -0
- package/dist/core/baseagent-loader.js +4 -4
- package/dist/core/bootstrap-messages.js +39 -18
- package/dist/core/bootstrap-service.js +32 -26
- package/dist/core/capability/capability-manager.js +8 -4
- package/dist/core/capability/providers/codex-capability-provider.js +1 -1
- package/dist/core/command/agent-control.js +7 -2
- package/dist/core/command/command-handler.js +128 -30
- package/dist/core/command/daemon-diagnostics.js +448 -0
- package/dist/core/command/menu-catalog.js +6 -1
- package/dist/core/command/menu-handler.js +410 -98
- package/dist/core/command/menu-protocol.js +123 -0
- package/dist/core/command/slash-gate.js +4 -6
- package/dist/core/command/slash-handler.js +199 -70
- package/dist/core/data-migration.js +5 -15
- package/dist/core/event-catalog.js +3 -0
- package/dist/core/handoff/runtime.js +3 -1
- package/dist/core/handoff/store.js +7 -1
- package/dist/core/message/im-renderer.js +13 -6
- package/dist/core/message/message-bridge.js +5 -36
- package/dist/core/message/message-queue.js +20 -15
- package/dist/core/message/message-utils.js +6 -3
- package/dist/core/message/response-engine.js +147 -94
- package/dist/core/model/catalog-service.js +310 -0
- package/dist/core/model/model-catalog.js +134 -334
- package/dist/core/model/model-diagnostics.js +4 -5
- package/dist/core/model/model-fallback.js +9 -16
- package/dist/core/model/model-permission.js +5 -2
- package/dist/core/permission/approval-gateway.js +183 -32
- package/dist/core/permission/protected-paths.js +63 -4
- package/dist/core/permission/sandbox-runtime.js +4 -2
- package/dist/core/permission/tool-policy.js +257 -31
- package/dist/core/runtime-lock.js +13 -8
- package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
- package/dist/core/session/session-manager.js +49 -13
- package/dist/core/session/session-renew.js +1 -0
- package/dist/index.js +214 -57
- package/dist/ipc.js +191 -102
- package/dist/paths.js +23 -1
- package/dist/stats/billing.js +18 -4
- package/dist/trigger/manager.js +3 -1
- package/dist/trigger/parser.js +77 -13
- package/dist/trigger/patch.js +104 -1
- package/dist/utils/atomic-write.js +26 -2
- package/dist/utils/cross-platform.js +436 -1
- package/dist/utils/ecweb-supervisor.js +11 -2
- package/dist/utils/ecweb-utils.js +8 -26
- package/dist/utils/error-utils.js +16 -2
- package/dist/utils/model-prices.jsonl +23 -20
- package/dist/utils/npm-ops.js +70 -15
- package/dist/utils/windows-autostart.js +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -0
- package/kits/docs/evolcore/agent-create.md +13 -0
- package/kits/docs/evolcore/group-collaboration.md +7 -2
- package/kits/docs/evolcore/menu-client.md +34 -0
- package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
- package/kits/migrations/migrate-role-config-v4.mjs +11 -5
- package/kits/migrations/migrate-role-config-v5.mjs +8 -2
- package/kits/templates/roles/admin.json +2 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +8 -1
- package/package.json +16 -4
- package/dist/utils/codex-cli.js +0 -43
|
@@ -91,15 +91,179 @@ export function maskManagedTempShellPaths(command, options = {}) {
|
|
|
91
91
|
return `__managed_tmp_${index}__`;
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
|
-
// 绝对禁止命令(所有角色、所有权限模式下都禁止,不可授权)
|
|
95
|
-
// 这些是系统级破坏操作,任何情况下都不应该允许
|
|
96
94
|
const ABSOLUTE_FORBIDDEN = [
|
|
97
|
-
/\bshutdown\b
|
|
98
|
-
/\breboot\b
|
|
99
|
-
/\bmkfs
|
|
100
|
-
/\bformat\s+[a-zA-Z]:/i,
|
|
101
|
-
/\bdd\s+if=.*of=\/dev
|
|
95
|
+
/\bshutdown\b/i,
|
|
96
|
+
/\breboot\b/i,
|
|
97
|
+
/\bmkfs(?:\.[A-Za-z0-9._-]+)?\b/i,
|
|
98
|
+
/\bformat\s+[a-zA-Z]:/i,
|
|
99
|
+
/\bdd\s+if=.*of=\/dev(?:\/|\b)/i,
|
|
102
100
|
];
|
|
101
|
+
const SCRIPT_EXECUTION_RE = /(?:\bos\.system\s*\(|\bsubprocess\.(?:run|call|Popen)\s*\(|\bchild_process\.(?:exec|spawn|fork)\s*\(|\b(?:system|exec|spawn|popen)\s*\()/i;
|
|
102
|
+
function hereDocDelimiter(line) {
|
|
103
|
+
let quote;
|
|
104
|
+
let escaped = false;
|
|
105
|
+
for (let index = 0; index < line.length - 1; index++) {
|
|
106
|
+
const char = line[index];
|
|
107
|
+
if (escaped) {
|
|
108
|
+
escaped = false;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (char === '\\' && quote !== 'single') {
|
|
112
|
+
escaped = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (quote === 'single') {
|
|
116
|
+
if (char === "'")
|
|
117
|
+
quote = undefined;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (quote === 'double') {
|
|
121
|
+
if (char === '"')
|
|
122
|
+
quote = undefined;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (char === "'") {
|
|
126
|
+
quote = 'single';
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (char === '"') {
|
|
130
|
+
quote = 'double';
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (char !== '<' || line[index + 1] !== '<' || line[index + 2] === '<')
|
|
134
|
+
continue;
|
|
135
|
+
let cursor = index + 2;
|
|
136
|
+
if (line[cursor] === '-')
|
|
137
|
+
cursor++;
|
|
138
|
+
while (cursor < line.length && /\s/.test(line[cursor]))
|
|
139
|
+
cursor++;
|
|
140
|
+
if (line[cursor] === "'" || line[cursor] === '"') {
|
|
141
|
+
const delimiterQuote = line[cursor++];
|
|
142
|
+
const start = cursor;
|
|
143
|
+
while (cursor < line.length && line[cursor] !== delimiterQuote)
|
|
144
|
+
cursor++;
|
|
145
|
+
return cursor > start && cursor < line.length ? line.slice(start, cursor) : undefined;
|
|
146
|
+
}
|
|
147
|
+
const match = line.slice(cursor).match(/^[A-Za-z_][A-Za-z0-9_-]*/);
|
|
148
|
+
return match?.[0];
|
|
149
|
+
}
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
function splitHereDocBodies(command) {
|
|
153
|
+
const lines = command.split(/\r?\n/);
|
|
154
|
+
let delimiter;
|
|
155
|
+
let body;
|
|
156
|
+
const bodies = [];
|
|
157
|
+
const retained = [];
|
|
158
|
+
for (const line of lines) {
|
|
159
|
+
if (delimiter) {
|
|
160
|
+
const candidate = line.replace(/^\t+/, '').trim();
|
|
161
|
+
if (candidate === delimiter) {
|
|
162
|
+
bodies.push((body ?? []).join('\n'));
|
|
163
|
+
delimiter = undefined;
|
|
164
|
+
body = undefined;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
body?.push(line);
|
|
168
|
+
}
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
retained.push(line);
|
|
172
|
+
delimiter = hereDocDelimiter(line);
|
|
173
|
+
if (delimiter)
|
|
174
|
+
body = [];
|
|
175
|
+
}
|
|
176
|
+
if (delimiter && body)
|
|
177
|
+
bodies.push(body.join('\n'));
|
|
178
|
+
return { command: retained.join('\n'), bodies };
|
|
179
|
+
}
|
|
180
|
+
function containsAbsoluteForbiddenText(value) {
|
|
181
|
+
return ABSOLUTE_FORBIDDEN.some(pattern => pattern.test(value));
|
|
182
|
+
}
|
|
183
|
+
function interpreterScript(args) {
|
|
184
|
+
const index = args.findIndex(argument => argument === '-c' || argument === '-e' || argument === '--eval');
|
|
185
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
186
|
+
}
|
|
187
|
+
function scriptContainsAbsoluteForbidden(script) {
|
|
188
|
+
return SCRIPT_EXECUTION_RE.test(script) && containsAbsoluteForbiddenText(script);
|
|
189
|
+
}
|
|
190
|
+
/** Match only executable argv, never regex/search text or script literals. */
|
|
191
|
+
function containsAbsoluteForbiddenCommand(command, depth = 0) {
|
|
192
|
+
const hereDocs = splitHereDocBodies(command);
|
|
193
|
+
if (containsShellSubstitution(hereDocs.command) && containsAbsoluteForbiddenText(hereDocs.command))
|
|
194
|
+
return true;
|
|
195
|
+
return splitShellCommandSegments(hereDocs.command).some(segment => {
|
|
196
|
+
const parsed = parseEffectiveCommand(segment);
|
|
197
|
+
if (!parsed)
|
|
198
|
+
return false;
|
|
199
|
+
const { executable, args } = parsed;
|
|
200
|
+
if (executable === 'shutdown' || executable === 'reboot')
|
|
201
|
+
return true;
|
|
202
|
+
if (/^mkfs(?:\..*)?$/.test(executable))
|
|
203
|
+
return true;
|
|
204
|
+
if (executable === 'format' && args.some(argument => /^[A-Za-z]:$/.test(argument)))
|
|
205
|
+
return true;
|
|
206
|
+
if (executable === 'dd'
|
|
207
|
+
&& args.some(argument => /^if=/.test(argument))
|
|
208
|
+
&& args.some(argument => /^of=\/dev(?:\/|$)/.test(argument)))
|
|
209
|
+
return true;
|
|
210
|
+
if (executable === 'eval' && containsAbsoluteForbiddenText(args.join(' ')))
|
|
211
|
+
return true;
|
|
212
|
+
if (['node', 'nodejs', 'python', 'python3', 'perl'].includes(executable)) {
|
|
213
|
+
const script = interpreterScript(args);
|
|
214
|
+
if (script && scriptContainsAbsoluteForbidden(script))
|
|
215
|
+
return true;
|
|
216
|
+
if (hereDocs.bodies.some(scriptContainsAbsoluteForbidden))
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
if (['bash', 'sh', 'zsh', 'dash'].includes(executable)
|
|
220
|
+
&& hereDocs.bodies.some(body => containsAbsoluteForbiddenCommand(body, depth + 1)))
|
|
221
|
+
return true;
|
|
222
|
+
if (depth >= 2 || !['bash', 'sh', 'zsh', 'dash', 'cmd', 'cmd.exe'].includes(executable))
|
|
223
|
+
return false;
|
|
224
|
+
const commandFlag = executable.startsWith('cmd') ? '/c' : '-c';
|
|
225
|
+
const commandIndex = args.findIndex(argument => argument.toLowerCase() === commandFlag);
|
|
226
|
+
return commandIndex >= 0
|
|
227
|
+
&& !!args[commandIndex + 1]
|
|
228
|
+
&& containsAbsoluteForbiddenCommand(args.slice(commandIndex + 1).join(' '), depth + 1);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
function containsShellSubstitution(command) {
|
|
232
|
+
let quote;
|
|
233
|
+
let escaped = false;
|
|
234
|
+
for (let index = 0; index < command.length; index++) {
|
|
235
|
+
const char = command[index];
|
|
236
|
+
if (escaped) {
|
|
237
|
+
escaped = false;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (char === '\\' && quote !== 'single') {
|
|
241
|
+
escaped = true;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (quote === 'single') {
|
|
245
|
+
if (char === "'")
|
|
246
|
+
quote = undefined;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (quote === 'double') {
|
|
250
|
+
if (char === '"')
|
|
251
|
+
quote = undefined;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (char === "'") {
|
|
255
|
+
quote = 'single';
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (char === '"') {
|
|
259
|
+
quote = 'double';
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (char === '`' || (char === '$' && command[index + 1] === '('))
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
103
267
|
// 危险操作(需要用户审批才能执行)
|
|
104
268
|
// 这些操作有合理使用场景,但需要用户明确授权
|
|
105
269
|
const DANGEROUS_PATTERNS = [
|
|
@@ -386,7 +550,8 @@ function classifyStructuredCommand(command, depth = 0) {
|
|
|
386
550
|
return matches.find(match => match.approvalScope === 'all') ?? matches[0];
|
|
387
551
|
}
|
|
388
552
|
function containsNetStopCommand(command, depth = 0) {
|
|
389
|
-
|
|
553
|
+
const hereDocs = splitHereDocBodies(command);
|
|
554
|
+
return splitShellCommandSegments(hereDocs.command).some(segment => {
|
|
390
555
|
const parsed = parseEffectiveCommand(segment);
|
|
391
556
|
if (!parsed)
|
|
392
557
|
return false;
|
|
@@ -394,6 +559,9 @@ function containsNetStopCommand(command, depth = 0) {
|
|
|
394
559
|
&& parsed.args[0]?.toLowerCase() === 'stop') {
|
|
395
560
|
return true;
|
|
396
561
|
}
|
|
562
|
+
if (['bash', 'sh', 'zsh', 'dash'].includes(parsed.executable)
|
|
563
|
+
&& hereDocs.bodies.some(body => containsNetStopCommand(body, depth + 1)))
|
|
564
|
+
return true;
|
|
397
565
|
if (depth >= 2 || !['bash', 'sh', 'zsh', 'dash', 'cmd', 'cmd.exe'].includes(parsed.executable)) {
|
|
398
566
|
return false;
|
|
399
567
|
}
|
|
@@ -1192,6 +1360,54 @@ function readonlySourceDiagnosticAllowed(analysis, projectPath) {
|
|
|
1192
1360
|
&& !isLClassPath(candidate, { root: resolveRoot() });
|
|
1193
1361
|
});
|
|
1194
1362
|
}
|
|
1363
|
+
/**
|
|
1364
|
+
* A narrow exception for the common repository source inventory query. The
|
|
1365
|
+
* general recursive H-class rule remains fail-closed; only an opt-in,
|
|
1366
|
+
* single-find query whose output is limited to TypeScript/JavaScript files at
|
|
1367
|
+
* the project root can use this path.
|
|
1368
|
+
*/
|
|
1369
|
+
function boundedRootSourceEnumeration(analysis, projectPath) {
|
|
1370
|
+
if (analysis.kind !== 'proven-readonly' || analysis.ir.pipelines.length !== 1)
|
|
1371
|
+
return false;
|
|
1372
|
+
const pipeline = analysis.ir.pipelines[0];
|
|
1373
|
+
const find = pipeline[0];
|
|
1374
|
+
if (!find || find.executable !== 'find' || find.pathOperands.length !== 1 || find.pathOperands[0].access !== 'recursive') {
|
|
1375
|
+
return false;
|
|
1376
|
+
}
|
|
1377
|
+
const workspace = resolveProtectedCandidate(projectPath);
|
|
1378
|
+
if (workspace === path.parse(workspace).root)
|
|
1379
|
+
return false;
|
|
1380
|
+
const scope = resolveProtectedCandidate(find.pathOperands[0].value, projectPath);
|
|
1381
|
+
if (scope !== workspace)
|
|
1382
|
+
return false;
|
|
1383
|
+
const allowedPredicates = new Set(['-o', '-or', '-a', '-and', '-print', '-print0']);
|
|
1384
|
+
let sawSourceSuffix = false;
|
|
1385
|
+
const scopeIndex = find.argv.indexOf(find.pathOperands[0].value);
|
|
1386
|
+
if (scopeIndex < 1)
|
|
1387
|
+
return false;
|
|
1388
|
+
const args = find.argv.slice(scopeIndex + 1);
|
|
1389
|
+
for (let index = 0; index < args.length; index++) {
|
|
1390
|
+
const argument = args[index];
|
|
1391
|
+
if (argument === '-name' || argument === '-iname') {
|
|
1392
|
+
const pattern = args[++index];
|
|
1393
|
+
if (!pattern || !/^\*\.(?:ts|js)$/i.test(pattern))
|
|
1394
|
+
return false;
|
|
1395
|
+
sawSourceSuffix = true;
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
if (argument === '-type') {
|
|
1399
|
+
if (args[++index] !== 'f')
|
|
1400
|
+
return false;
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
if (!allowedPredicates.has(argument))
|
|
1404
|
+
return false;
|
|
1405
|
+
}
|
|
1406
|
+
if (!sawSourceSuffix)
|
|
1407
|
+
return false;
|
|
1408
|
+
return pipeline.slice(1).every(command => ['head', 'sort', 'grep', 'wc'].includes(command.executable)
|
|
1409
|
+
&& command.pathOperands.length === 0);
|
|
1410
|
+
}
|
|
1195
1411
|
/** A lock filename used as a query pattern is safe; an operand naming the
|
|
1196
1412
|
* actual lock path is still protected. */
|
|
1197
1413
|
function containsLockPathReference(value) {
|
|
@@ -1246,10 +1462,13 @@ export function checkReadonly(toolName, input, projectPath, context) {
|
|
|
1246
1462
|
managedTempDir,
|
|
1247
1463
|
allowProtectedMetadata: context?.allowProtectedMetadata,
|
|
1248
1464
|
});
|
|
1465
|
+
const sourceQueryAllowed = readonlySourceDiagnosticAllowed(protectedOperands.analysis, projectPath);
|
|
1466
|
+
const boundedSourceQueryAllowed = boundedRootSourceEnumeration(protectedOperands.analysis, projectPath);
|
|
1249
1467
|
const sourceDiagnosticAllowed = context?.allowReadonlySourceDiagnostics
|
|
1250
|
-
&&
|
|
1251
|
-
&&
|
|
1252
|
-
|
|
1468
|
+
&& (!protectedOperands.lClass || boundedSourceQueryAllowed)
|
|
1469
|
+
&& ((sourceQueryAllowed
|
|
1470
|
+
&& (!protectedOperands.hClass || (protectedOperands.lockDiagnostic && !protectedOperands.pathHClass)))
|
|
1471
|
+
|| boundedSourceQueryAllowed);
|
|
1253
1472
|
if (sourceDiagnosticAllowed) {
|
|
1254
1473
|
return { behavior: 'allow' };
|
|
1255
1474
|
}
|
|
@@ -1556,11 +1775,15 @@ export function checkHClassWrite(toolName, input, context) {
|
|
|
1556
1775
|
managedTempDir: context?.managedTempDir,
|
|
1557
1776
|
allowProtectedMetadata: context?.allowProtectedMetadata,
|
|
1558
1777
|
});
|
|
1778
|
+
const sourceDiagnosticProjectPath = context?.projectPath ?? process.cwd();
|
|
1779
|
+
const sourceQueryAllowed = readonlySourceDiagnosticAllowed(protectedOperands.analysis, sourceDiagnosticProjectPath);
|
|
1780
|
+
const boundedSourceQueryAllowed = boundedRootSourceEnumeration(protectedOperands.analysis, sourceDiagnosticProjectPath);
|
|
1559
1781
|
const sourceDiagnosticAllowed = context?.permissionMode === 'readonly'
|
|
1560
1782
|
&& context.allowReadonlySourceDiagnostics === true
|
|
1561
|
-
&&
|
|
1562
|
-
&&
|
|
1563
|
-
|
|
1783
|
+
&& (!protectedOperands.lClass || boundedSourceQueryAllowed)
|
|
1784
|
+
&& ((sourceQueryAllowed
|
|
1785
|
+
&& (!protectedOperands.hClass || (protectedOperands.lockDiagnostic && !protectedOperands.pathHClass)))
|
|
1786
|
+
|| boundedSourceQueryAllowed);
|
|
1564
1787
|
if (protectedOperands.hClass && !sourceDiagnosticAllowed) {
|
|
1565
1788
|
logger.warn(`[H-Class Protection] 🔒 Protected filesystem operand in shell command: tool=${toolName} ` +
|
|
1566
1789
|
`session=${context?.sessionId} channel=${context?.channel} peer=${context?.peerId} role=${context?.role}`);
|
|
@@ -1721,7 +1944,10 @@ export function checkLClassWrite(toolName, input, context) {
|
|
|
1721
1944
|
...pathOptions,
|
|
1722
1945
|
managedTempDir: context?.managedTempDir,
|
|
1723
1946
|
});
|
|
1947
|
+
const boundedSourceQueryAllowed = context?.allowReadonlySourceDiagnostics === true
|
|
1948
|
+
&& boundedRootSourceEnumeration(protectedOperands.analysis, context?.projectPath ?? process.cwd());
|
|
1724
1949
|
const lClassDenied = protectedOperands.lClass
|
|
1950
|
+
&& !boundedSourceQueryAllowed
|
|
1725
1951
|
&& (context?.permissionMode === 'readonly' || protectedOperands.analysis.kind === 'unproven');
|
|
1726
1952
|
if (lClassDenied) {
|
|
1727
1953
|
logger.warn(`[L-Class Protection] L-class shell access denied: tool=${toolName} ` +
|
|
@@ -1789,23 +2015,15 @@ export function checkBlacklist(toolName, input) {
|
|
|
1789
2015
|
if (!cmd || cmd.trim() === '') {
|
|
1790
2016
|
return { behavior: 'allow', updatedInput: input };
|
|
1791
2017
|
}
|
|
1792
|
-
|
|
2018
|
+
// 只根据解析后的 executable 和 argv 判断,避免把搜索词、正则和
|
|
2019
|
+
// heredoc/script 正文当成实际命令。
|
|
2020
|
+
if (containsNetStopCommand(cmd) || containsAbsoluteForbiddenCommand(cmd)) {
|
|
1793
2021
|
const cmdPreview = cmd.length > 60 ? cmd.substring(0, 60) + '...' : cmd;
|
|
1794
2022
|
return {
|
|
1795
2023
|
behavior: 'deny',
|
|
1796
2024
|
message: `🚫 系统级危险操作已拦截\n命令:${cmdPreview}\n\n此类操作可能导致系统损坏,任何权限模式下都不允许执行。`,
|
|
1797
2025
|
};
|
|
1798
2026
|
}
|
|
1799
|
-
// 检查绝对禁止命令(系统级破坏操作)
|
|
1800
|
-
for (const pattern of ABSOLUTE_FORBIDDEN) {
|
|
1801
|
-
if (pattern.test(cmd)) {
|
|
1802
|
-
const cmdPreview = cmd.length > 60 ? cmd.substring(0, 60) + '...' : cmd;
|
|
1803
|
-
return {
|
|
1804
|
-
behavior: 'deny',
|
|
1805
|
-
message: `🚫 系统级危险操作已拦截\n命令:${cmdPreview}\n\n此类操作可能导致系统损坏,任何权限模式下都不允许执行。`
|
|
1806
|
-
};
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
2027
|
}
|
|
1810
2028
|
// 默认允许
|
|
1811
2029
|
return { behavior: 'allow', updatedInput: input };
|
|
@@ -1943,10 +2161,11 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
1943
2161
|
: command;
|
|
1944
2162
|
const tempCheck = checkManagedTempShellCommand(commandForPolicy, managedTempOptions(context));
|
|
1945
2163
|
if (tempCheck.kind === 'deny') {
|
|
2164
|
+
const currentTmpDir = resolveManagedTempDir(managedTempOptions(context).env);
|
|
1946
2165
|
return {
|
|
1947
2166
|
behavior: 'deny',
|
|
1948
2167
|
input,
|
|
1949
|
-
message: `🔒 临时文件必须位于当前会话的 $TMPDIR
|
|
2168
|
+
message: `🔒 临时文件必须位于当前会话的 $TMPDIR 内(当前值:${currentTmpDir ?? '<不可用>'}):${tempCheck.reason}`,
|
|
1950
2169
|
policyCode: 'temporary_path_outside_tmpdir',
|
|
1951
2170
|
};
|
|
1952
2171
|
}
|
|
@@ -1997,14 +2216,20 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
1997
2216
|
const ownAid = !!managedAidArgv
|
|
1998
2217
|
&& !!context.selfAid
|
|
1999
2218
|
&& managedAidArgv.at(-1)?.replace(/^@/, '') === context.selfAid.replace(/^@/, '');
|
|
2000
|
-
const
|
|
2219
|
+
const isManagedAidPublicRead = !!context.selfAid
|
|
2220
|
+
&& !!managedAidArgv
|
|
2001
2221
|
&& managedAidArgv[0] === 'ec'
|
|
2002
2222
|
&& managedAidArgv[1] === 'aid'
|
|
2003
|
-
&&
|
|
2004
|
-
&& ((managedAidArgv.length === 4 && ['show', 'lookup'].includes(managedAidArgv[2]))
|
|
2223
|
+
&& ((managedAidArgv.length === 4 && managedAidArgv[2] === 'lookup')
|
|
2005
2224
|
|| (managedAidArgv.length === 5
|
|
2006
2225
|
&& managedAidArgv[2] === 'agentmd'
|
|
2007
2226
|
&& managedAidArgv[3] === 'get'));
|
|
2227
|
+
const isSelfManagedAidShow = !!managedAidArgv
|
|
2228
|
+
&& managedAidArgv.length === 4
|
|
2229
|
+
&& managedAidArgv[0] === 'ec'
|
|
2230
|
+
&& managedAidArgv[1] === 'aid'
|
|
2231
|
+
&& managedAidArgv[2] === 'show'
|
|
2232
|
+
&& ownAid;
|
|
2008
2233
|
const isSelfAgentMdPublish = !!managedAidArgv
|
|
2009
2234
|
&& managedAidArgv.length === 5
|
|
2010
2235
|
&& managedAidArgv[0] === 'ec'
|
|
@@ -2014,14 +2239,14 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
2014
2239
|
&& ownAid;
|
|
2015
2240
|
const canPublishSelfAgentMd = isSelfAgentMdPublish
|
|
2016
2241
|
&& ['request', 'bypass'].includes(context.permissionMode ?? '');
|
|
2017
|
-
const isAllowedManagedAidCommand =
|
|
2242
|
+
const isAllowedManagedAidCommand = isManagedAidPublicRead || isSelfManagedAidShow || canPublishSelfAgentMd;
|
|
2018
2243
|
if (context.sessionId && /^\s*ec\s+aid(?:\s|$)/i.test(policyCommand) && !isAllowedManagedAidCommand) {
|
|
2019
2244
|
return {
|
|
2020
2245
|
behavior: 'deny',
|
|
2021
2246
|
input,
|
|
2022
2247
|
message: isSelfAgentMdPublish && (context.permissionMode === 'readonly' || context.permissionMode === 'auto')
|
|
2023
2248
|
? `🔒 ${context.permissionMode} 模式禁止发布 agent.md;请切换到 request 或 bypass 后再执行 \`ec aid agentmd put <当前 self AID>\`。`
|
|
2024
|
-
: '🔒 EvolCore
|
|
2249
|
+
: '🔒 EvolCore 托管会话允许 `ec aid show <当前 self AID>`、`ec aid lookup <任意合法 AID>` 或 `ec aid agentmd get <任意合法 AID>`;`ec aid agentmd put` 还要求针对当前 self AID 且处于 request 或 bypass。可选后缀仅限 `--format json`。禁止列举本地身份、切换身份或对其它 AID 执行 show/put。',
|
|
2025
2250
|
policyCode: 'managed_ec_aid_scope_forbidden',
|
|
2026
2251
|
};
|
|
2027
2252
|
}
|
|
@@ -2136,6 +2361,7 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
2136
2361
|
workspacePath: context.workspacePath,
|
|
2137
2362
|
root: context.root,
|
|
2138
2363
|
managedTempDir: context.managedTempDir,
|
|
2364
|
+
allowReadonlySourceDiagnostics: context.allowReadonlySourceDiagnostics,
|
|
2139
2365
|
});
|
|
2140
2366
|
if (lClass.behavior === 'deny') {
|
|
2141
2367
|
return { behavior: 'deny', input: checkedInput, message: lClass.message, policyCode: 'l_class_protection', matchedPath: lClass.matchedPath };
|
|
@@ -41,28 +41,33 @@ function isTrustedRuntimeLockDir(candidate) {
|
|
|
41
41
|
if (!isWithinTmp && !isManagedSessionSibling)
|
|
42
42
|
return false;
|
|
43
43
|
try {
|
|
44
|
+
// Windows Stats.mode does not describe ACL privacy. The managed runtime
|
|
45
|
+
// roots are protected by Windows ACL inheritance, so apply POSIX
|
|
46
|
+
// ownership and permission-bit checks only where those fields are
|
|
47
|
+
// meaningful.
|
|
48
|
+
const isWindows = process.platform === 'win32';
|
|
44
49
|
const tmpStat = fs.lstatSync(resolvedTmpRoot);
|
|
45
50
|
if (!tmpStat.isDirectory() || tmpStat.isSymbolicLink())
|
|
46
51
|
return false;
|
|
47
|
-
if (typeof process.getuid === 'function' && tmpStat.uid !== process.getuid())
|
|
52
|
+
if (!isWindows && typeof process.getuid === 'function' && tmpStat.uid !== process.getuid())
|
|
48
53
|
return false;
|
|
49
|
-
if ((tmpStat.mode & 0o077) !== 0)
|
|
54
|
+
if (!isWindows && (tmpStat.mode & 0o077) !== 0)
|
|
50
55
|
return false;
|
|
51
56
|
if (isManagedSessionSibling) {
|
|
52
57
|
const parentStat = fs.lstatSync(tmpParent);
|
|
53
58
|
if (!parentStat.isDirectory() || parentStat.isSymbolicLink())
|
|
54
59
|
return false;
|
|
55
|
-
if (typeof process.getuid === 'function' && parentStat.uid !== process.getuid())
|
|
60
|
+
if (!isWindows && typeof process.getuid === 'function' && parentStat.uid !== process.getuid())
|
|
56
61
|
return false;
|
|
57
|
-
if ((parentStat.mode & 0o077) !== 0)
|
|
62
|
+
if (!isWindows && (parentStat.mode & 0o077) !== 0)
|
|
58
63
|
return false;
|
|
59
64
|
}
|
|
60
65
|
const stat = fs.lstatSync(resolved);
|
|
61
66
|
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
62
67
|
return false;
|
|
63
|
-
if (typeof process.getuid === 'function' && stat.uid !== process.getuid())
|
|
68
|
+
if (!isWindows && typeof process.getuid === 'function' && stat.uid !== process.getuid())
|
|
64
69
|
return false;
|
|
65
|
-
return (stat.mode & 0o077) === 0;
|
|
70
|
+
return isWindows || (stat.mode & 0o077) === 0;
|
|
66
71
|
}
|
|
67
72
|
catch {
|
|
68
73
|
return false;
|
|
@@ -93,8 +98,8 @@ export function ensureManagedLockParent(lockPath) {
|
|
|
93
98
|
}
|
|
94
99
|
const stat = fs.lstatSync(current);
|
|
95
100
|
if (!stat.isDirectory() || stat.isSymbolicLink()
|
|
96
|
-
|| (typeof process.getuid === 'function' && stat.uid !== process.getuid())
|
|
97
|
-
|| (stat.mode & 0o077) !== 0) {
|
|
101
|
+
|| (process.platform !== 'win32' && typeof process.getuid === 'function' && stat.uid !== process.getuid())
|
|
102
|
+
|| (process.platform !== 'win32' && (stat.mode & 0o077) !== 0)) {
|
|
98
103
|
throw new Error(`managed runtime lock path is not a private real directory: ${current}`);
|
|
99
104
|
}
|
|
100
105
|
}
|
|
@@ -32,15 +32,20 @@ function loadSqlite() {
|
|
|
32
32
|
return sqliteModule;
|
|
33
33
|
}
|
|
34
34
|
export class CodexSessionFileAdapter {
|
|
35
|
+
codexHome;
|
|
35
36
|
baseagent = 'codex';
|
|
36
37
|
db = null;
|
|
37
38
|
dbInitialized = false;
|
|
39
|
+
rolloutPathCache = new Map();
|
|
40
|
+
constructor(codexHome = path.join(os.homedir(), '.codex')) {
|
|
41
|
+
this.codexHome = codexHome;
|
|
42
|
+
}
|
|
38
43
|
/**
|
|
39
44
|
* 动态发现最新的 state_*.sqlite 文件
|
|
40
45
|
* Codex 使用 sqlx 迁移,DB 文件名含版本号(state_5, state_6, ...)
|
|
41
46
|
*/
|
|
42
47
|
resolveStateDbPath() {
|
|
43
|
-
const codexHome =
|
|
48
|
+
const codexHome = this.codexHome;
|
|
44
49
|
if (!fs.existsSync(codexHome))
|
|
45
50
|
return null;
|
|
46
51
|
try {
|
|
@@ -94,11 +99,71 @@ export class CodexSessionFileAdapter {
|
|
|
94
99
|
// SDK 创建的 thread 不写 state_*.sqlite,但会持久化到 sessions 目录
|
|
95
100
|
return !!this.findRolloutFile(agentSessionId);
|
|
96
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* A row in state_*.sqlite is created before the first turn is necessarily
|
|
104
|
+
* recoverable. Require the rollout to contain both its own session metadata
|
|
105
|
+
* and the exact turn_context before EvolCore publishes a new binding.
|
|
106
|
+
*/
|
|
107
|
+
checkTurnPersisted(_projectPath, agentSessionId, turnId, _agentAid) {
|
|
108
|
+
const normalizedSessionId = agentSessionId.trim();
|
|
109
|
+
const normalizedTurnId = turnId.trim();
|
|
110
|
+
if (!normalizedSessionId || !normalizedTurnId)
|
|
111
|
+
return false;
|
|
112
|
+
let rolloutPath = null;
|
|
113
|
+
const db = this.getDb();
|
|
114
|
+
if (db) {
|
|
115
|
+
try {
|
|
116
|
+
const row = db.prepare('SELECT rollout_path FROM threads WHERE id = ? AND archived = 0').get(normalizedSessionId);
|
|
117
|
+
rolloutPath = this.resolveRolloutPath(row?.rollout_path, normalizedSessionId);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
logger.warn('[CodexAdapter] checkTurnPersisted DB query failed:', error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
rolloutPath ??= this.findRolloutFile(normalizedSessionId);
|
|
124
|
+
if (!rolloutPath)
|
|
125
|
+
return false;
|
|
126
|
+
try {
|
|
127
|
+
let hasMatchingSession = false;
|
|
128
|
+
let hasMatchingTurn = false;
|
|
129
|
+
const lines = fs.readFileSync(rolloutPath, 'utf-8').split('\n');
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
if (!line.trim())
|
|
132
|
+
continue;
|
|
133
|
+
try {
|
|
134
|
+
const event = JSON.parse(line);
|
|
135
|
+
if (event?.type === 'session_meta') {
|
|
136
|
+
const persistedSessionId = event.payload?.session_id ?? event.payload?.id;
|
|
137
|
+
const version = event.payload?.version ?? event.payload?.cli_version;
|
|
138
|
+
const hasVersion = version !== undefined && version !== null && String(version).trim().length > 0;
|
|
139
|
+
if (persistedSessionId === normalizedSessionId && hasVersion)
|
|
140
|
+
hasMatchingSession = true;
|
|
141
|
+
}
|
|
142
|
+
else if (event?.type === 'turn_context' && event.payload?.turn_id === normalizedTurnId) {
|
|
143
|
+
hasMatchingTurn = true;
|
|
144
|
+
}
|
|
145
|
+
if (hasMatchingSession && hasMatchingTurn)
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// The last JSONL line may still be in flight; a later poll retries it.
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
97
158
|
/**
|
|
98
159
|
* 在 ~/.codex/sessions/ 下递归查找含 agentSessionId 的 rollout JSONL 文件
|
|
99
160
|
*/
|
|
100
161
|
findRolloutFile(agentSessionId) {
|
|
101
|
-
const
|
|
162
|
+
const cached = this.rolloutPathCache.get(agentSessionId);
|
|
163
|
+
if (cached && fs.existsSync(cached))
|
|
164
|
+
return cached;
|
|
165
|
+
this.rolloutPathCache.delete(agentSessionId);
|
|
166
|
+
const sessionsDir = path.join(this.codexHome, 'sessions');
|
|
102
167
|
if (!fs.existsSync(sessionsDir))
|
|
103
168
|
return null;
|
|
104
169
|
try {
|
|
@@ -110,7 +175,9 @@ export class CodexSessionFileAdapter {
|
|
|
110
175
|
return found;
|
|
111
176
|
}
|
|
112
177
|
else if (entry.name.endsWith('.jsonl') && entry.name.includes(agentSessionId)) {
|
|
113
|
-
|
|
178
|
+
const rolloutPath = path.join(dir, entry.name);
|
|
179
|
+
this.rolloutPathCache.set(agentSessionId, rolloutPath);
|
|
180
|
+
return rolloutPath;
|
|
114
181
|
}
|
|
115
182
|
}
|
|
116
183
|
return null;
|
|
@@ -251,6 +318,7 @@ export class CodexSessionFileAdapter {
|
|
|
251
318
|
this.db = null;
|
|
252
319
|
}
|
|
253
320
|
this.dbInitialized = false;
|
|
321
|
+
this.rolloutPathCache.clear();
|
|
254
322
|
}
|
|
255
323
|
/**
|
|
256
324
|
* 从 rollout JSONL 读取第一条或最后一条 user_message
|