evolcore 0.0.21 → 0.0.22
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 +43 -0
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +3 -1
- package/dist/agents/claude-runner.js +14 -0
- package/dist/agents/codex-app-server-client.js +31 -5
- package/dist/agents/codex-runner.js +926 -121
- package/dist/aun/outbox.js +7 -0
- package/dist/channels/aun.js +209 -35
- package/dist/cli/daemon-commands.js +29 -8
- package/dist/cli/task-context.js +4 -0
- package/dist/cli/trigger-command.js +13 -4
- package/dist/config/config-field-policy.js +3 -0
- package/dist/config/config-manager.js +32 -5
- package/dist/config/contact-book-store.js +25 -3
- package/dist/core/auth/agent-delegation.js +12 -0
- package/dist/core/auth/auth-gateway.js +8 -0
- package/dist/core/auth/authorization-audit.js +66 -6
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +93 -25
- package/dist/core/command/command-handler.js +21 -0
- package/dist/core/command/menu-handler.js +9 -0
- package/dist/core/command/menu-protocol.js +1 -1
- package/dist/core/command/slash-handler.js +41 -18
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +32 -0
- package/dist/core/handoff/runtime.js +23 -3
- package/dist/core/message/im-renderer.js +7 -3
- package/dist/core/message/message-bridge.js +60 -2
- package/dist/core/message/message-log.js +33 -0
- package/dist/core/message/message-queue.js +21 -0
- package/dist/core/message/response-engine.js +172 -41
- package/dist/core/permission/ec-command-parser.js +272 -70
- package/dist/core/permission/protected-paths.js +11 -10
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +46 -5
- package/dist/core/session/session-manager.js +30 -0
- package/dist/core/session/session-renew.js +18 -1
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/index.js +64 -5
- package/dist/ipc.js +97 -17
- package/dist/paths.js +18 -0
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/feedback.js +14 -2
- package/dist/trigger/parser.js +10 -1
- package/dist/trigger/scheduler.js +20 -3
- package/dist/utils/logger.js +9 -4
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/evolcore/INDEX.md +2 -2
- package/kits/docs/evolcore/agent-create.md +146 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/group-collaboration.md +251 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/schemas/_meta.json +3 -2
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +2 -1
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +2 -2
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
import { isTrustedWindowsShellExecutable } from '../../utils/windows-shell-trust.js';
|
|
1
2
|
const HEAD_EXECUTABLES = new Set(['head', '/bin/head', '/usr/bin/head']);
|
|
2
3
|
// Codex app-server sends command executions through a fixed shell carrier.
|
|
3
4
|
// Treat the carrier as syntax only after parsing it as literal argv; never
|
|
4
5
|
// accept an arbitrary shell command string or unwrap composition here.
|
|
5
6
|
const CODEX_POSIX_SHELL_EXECUTABLES = new Set([
|
|
6
|
-
'bash', '/bin/bash', '/usr/bin/bash',
|
|
7
|
-
'sh', '/bin/sh', '/usr/bin/sh',
|
|
7
|
+
'bash', 'bash.exe', '/bin/bash', '/usr/bin/bash',
|
|
8
|
+
'sh', 'sh.exe', '/bin/sh', '/usr/bin/sh',
|
|
8
9
|
]);
|
|
9
10
|
const CODEX_POWERSHELL_EXECUTABLES = new Set([
|
|
10
11
|
'pwsh', 'pwsh.exe', 'powershell', 'powershell.exe',
|
|
11
12
|
]);
|
|
12
13
|
const CODEX_POWERSHELL_INSTALL_PATH_RE = /^[A-Za-z]:[\\/](?:Program Files[\\/]PowerShell[\\/][0-9]+(?:\.[0-9]+)*(?:-[A-Za-z0-9.-]+)?[\\/]pwsh\.exe|Windows[\\/]System32[\\/]WindowsPowerShell[\\/]v1\.0[\\/]powershell\.exe)$/i;
|
|
14
|
+
const CODEX_CUSTOM_POSIX_SHELL_NAMES = new Set(['bash', 'bash.exe', 'sh', 'sh.exe']);
|
|
15
|
+
const CODEX_CUSTOM_POWERSHELL_NAMES = new Set(['pwsh.exe', 'powershell.exe']);
|
|
16
|
+
const CODEX_CUSTOM_CMD_NAMES = new Set(['cmd.exe']);
|
|
13
17
|
const CODEX_CMD_EXECUTABLES = new Set(['cmd', 'cmd.exe']);
|
|
14
18
|
const CODEX_CMD_INSTALL_PATH_RE = /^[A-Za-z]:[\\/](?:Windows[\\/](?:System32|SysWOW64|Sysnative)[\\/]cmd\.exe)$/i;
|
|
15
19
|
// These switches affect only cmd startup/output formatting. `/c` is handled
|
|
@@ -17,6 +21,12 @@ const CODEX_CMD_INSTALL_PATH_RE = /^[A-Za-z]:[\\/](?:Windows[\\/](?:System32|Sys
|
|
|
17
21
|
// `/e:*`, and `/f:*` can change process lifetime or command semantics and are
|
|
18
22
|
// intentionally not treated as a trusted Codex carrier.
|
|
19
23
|
const CODEX_CMD_INERT_SWITCHES = new Set(['/d', '/s', '/q', '/a', '/u']);
|
|
24
|
+
const EVOLCORE_EXECUTABLE_ALIASES = new Set(['ec', 'ec.cmd', 'ec.exe']);
|
|
25
|
+
const EVOLCORE_NODE_EXECUTABLES = new Set(['node', 'node.exe']);
|
|
26
|
+
// Development/install entrypoints that are part of this repository's CLI.
|
|
27
|
+
// Only these exact relative paths are canonicalized; arbitrary Node scripts
|
|
28
|
+
// must never become an EC command merely because their filename is similar.
|
|
29
|
+
const EVOLCORE_NODE_WRAPPER_PATHS = new Set(['bin/ec.js', 'dist/cli/index.js']);
|
|
20
30
|
const SHELL_UNQUOTED_META = new Set([
|
|
21
31
|
';', '&', '|', '`', '<', '>', '$', '(', ')', '*', '?', '[', ']', '{', '}', '#',
|
|
22
32
|
]);
|
|
@@ -68,8 +78,7 @@ function hasEcCommandPrefix(command) {
|
|
|
68
78
|
}
|
|
69
79
|
if (boundary && /\s/.test(char))
|
|
70
80
|
continue;
|
|
71
|
-
if (boundary && command.slice(index
|
|
72
|
-
&& (index + 2 === command.length || /\s|[;&|`()<>]/.test(command[index + 2])))
|
|
81
|
+
if (boundary && /^ec(?:\.cmd|\.exe)?(?=$|\s|[;&|`()<>])/i.test(command.slice(index)))
|
|
73
82
|
return true;
|
|
74
83
|
boundary = false;
|
|
75
84
|
}
|
|
@@ -78,6 +87,45 @@ function hasEcCommandPrefix(command) {
|
|
|
78
87
|
function trimShellSpacing(value) {
|
|
79
88
|
return value.replace(/^[ \t]+|[ \t]+$/g, '');
|
|
80
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Accept an explicitly materialized absolute shell path, including Windows
|
|
92
|
+
* Store/App Execution Alias paths such as `...\\WindowsApps\\pwsh.exe`.
|
|
93
|
+
* Relative names remain restricted to the fixed executable allowlists above.
|
|
94
|
+
*/
|
|
95
|
+
function isAbsoluteCustomShellPath(value, executableNames) {
|
|
96
|
+
if (!value || /[\0\r\n"'<>|?*]/.test(value))
|
|
97
|
+
return false;
|
|
98
|
+
const normalized = value.replaceAll('\\', '/');
|
|
99
|
+
if (!/^[A-Za-z]:\//.test(normalized) && !normalized.startsWith('//'))
|
|
100
|
+
return false;
|
|
101
|
+
const basename = normalized.slice(normalized.lastIndexOf('/') + 1).toLowerCase();
|
|
102
|
+
return executableNames.has(basename);
|
|
103
|
+
}
|
|
104
|
+
function isNodeExecutable(value) {
|
|
105
|
+
if (!value)
|
|
106
|
+
return false;
|
|
107
|
+
const normalized = value.toLowerCase();
|
|
108
|
+
return EVOLCORE_NODE_EXECUTABLES.has(normalized)
|
|
109
|
+
|| isAbsoluteCustomShellPath(value, EVOLCORE_NODE_EXECUTABLES);
|
|
110
|
+
}
|
|
111
|
+
function normalizeNodeWrapperPath(value) {
|
|
112
|
+
const normalized = value.replaceAll('\\', '/').replace(/^\.\//, '').toLowerCase();
|
|
113
|
+
return EVOLCORE_NODE_WRAPPER_PATHS.has(normalized) ? normalized : undefined;
|
|
114
|
+
}
|
|
115
|
+
function isCodexPosixShellExecutable(value) {
|
|
116
|
+
if (!value)
|
|
117
|
+
return false;
|
|
118
|
+
return CODEX_POSIX_SHELL_EXECUTABLES.has(value.toLowerCase())
|
|
119
|
+
|| isAbsoluteCustomShellPath(value, CODEX_CUSTOM_POSIX_SHELL_NAMES);
|
|
120
|
+
}
|
|
121
|
+
function isCodexPowerShellExecutable(value) {
|
|
122
|
+
if (!value)
|
|
123
|
+
return false;
|
|
124
|
+
const normalized = value.toLowerCase();
|
|
125
|
+
return CODEX_POWERSHELL_EXECUTABLES.has(normalized)
|
|
126
|
+
|| CODEX_POWERSHELL_INSTALL_PATH_RE.test(value)
|
|
127
|
+
|| isAbsoluteCustomShellPath(value, CODEX_CUSTOM_POWERSHELL_NAMES);
|
|
128
|
+
}
|
|
81
129
|
const MANAGED_TMPDIR_TOKEN = '__EC_MANAGED_TMPDIR__';
|
|
82
130
|
/**
|
|
83
131
|
* Preserve the one environment reference allowed in managed EC paths.
|
|
@@ -154,7 +202,155 @@ function hasCarrierEcCommandPrefix(command, dialect) {
|
|
|
154
202
|
const inner = input.length >= 2 && input.startsWith('"') && input.endsWith('"')
|
|
155
203
|
? input.slice(1, -1)
|
|
156
204
|
: input;
|
|
157
|
-
return /^\s*ec(?:\s|$)/i.test(inner);
|
|
205
|
+
return /^\s*ec(?:\.cmd|\.exe)?(?:\s|$)/i.test(inner);
|
|
206
|
+
}
|
|
207
|
+
/** Normalize the Windows npm shims to the canonical EvolCore CLI name. */
|
|
208
|
+
export function normalizeEvolcoreCommandArgv(argv) {
|
|
209
|
+
if (argv.length === 0)
|
|
210
|
+
return [];
|
|
211
|
+
const executable = argv[0];
|
|
212
|
+
if (typeof executable === 'string' && EVOLCORE_EXECUTABLE_ALIASES.has(executable.toLowerCase())) {
|
|
213
|
+
return ['ec', ...argv.slice(1)];
|
|
214
|
+
}
|
|
215
|
+
if (typeof executable === 'string' && isNodeExecutable(executable) && argv.length >= 2
|
|
216
|
+
&& typeof argv[1] === 'string' && normalizeNodeWrapperPath(argv[1])) {
|
|
217
|
+
return ['ec', ...argv.slice(2)];
|
|
218
|
+
}
|
|
219
|
+
return [...argv];
|
|
220
|
+
}
|
|
221
|
+
/** Detect the historical PowerShell pattern that overwrote the thread carrier. */
|
|
222
|
+
export function hasPowerShellDelegationAssignment(command) {
|
|
223
|
+
return /^\s*\$env\s*:\s*EVOLCORE_DELEGATION_TOKEN\b/i.test(command)
|
|
224
|
+
&& /\bec(?:\.cmd|\.exe)?(?:\s|$)/i.test(command);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Windows Codex may materialize one environment variable assignment directly
|
|
228
|
+
* in the PowerShell command body instead of inheriting it from the process
|
|
229
|
+
* environment. Only the EvolCore delegation variable is accepted, and only
|
|
230
|
+
* when followed by one command on the same carrier. The value is validated for
|
|
231
|
+
* shape but never trusted for authorization; the runner still arms the stable
|
|
232
|
+
* in-memory thread carrier.
|
|
233
|
+
*/
|
|
234
|
+
function unwrapPowerShellDelegationPrefix(command) {
|
|
235
|
+
const input = command.trim();
|
|
236
|
+
if (!/^\$env\s*:\s*EVOLCORE_DELEGATION_TOKEN\b/i.test(input)) {
|
|
237
|
+
return { kind: 'none', command };
|
|
238
|
+
}
|
|
239
|
+
let index = 0;
|
|
240
|
+
const skipSpace = () => {
|
|
241
|
+
while (index < input.length && (input[index] === ' ' || input[index] === '\t'))
|
|
242
|
+
index++;
|
|
243
|
+
};
|
|
244
|
+
const consume = (value) => {
|
|
245
|
+
if (input.slice(index, index + value.length).toLowerCase() !== value.toLowerCase())
|
|
246
|
+
return false;
|
|
247
|
+
index += value.length;
|
|
248
|
+
return true;
|
|
249
|
+
};
|
|
250
|
+
skipSpace();
|
|
251
|
+
if (!consume('$env') || input[index] !== ':')
|
|
252
|
+
return { kind: 'invalid', command };
|
|
253
|
+
index++;
|
|
254
|
+
skipSpace();
|
|
255
|
+
if (!consume('EVOLCORE_DELEGATION_TOKEN'))
|
|
256
|
+
return { kind: 'invalid', command };
|
|
257
|
+
skipSpace();
|
|
258
|
+
if (input[index] !== '=')
|
|
259
|
+
return { kind: 'invalid', command };
|
|
260
|
+
index++;
|
|
261
|
+
skipSpace();
|
|
262
|
+
const quote = input[index];
|
|
263
|
+
if (quote !== "'" && quote !== '"')
|
|
264
|
+
return { kind: 'invalid', command };
|
|
265
|
+
const valueStart = index++;
|
|
266
|
+
let value = '';
|
|
267
|
+
let closed = false;
|
|
268
|
+
// When the complete -Command body is itself single-quoted, command-line
|
|
269
|
+
// serialization can leave the assignment value in a doubled `''token''`
|
|
270
|
+
// form. Its token alphabet cannot contain quotes, so this exact shape is
|
|
271
|
+
// unambiguous and can be decoded without executing PowerShell grammar.
|
|
272
|
+
if (quote === "'" && input[valueStart + 1] === "'") {
|
|
273
|
+
const contentStart = valueStart + 2;
|
|
274
|
+
const end = input.indexOf("''", contentStart);
|
|
275
|
+
if (end < contentStart)
|
|
276
|
+
return { kind: 'invalid', command };
|
|
277
|
+
value = input.slice(contentStart, end);
|
|
278
|
+
index = end + 2;
|
|
279
|
+
closed = true;
|
|
280
|
+
}
|
|
281
|
+
while (index < input.length) {
|
|
282
|
+
if (closed)
|
|
283
|
+
break;
|
|
284
|
+
const char = input[index];
|
|
285
|
+
if (char === quote) {
|
|
286
|
+
// PowerShell single-quoted strings escape a quote as `''`. For the
|
|
287
|
+
// delegation token alphabet this is not needed, but accepting it lets us
|
|
288
|
+
// decode the doubled-quote form emitted by powershell.exe argv quoting.
|
|
289
|
+
if (quote === "'" && input[index + 1] === "'") {
|
|
290
|
+
value += "'";
|
|
291
|
+
index += 2;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
index++;
|
|
295
|
+
closed = true;
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
if (quote === '"' && char === '`')
|
|
299
|
+
return { kind: 'invalid', command };
|
|
300
|
+
if (char === '\r' || char === '\n')
|
|
301
|
+
return { kind: 'invalid', command };
|
|
302
|
+
value += char;
|
|
303
|
+
index++;
|
|
304
|
+
}
|
|
305
|
+
if (!closed || index <= valueStart + 1 || !/^[A-Za-z0-9_-]{32,128}$/.test(value)) {
|
|
306
|
+
return { kind: 'invalid', command };
|
|
307
|
+
}
|
|
308
|
+
if (input[index] !== ';')
|
|
309
|
+
return { kind: 'invalid', command };
|
|
310
|
+
index++;
|
|
311
|
+
const remainder = input.slice(index).trim();
|
|
312
|
+
if (!remainder)
|
|
313
|
+
return { kind: 'invalid', command };
|
|
314
|
+
return { kind: 'valid', command: remainder, token: value };
|
|
315
|
+
}
|
|
316
|
+
export function parseCodexCarrierCommand(carrier, options = {}) {
|
|
317
|
+
if (options.verifyShellExecutable) {
|
|
318
|
+
if (!carrier.executable) {
|
|
319
|
+
return parseFailure('untrusted-shell', carrier.command, 0, 0, carrier.dialect);
|
|
320
|
+
}
|
|
321
|
+
const kind = carrier.dialect === 'posix' ? 'bash' : carrier.dialect;
|
|
322
|
+
if (!isTrustedWindowsShellExecutable(carrier.executable, kind)) {
|
|
323
|
+
return parseFailure('untrusted-shell', carrier.command, 0, 0, carrier.dialect);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const unwrapped = carrier.dialect === 'powershell'
|
|
327
|
+
? unwrapPowerShellDelegationPrefix(carrier.command)
|
|
328
|
+
: { kind: 'none', command: carrier.command };
|
|
329
|
+
// Windows PowerShell may materialize the thread's shell environment as a
|
|
330
|
+
// single `$env:EVOLCORE_DELEGATION_TOKEN = '...'` prefix. Keep the token as
|
|
331
|
+
// metadata for the approval layer, which must compare it with the runner's
|
|
332
|
+
// in-memory carrier before arming delegation. Malformed or unrelated
|
|
333
|
+
// assignments remain syntax failures and can never be authorized.
|
|
334
|
+
if (unwrapped.kind === 'invalid'
|
|
335
|
+
|| (carrier.dialect === 'powershell'
|
|
336
|
+
&& hasPowerShellDelegationAssignment(carrier.command)
|
|
337
|
+
&& unwrapped.kind === 'none')
|
|
338
|
+
|| (unwrapped.kind === 'valid'
|
|
339
|
+
&& options.expectedDelegationToken !== undefined
|
|
340
|
+
&& options.expectedDelegationToken !== unwrapped.token)) {
|
|
341
|
+
return parseFailure('shell-composition', carrier.command, 0, 0, carrier.dialect);
|
|
342
|
+
}
|
|
343
|
+
const parsed = parseLiteralShellCommand(unwrapped.command, {
|
|
344
|
+
...options,
|
|
345
|
+
dialect: carrier.dialect,
|
|
346
|
+
});
|
|
347
|
+
return parsed.ok
|
|
348
|
+
? {
|
|
349
|
+
...parsed,
|
|
350
|
+
argv: normalizeEvolcoreCommandArgv(parsed.argv),
|
|
351
|
+
...(unwrapped.kind === 'valid' ? { delegationToken: unwrapped.token } : {}),
|
|
352
|
+
}
|
|
353
|
+
: parsed;
|
|
158
354
|
}
|
|
159
355
|
function parseFailure(issue, input, offset, tokenIndex, dialect) {
|
|
160
356
|
return {
|
|
@@ -355,7 +551,9 @@ function isCodexCmdExecutable(value) {
|
|
|
355
551
|
if (!value)
|
|
356
552
|
return false;
|
|
357
553
|
const normalized = value.toLowerCase();
|
|
358
|
-
return CODEX_CMD_EXECUTABLES.has(normalized)
|
|
554
|
+
return CODEX_CMD_EXECUTABLES.has(normalized)
|
|
555
|
+
|| CODEX_CMD_INSTALL_PATH_RE.test(value)
|
|
556
|
+
|| isAbsoluteCustomShellPath(value, CODEX_CUSTOM_CMD_NAMES);
|
|
359
557
|
}
|
|
360
558
|
function resolveCodexCmdCarrierArgv(argv) {
|
|
361
559
|
if (argv.length < 3 || !isCodexCmdExecutable(argv[0]))
|
|
@@ -377,7 +575,7 @@ function resolveCodexCmdCarrierArgv(argv) {
|
|
|
377
575
|
}
|
|
378
576
|
const command = argv.at(-1);
|
|
379
577
|
return typeof command === 'string' && command.trim()
|
|
380
|
-
? { command, dialect: 'cmd' }
|
|
578
|
+
? { command, dialect: 'cmd', executable: argv[0] }
|
|
381
579
|
: undefined;
|
|
382
580
|
}
|
|
383
581
|
/** Resolve a Windows cmd carrier when the app-server provides only a string. */
|
|
@@ -402,10 +600,7 @@ function resolveCodexPowerShellCarrierString(command) {
|
|
|
402
600
|
if (!match)
|
|
403
601
|
return undefined;
|
|
404
602
|
const executable = match[1] ?? match[2];
|
|
405
|
-
|
|
406
|
-
const isPowerShell = CODEX_POWERSHELL_EXECUTABLES.has(normalized)
|
|
407
|
-
|| CODEX_POWERSHELL_INSTALL_PATH_RE.test(executable);
|
|
408
|
-
if (!isPowerShell)
|
|
603
|
+
if (!isCodexPowerShellExecutable(executable))
|
|
409
604
|
return undefined;
|
|
410
605
|
// A string-form carrier has one additional command-line quoting layer. The
|
|
411
606
|
// command argument is commonly serialized as `"ec ..."`; remove exactly
|
|
@@ -416,11 +611,31 @@ function resolveCodexPowerShellCarrierString(command) {
|
|
|
416
611
|
|| (nested.startsWith("'") && nested.endsWith("'")))) {
|
|
417
612
|
nested = nested.slice(1, -1);
|
|
418
613
|
}
|
|
419
|
-
return nested ? { command: nested, dialect: 'powershell' } : undefined;
|
|
614
|
+
return nested ? { command: nested, dialect: 'powershell', executable } : undefined;
|
|
615
|
+
}
|
|
616
|
+
/** Resolve a POSIX shell carrier when app-server provides only a string. */
|
|
617
|
+
function resolveCodexPosixShellCarrierString(command) {
|
|
618
|
+
const input = trimShellSpacing(command);
|
|
619
|
+
const match = input.match(/^(?:"([^"]+)"|(\S+))\s+(-lc|-c)\s+(.+)$/);
|
|
620
|
+
if (!match)
|
|
621
|
+
return undefined;
|
|
622
|
+
const executable = match[1] ?? match[2];
|
|
623
|
+
if (!isCodexPosixShellExecutable(executable))
|
|
624
|
+
return undefined;
|
|
625
|
+
// A string-form carrier has one additional command-line quoting layer. Strip
|
|
626
|
+
// only the outer pair; quotes belonging to the nested command remain intact.
|
|
627
|
+
let nested = match[4].trim();
|
|
628
|
+
if (nested.length >= 2
|
|
629
|
+
&& ((nested.startsWith('"') && nested.endsWith('"'))
|
|
630
|
+
|| (nested.startsWith("'") && nested.endsWith("'")))) {
|
|
631
|
+
nested = nested.slice(1, -1);
|
|
632
|
+
}
|
|
633
|
+
return nested ? { command: nested, dialect: 'posix', executable } : undefined;
|
|
420
634
|
}
|
|
421
635
|
export function resolveCodexShellCarrierString(command) {
|
|
422
636
|
return resolveCodexCmdCarrierString(command)
|
|
423
|
-
?? resolveCodexPowerShellCarrierString(command)
|
|
637
|
+
?? resolveCodexPowerShellCarrierString(command)
|
|
638
|
+
?? resolveCodexPosixShellCarrierString(command);
|
|
424
639
|
}
|
|
425
640
|
/**
|
|
426
641
|
* Extract a trusted cmd executable's command body without trusting its startup
|
|
@@ -462,7 +677,7 @@ function looksLikeEcCommandBody(command) {
|
|
|
462
677
|
return false;
|
|
463
678
|
const input = command.trim();
|
|
464
679
|
const body = (input.startsWith('"') ? input.slice(1) : input).trimStart();
|
|
465
|
-
return /^ec(?:\s|$)/i.test(body);
|
|
680
|
+
return /^ec(?:\.cmd|\.exe)?(?:\s|$)/i.test(body);
|
|
466
681
|
}
|
|
467
682
|
/** True only when a trusted cmd executable is carrying an EC command body. */
|
|
468
683
|
export function hasCodexCmdCarrierEcIntent(input) {
|
|
@@ -627,15 +842,14 @@ export function parseLiteralShellArgv(command, options = {}) {
|
|
|
627
842
|
return result.ok ? result.argv : null;
|
|
628
843
|
}
|
|
629
844
|
export function resolveCodexShellCarrierArgv(argv) {
|
|
630
|
-
if (argv.length === 3
|
|
631
|
-
|
|
845
|
+
if (argv.length === 3
|
|
846
|
+
&& (argv[1] === '-lc' || argv[1] === '-c')
|
|
847
|
+
&& isCodexPosixShellExecutable(argv[0])) {
|
|
848
|
+
return { command: argv[2], dialect: 'posix', executable: argv[0] };
|
|
632
849
|
}
|
|
633
850
|
if (argv.length === 3) {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|| CODEX_POWERSHELL_INSTALL_PATH_RE.test(argv[0]);
|
|
637
|
-
if (isPowerShell && argv[1].toLowerCase() === '-command') {
|
|
638
|
-
return { command: argv[2], dialect: 'powershell' };
|
|
851
|
+
if (isCodexPowerShellExecutable(argv[0]) && argv[1].toLowerCase() === '-command') {
|
|
852
|
+
return { command: argv[2], dialect: 'powershell', executable: argv[0] };
|
|
639
853
|
}
|
|
640
854
|
}
|
|
641
855
|
// Node/cross-spawn and Codex use cmd.exe with a small set of startup flags.
|
|
@@ -656,27 +870,26 @@ export function parseCodexShellCommandArgv(command, options = {}) {
|
|
|
656
870
|
if (outer) {
|
|
657
871
|
const carrier = resolveCodexShellCarrierArgv(outer);
|
|
658
872
|
if (carrier !== undefined) {
|
|
659
|
-
|
|
873
|
+
const parsed = parseCodexCarrierCommand(carrier, options);
|
|
874
|
+
return parsed.ok ? parsed.argv : null;
|
|
660
875
|
}
|
|
661
876
|
const stringCarrier = resolveCodexShellCarrierString(command);
|
|
662
877
|
if (stringCarrier) {
|
|
663
|
-
|
|
878
|
+
const parsed = parseCodexCarrierCommand(stringCarrier, options);
|
|
879
|
+
return parsed.ok ? parsed.argv : null;
|
|
664
880
|
}
|
|
665
881
|
if (parseCodexCmdCarrierStringFailure(command, options))
|
|
666
882
|
return null;
|
|
667
|
-
return outer;
|
|
883
|
+
return normalizeEvolcoreCommandArgv(outer);
|
|
668
884
|
}
|
|
669
885
|
const stringCarrier = resolveCodexShellCarrierString(command);
|
|
670
886
|
if (!stringCarrier)
|
|
671
887
|
return null;
|
|
672
|
-
const parsed =
|
|
673
|
-
|
|
674
|
-
dialect: stringCarrier.dialect,
|
|
675
|
-
});
|
|
676
|
-
return parsed.ok ? parsed.argv : null;
|
|
888
|
+
const parsed = parseCodexCarrierCommand(stringCarrier, options);
|
|
889
|
+
return parsed.ok ? normalizeEvolcoreCommandArgv(parsed.argv) : null;
|
|
677
890
|
}
|
|
678
891
|
/** Parse a Codex tool input while preserving safe syntax failure metadata. */
|
|
679
|
-
export function parseCodexToolCommand(input) {
|
|
892
|
+
export function parseCodexToolCommand(input, options = {}) {
|
|
680
893
|
if (Array.isArray(input.commandArgv)) {
|
|
681
894
|
if (!input.commandArgv.every(value => typeof value === 'string')) {
|
|
682
895
|
return parseFailure('invalid-control-char', '', 0, 0, 'posix');
|
|
@@ -687,46 +900,36 @@ export function parseCodexToolCommand(input) {
|
|
|
687
900
|
return parseFailure('invalid-control-char', '', 0, nulIndex, 'posix');
|
|
688
901
|
const carrier = resolveCodexShellCarrierArgv(outer);
|
|
689
902
|
if (carrier === undefined) {
|
|
690
|
-
return parseCodexCmdCarrierArgvFailure(outer)
|
|
903
|
+
return parseCodexCmdCarrierArgvFailure(outer)
|
|
904
|
+
?? { ok: true, argv: normalizeEvolcoreCommandArgv(outer) };
|
|
691
905
|
}
|
|
692
|
-
return
|
|
693
|
-
allowManagedTmpDir: true,
|
|
694
|
-
dialect: carrier.dialect,
|
|
695
|
-
});
|
|
906
|
+
return parseCodexCarrierCommand(carrier, { allowManagedTmpDir: true, ...options });
|
|
696
907
|
}
|
|
697
908
|
const command = typeof input.command === 'string' ? input.command : '';
|
|
698
909
|
const outer = parseLiteralShellCommand(command, { allowManagedTmpDir: true });
|
|
699
910
|
if (outer.ok) {
|
|
700
911
|
const carrier = resolveCodexShellCarrierArgv(outer.argv);
|
|
701
912
|
if (carrier !== undefined) {
|
|
702
|
-
return
|
|
703
|
-
allowManagedTmpDir: true,
|
|
704
|
-
dialect: carrier.dialect,
|
|
705
|
-
});
|
|
913
|
+
return parseCodexCarrierCommand(carrier, { allowManagedTmpDir: true, ...options });
|
|
706
914
|
}
|
|
707
915
|
const stringCarrier = resolveCodexShellCarrierString(command);
|
|
708
916
|
return stringCarrier
|
|
709
|
-
?
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
})
|
|
713
|
-
: parseCodexCmdCarrierStringFailure(command, { allowManagedTmpDir: true }) ?? outer;
|
|
917
|
+
? parseCodexCarrierCommand(stringCarrier, { allowManagedTmpDir: true, ...options })
|
|
918
|
+
: parseCodexCmdCarrierStringFailure(command, { allowManagedTmpDir: true })
|
|
919
|
+
?? { ...outer, argv: normalizeEvolcoreCommandArgv(outer.argv) };
|
|
714
920
|
}
|
|
715
921
|
const stringCarrier = resolveCodexShellCarrierString(command);
|
|
716
922
|
return stringCarrier
|
|
717
|
-
?
|
|
718
|
-
allowManagedTmpDir: true,
|
|
719
|
-
dialect: stringCarrier.dialect,
|
|
720
|
-
})
|
|
923
|
+
? parseCodexCarrierCommand(stringCarrier, { allowManagedTmpDir: true, ...options })
|
|
721
924
|
: parseCodexCmdCarrierStringFailure(command, { allowManagedTmpDir: true }) ?? outer;
|
|
722
925
|
}
|
|
723
926
|
/**
|
|
724
927
|
* Resolve a Codex command input while preserving the raw `commandArgv` form
|
|
725
928
|
* supplied by app-server. Joining that argv with spaces loses the quoting of
|
|
726
|
-
* the inner `-lc` command and was the source of a false proactive denial.
|
|
929
|
+
* the inner `-c`/`-lc` command and was the source of a false proactive denial.
|
|
727
930
|
*/
|
|
728
|
-
export function parseCodexToolCommandArgv(input) {
|
|
729
|
-
const result = parseCodexToolCommand(input);
|
|
931
|
+
export function parseCodexToolCommandArgv(input, options = {}) {
|
|
932
|
+
const result = parseCodexToolCommand(input, options);
|
|
730
933
|
return result.ok ? result.argv : null;
|
|
731
934
|
}
|
|
732
935
|
function isSimpleExecStringLiteral(value) {
|
|
@@ -1214,12 +1417,15 @@ export function parseCodexExecToolCommand(input) {
|
|
|
1214
1417
|
return decoded;
|
|
1215
1418
|
}
|
|
1216
1419
|
function parseProactiveToolCommandArgv(toolName, input) {
|
|
1420
|
+
const options = {
|
|
1421
|
+
verifyShellExecutable: process.platform === 'win32',
|
|
1422
|
+
};
|
|
1217
1423
|
if (toolName === 'Bash' || toolName === 'Shell')
|
|
1218
|
-
return parseCodexToolCommandArgv(input);
|
|
1424
|
+
return parseCodexToolCommandArgv(input, options);
|
|
1219
1425
|
if (toolName !== 'exec')
|
|
1220
1426
|
return null;
|
|
1221
1427
|
const command = parseCodexExecToolCommand(input);
|
|
1222
|
-
return command ? parseCodexShellCommandArgv(command, { allowManagedTmpDir: true }) : null;
|
|
1428
|
+
return command ? parseCodexShellCommandArgv(command, { allowManagedTmpDir: true, ...options }) : null;
|
|
1223
1429
|
}
|
|
1224
1430
|
function scanBoundedOutputOperators(command) {
|
|
1225
1431
|
const operators = [];
|
|
@@ -1410,29 +1616,25 @@ export function classifyEvolcoreShellCommand(command, options = {}) {
|
|
|
1410
1616
|
const parseOptions = { allowManagedTmpDir: true, ...options };
|
|
1411
1617
|
const literal = parseLiteralShellCommand(command, parseOptions);
|
|
1412
1618
|
if (literal.ok) {
|
|
1413
|
-
if (literal.argv[0] === 'ec')
|
|
1619
|
+
if (normalizeEvolcoreCommandArgv(literal.argv)[0] === 'ec')
|
|
1414
1620
|
return { kind: 'literal' };
|
|
1415
1621
|
const carrier = resolveCodexShellCarrierArgv(literal.argv);
|
|
1416
1622
|
if (carrier !== undefined) {
|
|
1417
|
-
const nested =
|
|
1418
|
-
...parseOptions,
|
|
1419
|
-
dialect: carrier.dialect,
|
|
1420
|
-
});
|
|
1623
|
+
const nested = parseCodexCarrierCommand(carrier, parseOptions);
|
|
1421
1624
|
if (nested.ok && nested.argv[0] === 'ec')
|
|
1422
1625
|
return { kind: 'literal' };
|
|
1423
|
-
if (!nested.ok && hasCarrierEcCommandPrefix(carrier.command, carrier.dialect)
|
|
1626
|
+
if (!nested.ok && (hasCarrierEcCommandPrefix(carrier.command, carrier.dialect)
|
|
1627
|
+
|| (carrier.dialect === 'powershell' && hasPowerShellDelegationAssignment(carrier.command)))) {
|
|
1424
1628
|
return { kind: 'composite', issue: nested.issue };
|
|
1425
1629
|
}
|
|
1426
1630
|
}
|
|
1427
1631
|
const stringCarrier = resolveCodexShellCarrierString(command);
|
|
1428
1632
|
if (stringCarrier) {
|
|
1429
|
-
const nested =
|
|
1430
|
-
...parseOptions,
|
|
1431
|
-
dialect: stringCarrier.dialect,
|
|
1432
|
-
});
|
|
1633
|
+
const nested = parseCodexCarrierCommand(stringCarrier, parseOptions);
|
|
1433
1634
|
if (nested.ok && nested.argv[0] === 'ec')
|
|
1434
1635
|
return { kind: 'literal' };
|
|
1435
|
-
if (!nested.ok && hasCarrierEcCommandPrefix(stringCarrier.command, stringCarrier.dialect)
|
|
1636
|
+
if (!nested.ok && (hasCarrierEcCommandPrefix(stringCarrier.command, stringCarrier.dialect)
|
|
1637
|
+
|| (stringCarrier.dialect === 'powershell' && hasPowerShellDelegationAssignment(stringCarrier.command)))) {
|
|
1436
1638
|
return { kind: 'composite', issue: nested.issue };
|
|
1437
1639
|
}
|
|
1438
1640
|
}
|
|
@@ -1450,13 +1652,11 @@ export function classifyEvolcoreShellCommand(command, options = {}) {
|
|
|
1450
1652
|
}
|
|
1451
1653
|
const stringCarrier = resolveCodexShellCarrierString(command);
|
|
1452
1654
|
if (stringCarrier) {
|
|
1453
|
-
const nested =
|
|
1454
|
-
...parseOptions,
|
|
1455
|
-
dialect: stringCarrier.dialect,
|
|
1456
|
-
});
|
|
1655
|
+
const nested = parseCodexCarrierCommand(stringCarrier, parseOptions);
|
|
1457
1656
|
if (nested.ok && nested.argv[0] === 'ec')
|
|
1458
1657
|
return { kind: 'literal' };
|
|
1459
|
-
if (!nested.ok && hasCarrierEcCommandPrefix(stringCarrier.command, stringCarrier.dialect)
|
|
1658
|
+
if (!nested.ok && (hasCarrierEcCommandPrefix(stringCarrier.command, stringCarrier.dialect)
|
|
1659
|
+
|| (stringCarrier.dialect === 'powershell' && hasPowerShellDelegationAssignment(stringCarrier.command)))) {
|
|
1460
1660
|
return { kind: 'composite', issue: nested.issue };
|
|
1461
1661
|
}
|
|
1462
1662
|
return { kind: 'none' };
|
|
@@ -1687,7 +1887,9 @@ export function parseEvolcoreSendCommand(command) {
|
|
|
1687
1887
|
export function isEvolcoreSendCommandForSession(toolName, input, channelId) {
|
|
1688
1888
|
if (toolName !== 'Bash')
|
|
1689
1889
|
return false;
|
|
1690
|
-
const argv = parseCodexToolCommandArgv(input
|
|
1890
|
+
const argv = parseCodexToolCommandArgv(input, {
|
|
1891
|
+
verifyShellExecutable: process.platform === 'win32',
|
|
1892
|
+
});
|
|
1691
1893
|
const parsed = argv ? parseEvolcoreSendArgv(argv) : null;
|
|
1692
1894
|
if (!parsed)
|
|
1693
1895
|
return false;
|
|
@@ -268,21 +268,21 @@ export function getHClassSandboxPatterns(root = resolveRoot()) {
|
|
|
268
268
|
'.lock',
|
|
269
269
|
'daemon.pid',
|
|
270
270
|
path.join('data', 'causation-aun.json*'),
|
|
271
|
-
path.join('data', 'triggers'
|
|
272
|
-
path.join('data', 'sessions'
|
|
271
|
+
path.join('data', 'triggers') + '/**',
|
|
272
|
+
path.join('data', 'sessions') + '/**',
|
|
273
273
|
path.join('data', 'contact-book-audit.jsonl'),
|
|
274
274
|
path.join('data', 'feishu-seen-*.jsonl'),
|
|
275
275
|
path.join('data', '*-greeted-*.jsonl'),
|
|
276
276
|
path.join('data', 'message-queue.json*'),
|
|
277
277
|
path.join('data', 'daemon', 'message-queue.json*'),
|
|
278
|
-
path.join('data', 'daemon', 'control'
|
|
279
|
-
path.join('data', 'daemon', 'migrations'
|
|
280
|
-
path.join('data', 'channel-state'
|
|
278
|
+
path.join('data', 'daemon', 'control') + '/**',
|
|
279
|
+
path.join('data', 'daemon', 'migrations') + '/**',
|
|
280
|
+
path.join('data', 'channel-state') + '/**',
|
|
281
281
|
path.join('data', 'instance', 'control.token'),
|
|
282
282
|
path.join('data', 'outbox'),
|
|
283
|
-
path.join('data', 'outbox'
|
|
283
|
+
path.join('data', 'outbox') + '/**',
|
|
284
284
|
path.join('data', 'handoff'),
|
|
285
|
-
path.join('data', 'handoff'
|
|
285
|
+
path.join('data', 'handoff') + '/**',
|
|
286
286
|
path.join('agents', '*', 'sessions', '**'),
|
|
287
287
|
path.join('agents', '*', 'data', 'channels', '**'),
|
|
288
288
|
path.join('agents', '*', 'data', 'handoff', '**'),
|
|
@@ -296,8 +296,8 @@ export function getLClassWritePatterns(root = resolveRoot()) {
|
|
|
296
296
|
const absoluteRoot = resolveThroughExistingAncestor(root);
|
|
297
297
|
return [
|
|
298
298
|
path.join(absoluteRoot, 'agents', '*', 'triggers'),
|
|
299
|
-
path.join(absoluteRoot, 'agents', '*', 'triggers'
|
|
300
|
-
path.join(absoluteRoot, 'agents', '*', 'roles'
|
|
299
|
+
path.join(absoluteRoot, 'agents', '*', 'triggers') + '/**',
|
|
300
|
+
path.join(absoluteRoot, 'agents', '*', 'roles') + '/**',
|
|
301
301
|
];
|
|
302
302
|
}
|
|
303
303
|
const CLAUDE_PROTECTED_TARGET_LIMIT = 8192;
|
|
@@ -451,7 +451,8 @@ export function buildCodexProtectedFilesystemRules(root = resolveRoot(), options
|
|
|
451
451
|
lClassRules[target.path] = lClassAccess;
|
|
452
452
|
}
|
|
453
453
|
if (expandLClassReadDeny || !options.denyLClassRead) {
|
|
454
|
-
|
|
454
|
+
// Use '/**' suffix instead of path.join to satisfy Codex's glob requirement
|
|
455
|
+
lClassRules[target.path + '/**'] = lClassAccess;
|
|
455
456
|
}
|
|
456
457
|
}
|
|
457
458
|
else {
|
|
@@ -14,6 +14,18 @@ export function classifyToolErrorCode(input) {
|
|
|
14
14
|
const explicit = normalizeToolErrorCode(input.errorCode);
|
|
15
15
|
if (explicit)
|
|
16
16
|
return explicit;
|
|
17
|
+
if (input.decisionSource === 'policy')
|
|
18
|
+
return 'POLICY_DENIED';
|
|
19
|
+
if (input.decisionSource === 'approval')
|
|
20
|
+
return 'USER_DENIED';
|
|
21
|
+
return 'EXECUTION_FAILED';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Compatibility classifier for historical, unstructured logs only.
|
|
25
|
+
* Production event producers must use classifyToolErrorCode() and explicit
|
|
26
|
+
* errorCode/decisionSource fields instead of interpreting command output.
|
|
27
|
+
*/
|
|
28
|
+
export function classifyLegacyToolErrorText(input) {
|
|
17
29
|
const text = [input.error, input.result]
|
|
18
30
|
.map(value => {
|
|
19
31
|
if (typeof value === 'string')
|