evolcore 0.0.10 → 0.0.12
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 +44 -0
- package/README.md +3 -3
- package/bin/codex-managed-hook.mjs +80 -0
- package/dist/agents/baseagent.js +4 -0
- package/dist/agents/claude-runner.js +158 -44
- package/dist/agents/codex-app-server-client.js +153 -23
- package/dist/agents/codex-runner.js +331 -43
- package/dist/agents/ecagent-runner.js +53 -6
- package/dist/agents/gemini-runner.js +1 -0
- package/dist/agents/request-identity.js +55 -0
- package/dist/aun/aid/client.js +11 -21
- package/dist/aun/aid/managed-operation.js +107 -0
- package/dist/aun/msg/group.js +81 -12
- package/dist/aun/msg/managed-operation.js +804 -0
- package/dist/aun/msg/mention-schema.js +20 -0
- package/dist/aun/msg/p2p.js +7 -0
- package/dist/aun/outbox.js +28 -31
- package/dist/channels/aun.js +418 -235
- package/dist/channels/daemon.js +13 -0
- package/dist/cli/agent-command.js +72 -17
- package/dist/cli/agent.js +249 -22
- package/dist/cli/aun-commands.js +270 -69
- package/dist/cli/bench.js +12 -3
- package/dist/cli/daemon-commands.js +52 -33
- package/dist/cli/fs-command.js +60 -0
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +106 -63
- package/dist/cli/init-cancel.js +208 -0
- package/dist/cli/init-channel.js +343 -195
- package/dist/cli/init.js +21 -9
- package/dist/cli/managed-command-guard.js +41 -0
- package/dist/cli/model.js +11 -4
- package/dist/cli/queue-command.js +11 -1
- package/dist/cli/response.js +71 -0
- package/dist/cli/restart-monitor.js +4 -18
- package/dist/cli/trigger-command.js +4 -0
- package/dist/config/builtin-roles.js +5 -0
- package/dist/config/contact-book-store.js +11 -3
- package/dist/config/contact-request-service.js +161 -15
- package/dist/config/gateway-config.js +26 -10
- package/dist/config/mention-mode.js +8 -24
- package/dist/core/agent-reload-coordinator.js +53 -0
- package/dist/core/auth/agent-delegation.js +0 -1
- package/dist/core/auth/operation-authorizer.js +62 -154
- package/dist/core/auth/operation-catalog.js +264 -22
- package/dist/core/bootstrap-messages.js +68 -0
- package/dist/core/bootstrap-service.js +144 -10
- package/dist/core/capability/providers/claude-capability-provider.js +9 -31
- package/dist/core/capability/providers/codex-capability-provider.js +4 -15
- package/dist/core/capability/skill-discovery.js +55 -0
- package/dist/core/channel-loader.js +24 -7
- package/dist/core/command/agent-control.js +14 -11
- package/dist/core/command/cli-intent-parser.js +9 -0
- package/dist/core/command/command-handler.js +467 -6
- package/dist/core/command/connect-menu.js +15 -0
- package/dist/core/command/group-menu.js +4 -4
- package/dist/core/command/menu-catalog.js +366 -0
- package/dist/core/command/menu-handler.js +213 -110
- package/dist/core/command/menu-protocol.js +71 -0
- package/dist/core/command/slash-gate.js +5 -2
- package/dist/core/command/slash-handler.js +45 -25
- package/dist/core/daemon-file-cache.js +2 -1
- package/dist/core/data-migration.js +9 -2
- package/dist/core/evolagent-registry.js +125 -35
- package/dist/core/evolagent.js +12 -5
- package/dist/core/handoff/dispatcher.js +5 -2
- package/dist/core/handoff/runtime.js +145 -21
- package/dist/core/handoff/store.js +24 -0
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +38 -4
- package/dist/core/message/logical-queue-bridge.js +1 -0
- package/dist/core/message/mention-schema.js +126 -0
- package/dist/core/message/message-bridge.js +237 -79
- package/dist/core/message/message-log.js +23 -0
- package/dist/core/message/message-queue.js +20 -4
- package/dist/core/message/response-engine.js +54 -36
- package/dist/core/model/model-catalog.js +143 -24
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/ec-command-parser.js +156 -10
- package/dist/core/permission/index.js +1 -0
- package/dist/core/permission/readonly-shell-query.js +532 -0
- package/dist/core/permission/sandbox-runtime.js +22 -1
- package/dist/core/permission/shell-environment.js +46 -0
- package/dist/core/permission/tool-policy.js +292 -111
- package/dist/core/protected-paths.js +17 -9
- package/dist/core/runner-reload-transaction.js +57 -0
- package/dist/core/session/session-manager.js +21 -2
- package/dist/eck/manifest-engine.js +39 -13
- package/dist/index.js +644 -180
- package/dist/ipc.js +246 -19
- package/dist/response-system/coordinator.js +6 -4
- package/dist/response-system/engines/v1/proactive-flow.js +26 -8
- package/dist/response-system/modes/single-session/index.js +14 -1
- package/dist/response-system/selector.js +6 -5
- package/dist/trigger/anomaly-store.js +4 -0
- package/dist/trigger/script-executor.js +1 -0
- package/dist/utils/aid-bind.js +3 -8
- package/dist/utils/codex-app-server-registry.js +90 -0
- package/dist/utils/codex-cli.js +5 -1
- package/dist/utils/cross-platform.js +15 -0
- package/dist/utils/log-writer.js +6 -10
- package/dist/utils/logger.js +5 -5
- package/dist/utils/npm-ops.js +5 -12
- package/dist/utils/tool-summary.js +11 -0
- package/kits/docs/channels/aun.md +1 -1
- package/kits/docs/context-assembly.md +2 -2
- package/kits/docs/evolcore/agent.md +11 -6
- package/kits/docs/evolcore/aid.md +14 -0
- package/kits/docs/evolcore/msg.md +13 -0
- package/kits/docs/prompt-loading-architecture.md +1 -2
- package/kits/docs/venues/group.md +1 -1
- package/kits/eck_manifest.json +0 -12
- package/kits/rules/01-overview.md +9 -0
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/05-venue.md +2 -2
- package/kits/schemas/agent-config.schema.3.json +1 -1
- package/kits/schemas/agent-config.schema.4.json +1 -1
- package/kits/schemas/relation-config.schema.2.json +1 -1
- package/kits/schemas/role-config.schema.1.json +1 -1
- package/kits/schemas/single-session.schema.1.json +3 -3
- package/kits/schemas/single-session.schema.2.json +3 -3
- package/kits/templates/roles/admin.json +27 -0
- package/kits/templates/roles/member.json +44 -0
- package/kits/templates/roles/visitor.json +40 -2
- package/kits/templates/system-fragments/bootstrap.md +12 -6
- package/kits/templates/system-fragments/channel.md +6 -0
- package/kits/templates/system-fragments/session.md +2 -0
- package/package.json +3 -2
- package/skills/eclink/SKILL.md +15 -3
- package/skills/eclink/agents/openai.yaml +3 -3
- package/dist/core/command/evol-menu-version-gate.js +0 -39
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { parseLiteralShellArgv } from './ec-command-parser.js';
|
|
3
|
+
const MAX_QUERY_LISTS = 8;
|
|
4
|
+
const MAX_PIPELINE_STAGES = 3;
|
|
5
|
+
const SUPPORTED_EXECUTABLES = new Set([
|
|
6
|
+
'echo', 'pwd', 'ls', 'cat', 'grep', 'rg', 'find', 'sed', 'head', 'sort',
|
|
7
|
+
]);
|
|
8
|
+
const FIND_MUTATING_PRIMARY_RE = /^-(?:delete|exec|execdir|ok|okdir|fprint|fprint0|fprintf|fls)$/;
|
|
9
|
+
const RG_PROCESS_SPAWNING_OPTION_RE = /^(?:--pre|--hostname-bin|--pre-glob|--search-zip)(?:=|$)/;
|
|
10
|
+
// The fd number must be adjacent to `>` in real shell grammar. Accepting
|
|
11
|
+
// `2 > /dev/null` would hide a literal file operand named `2` while actually
|
|
12
|
+
// redirecting stdout rather than stderr.
|
|
13
|
+
const STDERR_DEV_NULL_RE = /^(.*?)[ \t]+2>[ \t]*\/dev\/null[ \t]*$/;
|
|
14
|
+
function normalizeExecutable(value) {
|
|
15
|
+
const normalized = value.replace(/\\/g, '/');
|
|
16
|
+
const executable = path.posix.basename(normalized);
|
|
17
|
+
if (!SUPPORTED_EXECUTABLES.has(executable))
|
|
18
|
+
return undefined;
|
|
19
|
+
if (normalized === executable || normalized === `/bin/${executable}` || normalized === `/usr/bin/${executable}`) {
|
|
20
|
+
return executable;
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
function splitQueryStructure(command) {
|
|
25
|
+
const pipelines = [];
|
|
26
|
+
let pipeline = [];
|
|
27
|
+
let segment = '';
|
|
28
|
+
let quote = null;
|
|
29
|
+
let escaped = false;
|
|
30
|
+
const finishStage = () => {
|
|
31
|
+
const trimmed = segment.trim();
|
|
32
|
+
if (!trimmed)
|
|
33
|
+
return false;
|
|
34
|
+
pipeline.push(trimmed);
|
|
35
|
+
segment = '';
|
|
36
|
+
return pipeline.length <= MAX_PIPELINE_STAGES;
|
|
37
|
+
};
|
|
38
|
+
const finishPipeline = () => {
|
|
39
|
+
if (!finishStage())
|
|
40
|
+
return false;
|
|
41
|
+
pipelines.push(pipeline);
|
|
42
|
+
pipeline = [];
|
|
43
|
+
return pipelines.length <= MAX_QUERY_LISTS;
|
|
44
|
+
};
|
|
45
|
+
for (let index = 0; index < command.length; index++) {
|
|
46
|
+
const char = command[index];
|
|
47
|
+
if (escaped) {
|
|
48
|
+
segment += char;
|
|
49
|
+
escaped = false;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (char === '\\') {
|
|
53
|
+
segment += char;
|
|
54
|
+
escaped = true;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (quote === 'single') {
|
|
58
|
+
segment += char;
|
|
59
|
+
if (char === "'")
|
|
60
|
+
quote = null;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (quote === 'double') {
|
|
64
|
+
segment += char;
|
|
65
|
+
if (char === '"')
|
|
66
|
+
quote = null;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (char === "'" || char === '"') {
|
|
70
|
+
quote = char === "'" ? 'single' : 'double';
|
|
71
|
+
segment += char;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (char === '\0' || char === '\r' || char === '\n' || char === '&' || char === '<')
|
|
75
|
+
return null;
|
|
76
|
+
if (char === '|') {
|
|
77
|
+
if (command[index + 1] === '|' || !finishStage())
|
|
78
|
+
return null;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (char === ';') {
|
|
82
|
+
if (!finishPipeline())
|
|
83
|
+
return null;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
segment += char;
|
|
87
|
+
}
|
|
88
|
+
if (escaped || quote || !finishPipeline())
|
|
89
|
+
return null;
|
|
90
|
+
return pipelines;
|
|
91
|
+
}
|
|
92
|
+
function parseStageArgv(segment) {
|
|
93
|
+
const redirectMatch = segment.match(STDERR_DEV_NULL_RE);
|
|
94
|
+
const withoutRedirect = redirectMatch?.[1] ?? segment;
|
|
95
|
+
if (withoutRedirect.includes('>'))
|
|
96
|
+
return null;
|
|
97
|
+
return parseLiteralShellArgv(withoutRedirect.trim());
|
|
98
|
+
}
|
|
99
|
+
function exact(value) {
|
|
100
|
+
return { value, access: 'exact' };
|
|
101
|
+
}
|
|
102
|
+
function recursive(value) {
|
|
103
|
+
return { value, access: 'recursive' };
|
|
104
|
+
}
|
|
105
|
+
function parseOptionsAndPaths(args, valueOptions, recursiveOptions = new Set()) {
|
|
106
|
+
const paths = [];
|
|
107
|
+
let optionsEnded = false;
|
|
108
|
+
let recursiveAccess = false;
|
|
109
|
+
for (let index = 0; index < args.length; index++) {
|
|
110
|
+
const argument = args[index];
|
|
111
|
+
if (!optionsEnded && argument === '--') {
|
|
112
|
+
optionsEnded = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (!optionsEnded && argument.startsWith('-') && argument !== '-') {
|
|
116
|
+
const option = argument.split('=', 1)[0];
|
|
117
|
+
if (recursiveOptions.has(option) || [...recursiveOptions].some(entry => entry.length === 2 && argument.startsWith('-') && !argument.startsWith('--') && argument.slice(1).includes(entry[1]))) {
|
|
118
|
+
recursiveAccess = true;
|
|
119
|
+
}
|
|
120
|
+
if (!argument.includes('=') && valueOptions.has(option)) {
|
|
121
|
+
if (++index >= args.length)
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (argument !== '-')
|
|
127
|
+
paths.push(argument);
|
|
128
|
+
}
|
|
129
|
+
return { paths, recursive: recursiveAccess };
|
|
130
|
+
}
|
|
131
|
+
function analyzeLs(args) {
|
|
132
|
+
if (args.some(argument => argument === '--dereference'
|
|
133
|
+
|| (!argument.startsWith('--') && /^-[^-]*L/.test(argument))))
|
|
134
|
+
return null;
|
|
135
|
+
const parsed = parseOptionsAndPaths(args, new Set([
|
|
136
|
+
'-I', '-T', '-w', '--block-size', '--color', '--format', '--hide', '--ignore',
|
|
137
|
+
'--indicator-style', '--quoting-style', '--sort', '--tabsize', '--time', '--time-style',
|
|
138
|
+
]), new Set(['-R', '--recursive']));
|
|
139
|
+
if (!parsed)
|
|
140
|
+
return null;
|
|
141
|
+
const values = parsed.paths.length > 0 ? parsed.paths : ['.'];
|
|
142
|
+
return values.map(value => parsed.recursive ? recursive(value) : exact(value));
|
|
143
|
+
}
|
|
144
|
+
function analyzeCat(args) {
|
|
145
|
+
const parsed = parseOptionsAndPaths(args, new Set());
|
|
146
|
+
return parsed?.paths.map(exact) ?? null;
|
|
147
|
+
}
|
|
148
|
+
function consumeOptionValue(args, index, inline) {
|
|
149
|
+
if (inline)
|
|
150
|
+
return index;
|
|
151
|
+
return index + 1 < args.length ? index + 1 : null;
|
|
152
|
+
}
|
|
153
|
+
function analyzeGrep(args) {
|
|
154
|
+
const paths = [];
|
|
155
|
+
let optionsEnded = false;
|
|
156
|
+
let hasExplicitPattern = false;
|
|
157
|
+
let implicitPatternSeen = false;
|
|
158
|
+
let inputOperandSeen = false;
|
|
159
|
+
let recursiveAccess = false;
|
|
160
|
+
const textValueOptions = new Set([
|
|
161
|
+
'-A', '-B', '-C', '-m', '--after-context', '--before-context', '--binary-files',
|
|
162
|
+
'--color', '--context', '--directories', '--exclude', '--exclude-dir', '--group-separator',
|
|
163
|
+
'--include', '--label', '--max-count',
|
|
164
|
+
]);
|
|
165
|
+
const patternFileOptions = new Set(['-f', '--file']);
|
|
166
|
+
const auxiliaryFileOptions = new Set(['--exclude-from']);
|
|
167
|
+
for (let index = 0; index < args.length; index++) {
|
|
168
|
+
const argument = args[index];
|
|
169
|
+
if (!optionsEnded && argument === '--') {
|
|
170
|
+
optionsEnded = true;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (!optionsEnded && argument.startsWith('-') && argument !== '-') {
|
|
174
|
+
const shortBody = argument.startsWith('--') ? '' : argument.slice(1);
|
|
175
|
+
const inlineValueOption = shortBody.length > 1 && ['d', 'e', 'f'].includes(shortBody[0]);
|
|
176
|
+
if (!inlineValueOption && /[def]/.test(shortBody.slice(1)))
|
|
177
|
+
return null;
|
|
178
|
+
if (argument === '-R' || argument === '--dereference-recursive'
|
|
179
|
+
|| (!inlineValueOption && shortBody.includes('R')))
|
|
180
|
+
return null;
|
|
181
|
+
if (argument === '-r' || argument === '--recursive'
|
|
182
|
+
|| (!inlineValueOption && shortBody.includes('r'))) {
|
|
183
|
+
recursiveAccess = true;
|
|
184
|
+
}
|
|
185
|
+
const option = argument.split('=', 1)[0];
|
|
186
|
+
if (option === '--directories' || argument === '-d' || (!argument.startsWith('--') && argument.startsWith('-d'))) {
|
|
187
|
+
const inline = argument.includes('=') || (!argument.startsWith('--') && argument.length > 2);
|
|
188
|
+
const consumed = consumeOptionValue(args, index, inline);
|
|
189
|
+
if (consumed === null)
|
|
190
|
+
return null;
|
|
191
|
+
const value = argument.includes('=')
|
|
192
|
+
? argument.slice(argument.indexOf('=') + 1)
|
|
193
|
+
: inline
|
|
194
|
+
? argument.slice(2)
|
|
195
|
+
: args[consumed];
|
|
196
|
+
if (value === 'recurse')
|
|
197
|
+
recursiveAccess = true;
|
|
198
|
+
index = consumed;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const inline = argument.includes('=') || (!argument.startsWith('--') && argument.length > 2
|
|
202
|
+
&& (argument[1] === 'e' || argument[1] === 'f'));
|
|
203
|
+
if (option === '-e' || option === '--regexp' || (!argument.startsWith('--') && argument.startsWith('-e'))) {
|
|
204
|
+
const consumed = consumeOptionValue(args, index, inline);
|
|
205
|
+
if (consumed === null)
|
|
206
|
+
return null;
|
|
207
|
+
index = consumed;
|
|
208
|
+
hasExplicitPattern = true;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const patternFileOption = patternFileOptions.has(option)
|
|
212
|
+
|| (!argument.startsWith('--') && argument.startsWith('-f'));
|
|
213
|
+
const pathOption = patternFileOption || auxiliaryFileOptions.has(option);
|
|
214
|
+
if (pathOption) {
|
|
215
|
+
const consumed = consumeOptionValue(args, index, inline);
|
|
216
|
+
if (consumed === null)
|
|
217
|
+
return null;
|
|
218
|
+
if (inline) {
|
|
219
|
+
const value = argument.includes('=') ? argument.slice(argument.indexOf('=') + 1) : argument.slice(2);
|
|
220
|
+
if (value)
|
|
221
|
+
paths.push(exact(value));
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
paths.push(exact(args[consumed]));
|
|
225
|
+
}
|
|
226
|
+
index = consumed;
|
|
227
|
+
if (patternFileOption)
|
|
228
|
+
hasExplicitPattern = true;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (textValueOptions.has(option)) {
|
|
232
|
+
const consumed = consumeOptionValue(args, index, argument.includes('='));
|
|
233
|
+
if (consumed === null)
|
|
234
|
+
return null;
|
|
235
|
+
index = consumed;
|
|
236
|
+
}
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (!hasExplicitPattern && !implicitPatternSeen) {
|
|
240
|
+
implicitPatternSeen = true;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
inputOperandSeen = true;
|
|
244
|
+
if (argument !== '-')
|
|
245
|
+
paths.push(recursiveAccess ? recursive(argument) : exact(argument));
|
|
246
|
+
}
|
|
247
|
+
if (!hasExplicitPattern && !implicitPatternSeen)
|
|
248
|
+
return null;
|
|
249
|
+
if (recursiveAccess && !inputOperandSeen)
|
|
250
|
+
paths.push(recursive('.'));
|
|
251
|
+
return paths;
|
|
252
|
+
}
|
|
253
|
+
function analyzeRg(args) {
|
|
254
|
+
if (args.some(argument => RG_PROCESS_SPAWNING_OPTION_RE.test(argument)
|
|
255
|
+
|| argument === '--follow'
|
|
256
|
+
// `-z` is the short form of `--search-zip`; it may also appear in a
|
|
257
|
+
// short-option cluster such as `-nz`. Once -e/-f/-g starts an attached
|
|
258
|
+
// value, later letters belong to that value rather than the option set.
|
|
259
|
+
|| (argument.startsWith('-') && !argument.startsWith('--') && (() => {
|
|
260
|
+
const body = argument.slice(1);
|
|
261
|
+
if (body.length > 1 && ['e', 'f', 'g'].includes(body[0]))
|
|
262
|
+
return false;
|
|
263
|
+
if (/[efg]/.test(body.slice(1)))
|
|
264
|
+
return true;
|
|
265
|
+
return /[Lz]/.test(body);
|
|
266
|
+
})())))
|
|
267
|
+
return null;
|
|
268
|
+
const paths = [];
|
|
269
|
+
let optionsEnded = false;
|
|
270
|
+
let hasExplicitPattern = false;
|
|
271
|
+
let implicitPatternSeen = false;
|
|
272
|
+
let filesMode = false;
|
|
273
|
+
let inputOperandSeen = false;
|
|
274
|
+
const textValueOptions = new Set([
|
|
275
|
+
'-A', '-B', '-C', '-E', '-M', '-g', '-j', '-m', '-r', '-t', '-T',
|
|
276
|
+
'--after-context', '--before-context', '--color', '--colors', '--context', '--context-separator',
|
|
277
|
+
'--dfa-size-limit', '--encoding', '--engine', '--field-context-separator', '--field-match-separator',
|
|
278
|
+
'--glob', '--iglob', '--max-columns', '--max-count', '--max-depth', '--max-filesize', '--path-separator',
|
|
279
|
+
'--regex-size-limit', '--replace', '--sort', '--sortr', '--type', '--type-add', '--type-clear', '--type-not',
|
|
280
|
+
]);
|
|
281
|
+
const patternFileOptions = new Set(['-f', '--file']);
|
|
282
|
+
const auxiliaryFileOptions = new Set(['--ignore-file']);
|
|
283
|
+
for (let index = 0; index < args.length; index++) {
|
|
284
|
+
const argument = args[index];
|
|
285
|
+
if (!optionsEnded && argument === '--') {
|
|
286
|
+
optionsEnded = true;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (!optionsEnded && argument.startsWith('-') && argument !== '-') {
|
|
290
|
+
const option = argument.split('=', 1)[0];
|
|
291
|
+
if (['--files', '--files-with-matches', '--files-without-match', '--type-list'].includes(option))
|
|
292
|
+
filesMode = true;
|
|
293
|
+
const inlineShort = !argument.startsWith('--') && argument.length > 2
|
|
294
|
+
&& (argument[1] === 'e' || argument[1] === 'f' || argument[1] === 'g');
|
|
295
|
+
if (option === '-e' || option === '--regexp' || (!argument.startsWith('--') && argument.startsWith('-e'))) {
|
|
296
|
+
const consumed = consumeOptionValue(args, index, argument.includes('=') || inlineShort);
|
|
297
|
+
if (consumed === null)
|
|
298
|
+
return null;
|
|
299
|
+
index = consumed;
|
|
300
|
+
hasExplicitPattern = true;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const patternFileOption = patternFileOptions.has(option)
|
|
304
|
+
|| (!argument.startsWith('--') && argument.startsWith('-f'));
|
|
305
|
+
const pathOption = patternFileOption || auxiliaryFileOptions.has(option);
|
|
306
|
+
if (pathOption) {
|
|
307
|
+
const inline = argument.includes('=') || inlineShort;
|
|
308
|
+
const consumed = consumeOptionValue(args, index, inline);
|
|
309
|
+
if (consumed === null)
|
|
310
|
+
return null;
|
|
311
|
+
if (inline) {
|
|
312
|
+
const value = argument.includes('=') ? argument.slice(argument.indexOf('=') + 1) : argument.slice(2);
|
|
313
|
+
if (value)
|
|
314
|
+
paths.push(exact(value));
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
paths.push(exact(args[consumed]));
|
|
318
|
+
}
|
|
319
|
+
index = consumed;
|
|
320
|
+
if (patternFileOption)
|
|
321
|
+
hasExplicitPattern = true;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (textValueOptions.has(option) || (!argument.startsWith('--') && argument.startsWith('-g'))) {
|
|
325
|
+
const consumed = consumeOptionValue(args, index, argument.includes('=') || inlineShort);
|
|
326
|
+
if (consumed === null)
|
|
327
|
+
return null;
|
|
328
|
+
index = consumed;
|
|
329
|
+
}
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (!filesMode && !hasExplicitPattern && !implicitPatternSeen) {
|
|
333
|
+
implicitPatternSeen = true;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
inputOperandSeen = true;
|
|
337
|
+
if (argument !== '-')
|
|
338
|
+
paths.push(recursive(argument));
|
|
339
|
+
}
|
|
340
|
+
if (!filesMode && !hasExplicitPattern && !implicitPatternSeen)
|
|
341
|
+
return null;
|
|
342
|
+
if (!inputOperandSeen)
|
|
343
|
+
paths.push(recursive('.'));
|
|
344
|
+
return paths;
|
|
345
|
+
}
|
|
346
|
+
function analyzeFind(args) {
|
|
347
|
+
if (args.some(argument => FIND_MUTATING_PRIMARY_RE.test(argument)))
|
|
348
|
+
return null;
|
|
349
|
+
if (args.includes('-L') || args.includes('-H') || args.includes('-follow'))
|
|
350
|
+
return null;
|
|
351
|
+
const paths = [];
|
|
352
|
+
let index = 0;
|
|
353
|
+
while (index < args.length && (args[index] === '-P' || args[index].startsWith('-O') || args[index] === '-D')) {
|
|
354
|
+
if (args[index] === '-D') {
|
|
355
|
+
if (++index >= args.length)
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
index++;
|
|
359
|
+
}
|
|
360
|
+
while (index < args.length && !args[index].startsWith('-') && !['!', '(', ')'].includes(args[index])) {
|
|
361
|
+
paths.push(recursive(args[index++]));
|
|
362
|
+
}
|
|
363
|
+
if (paths.length === 0)
|
|
364
|
+
paths.push(recursive('.'));
|
|
365
|
+
const noValue = new Set([
|
|
366
|
+
'!', '(', ')', '-a', '-and', '-d', '-daystart', '-depth', '-empty', '-executable', '-false',
|
|
367
|
+
'-help', '--help', '-ignore_readdir_race', '-ls', '-mount', '-noignore_readdir_race', '-noleaf',
|
|
368
|
+
'-not', '-o', '-or', '-print', '-print0', '-prune', '-quit', '-readable', '-true', '-version',
|
|
369
|
+
'--version', '-writable', '-xdev',
|
|
370
|
+
]);
|
|
371
|
+
const textValue = new Set([
|
|
372
|
+
'-amin', '-atime', '-cmin', '-ctime', '-fstype', '-gid', '-group', '-ilname', '-iname', '-inum',
|
|
373
|
+
'-ipath', '-iregex', '-iwholename', '-links', '-lname', '-maxdepth', '-mindepth', '-mmin', '-mtime',
|
|
374
|
+
'-name', '-path', '-perm', '-printf', '-regex', '-regextype', '-size', '-type', '-uid', '-used',
|
|
375
|
+
'-user', '-wholename', '-xtype',
|
|
376
|
+
]);
|
|
377
|
+
const pathValue = new Set(['-anewer', '-cnewer', '-newer', '-samefile']);
|
|
378
|
+
while (index < args.length) {
|
|
379
|
+
const argument = args[index++];
|
|
380
|
+
// The file contents become additional starting points, so their protected
|
|
381
|
+
// path semantics cannot be proven from argv alone.
|
|
382
|
+
if (argument === '-files0-from')
|
|
383
|
+
return null;
|
|
384
|
+
if (noValue.has(argument))
|
|
385
|
+
continue;
|
|
386
|
+
if (textValue.has(argument)) {
|
|
387
|
+
if (index >= args.length)
|
|
388
|
+
return null;
|
|
389
|
+
index++;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (/^-newer[aBcm]?t$/.test(argument)) {
|
|
393
|
+
if (index >= args.length)
|
|
394
|
+
return null;
|
|
395
|
+
index++;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (/^-newer[A-Za-z]{2}$/.test(argument) || pathValue.has(argument)) {
|
|
399
|
+
if (index >= args.length)
|
|
400
|
+
return null;
|
|
401
|
+
paths.push(exact(args[index++]));
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
return paths;
|
|
407
|
+
}
|
|
408
|
+
function analyzeSed(args) {
|
|
409
|
+
if (!['-n', '--quiet', '--silent'].includes(args[0]))
|
|
410
|
+
return null;
|
|
411
|
+
const program = args[1];
|
|
412
|
+
if (!program || !/^(?:p|(?:\d+|\$)(?:,(?:\d+|\$))?p)$/.test(program))
|
|
413
|
+
return null;
|
|
414
|
+
if (args.slice(2).some(argument => argument.startsWith('-') && argument !== '-'))
|
|
415
|
+
return null;
|
|
416
|
+
return args.slice(2).filter(argument => argument !== '-').map(exact);
|
|
417
|
+
}
|
|
418
|
+
function analyzeHead(args) {
|
|
419
|
+
const parsed = parseOptionsAndPaths(args, new Set(['-c', '-n', '--bytes', '--lines']));
|
|
420
|
+
return parsed?.paths.map(exact) ?? null;
|
|
421
|
+
}
|
|
422
|
+
function analyzeSort(args) {
|
|
423
|
+
if (args.some(argument => /^(?:-o|-T)(?:.+|$)|^(?:--output|--compress-program|--temporary-directory|--files0-from)(?:=|$)/.test(argument))) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
const paths = [];
|
|
427
|
+
let optionsEnded = false;
|
|
428
|
+
const textValueOptions = new Set([
|
|
429
|
+
'-k', '-S', '-t', '--batch-size', '--buffer-size', '--field-separator', '--key', '--parallel', '--sort',
|
|
430
|
+
]);
|
|
431
|
+
for (let index = 0; index < args.length; index++) {
|
|
432
|
+
const argument = args[index];
|
|
433
|
+
if (!optionsEnded && argument === '--') {
|
|
434
|
+
optionsEnded = true;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
if (!optionsEnded && argument.startsWith('-') && argument !== '-') {
|
|
438
|
+
const option = argument.split('=', 1)[0];
|
|
439
|
+
if (option === '--random-source') {
|
|
440
|
+
const inline = argument.includes('=');
|
|
441
|
+
const consumed = consumeOptionValue(args, index, inline);
|
|
442
|
+
if (consumed === null)
|
|
443
|
+
return null;
|
|
444
|
+
paths.push(exact(inline ? argument.slice(argument.indexOf('=') + 1) : args[consumed]));
|
|
445
|
+
index = consumed;
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (textValueOptions.has(option)) {
|
|
449
|
+
const consumed = consumeOptionValue(args, index, argument.includes('='));
|
|
450
|
+
if (consumed === null)
|
|
451
|
+
return null;
|
|
452
|
+
index = consumed;
|
|
453
|
+
}
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (argument !== '-')
|
|
457
|
+
paths.push(exact(argument));
|
|
458
|
+
}
|
|
459
|
+
return paths;
|
|
460
|
+
}
|
|
461
|
+
function analyzeCommand(argv) {
|
|
462
|
+
const executable = normalizeExecutable(argv[0]);
|
|
463
|
+
if (!executable)
|
|
464
|
+
return null;
|
|
465
|
+
const args = argv.slice(1);
|
|
466
|
+
let pathOperands = null;
|
|
467
|
+
switch (executable) {
|
|
468
|
+
case 'echo':
|
|
469
|
+
pathOperands = [];
|
|
470
|
+
break;
|
|
471
|
+
case 'pwd':
|
|
472
|
+
pathOperands = args.every(argument => ['-L', '-P', '--help', '--version'].includes(argument)) ? [] : null;
|
|
473
|
+
break;
|
|
474
|
+
case 'ls':
|
|
475
|
+
pathOperands = analyzeLs(args);
|
|
476
|
+
break;
|
|
477
|
+
case 'cat':
|
|
478
|
+
pathOperands = analyzeCat(args);
|
|
479
|
+
break;
|
|
480
|
+
case 'grep':
|
|
481
|
+
pathOperands = analyzeGrep(args);
|
|
482
|
+
break;
|
|
483
|
+
case 'rg':
|
|
484
|
+
pathOperands = analyzeRg(args);
|
|
485
|
+
break;
|
|
486
|
+
case 'find':
|
|
487
|
+
pathOperands = analyzeFind(args);
|
|
488
|
+
break;
|
|
489
|
+
case 'sed':
|
|
490
|
+
pathOperands = analyzeSed(args);
|
|
491
|
+
break;
|
|
492
|
+
case 'head':
|
|
493
|
+
pathOperands = analyzeHead(args);
|
|
494
|
+
break;
|
|
495
|
+
case 'sort':
|
|
496
|
+
pathOperands = analyzeSort(args);
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
if (!pathOperands)
|
|
500
|
+
return null;
|
|
501
|
+
return { command: { executable, argv, pathOperands } };
|
|
502
|
+
}
|
|
503
|
+
export function analyzeReadonlyShellQuery(command) {
|
|
504
|
+
const structure = splitQueryStructure(command);
|
|
505
|
+
if (!structure)
|
|
506
|
+
return { kind: 'unproven', reason: 'unsupported-shell-structure' };
|
|
507
|
+
const pipelines = [];
|
|
508
|
+
const pathOperands = [];
|
|
509
|
+
for (const stages of structure) {
|
|
510
|
+
const pipeline = [];
|
|
511
|
+
for (let index = 0; index < stages.length; index++) {
|
|
512
|
+
const argv = parseStageArgv(stages[index]);
|
|
513
|
+
if (!argv)
|
|
514
|
+
return { kind: 'unproven', reason: 'non-literal-command' };
|
|
515
|
+
const analyzed = analyzeCommand(argv);
|
|
516
|
+
if (!analyzed)
|
|
517
|
+
return { kind: 'unproven', reason: 'unsupported-command-or-option' };
|
|
518
|
+
if (index > 0) {
|
|
519
|
+
if (!['head', 'sort'].includes(analyzed.command.executable)) {
|
|
520
|
+
return { kind: 'unproven', reason: 'unsupported-pipeline-filter' };
|
|
521
|
+
}
|
|
522
|
+
if (analyzed.command.pathOperands.length > 0) {
|
|
523
|
+
return { kind: 'unproven', reason: 'pipeline-filter-must-use-stdin' };
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
pipeline.push(analyzed.command);
|
|
527
|
+
pathOperands.push(...analyzed.command.pathOperands);
|
|
528
|
+
}
|
|
529
|
+
pipelines.push(pipeline);
|
|
530
|
+
}
|
|
531
|
+
return { kind: 'proven-readonly', ir: { pipelines, pathOperands } };
|
|
532
|
+
}
|
|
@@ -210,7 +210,7 @@ function appendLClassReadOnlyBinds(args, root) {
|
|
|
210
210
|
args.push('--ro-bind', target.path, target.path);
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
-
export function buildHClassGuardCommand(executable, executableArgs, root = resolveRoot()) {
|
|
213
|
+
export function buildHClassGuardCommand(executable, executableArgs, root = resolveRoot(), options = {}) {
|
|
214
214
|
const bubblewrapPath = resolveBubblewrapPath();
|
|
215
215
|
if (!bubblewrapPath)
|
|
216
216
|
return undefined;
|
|
@@ -224,6 +224,27 @@ export function buildHClassGuardCommand(executable, executableArgs, root = resol
|
|
|
224
224
|
];
|
|
225
225
|
appendLClassReadOnlyBinds(args, path.resolve(root));
|
|
226
226
|
appendHClassMasks(args, path.resolve(root), true);
|
|
227
|
+
if (options.managedCodexRequirements) {
|
|
228
|
+
const requirements = path.resolve(options.managedCodexRequirements);
|
|
229
|
+
if (!fs.statSync(requirements).isFile()) {
|
|
230
|
+
throw new Error(`Codex managed requirements is not a file: ${requirements}`);
|
|
231
|
+
}
|
|
232
|
+
const managedDirectory = path.dirname(requirements);
|
|
233
|
+
// Keep the copied hook immutable even when a session uses bypass mode.
|
|
234
|
+
args.push('--ro-bind', managedDirectory, managedDirectory);
|
|
235
|
+
// A host may not have /etc/codex yet. Overlay /etc inside this namespace so
|
|
236
|
+
// Bubblewrap can create the mountpoint without modifying the host, while
|
|
237
|
+
// retaining the complete host /etc as the read-only lower layer.
|
|
238
|
+
args.push('--overlay-src', '/etc');
|
|
239
|
+
args.push('--tmp-overlay', '/etc');
|
|
240
|
+
args.push('--dir', '/etc/codex');
|
|
241
|
+
// Replace the complete Codex system-config directory inside this app-server
|
|
242
|
+
// namespace so host system hooks cannot join EvolCore's managed hook set.
|
|
243
|
+
args.push('--tmpfs', '/etc/codex');
|
|
244
|
+
args.push('--ro-bind', requirements, '/etc/codex/requirements.toml');
|
|
245
|
+
args.push('--remount-ro', '/etc/codex');
|
|
246
|
+
args.push('--remount-ro', '/etc');
|
|
247
|
+
}
|
|
227
248
|
args.push('--', executable, ...executableArgs);
|
|
228
249
|
return { command: bubblewrapPath, args };
|
|
229
250
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const SHELL_CODE_INJECTION_ENV = new Set([
|
|
2
|
+
'BASH_ENV',
|
|
3
|
+
'BASHOPTS',
|
|
4
|
+
'BASH_XTRACEFD',
|
|
5
|
+
'CDPATH',
|
|
6
|
+
'ENV',
|
|
7
|
+
'GCONV_PATH',
|
|
8
|
+
'GREP_OPTIONS',
|
|
9
|
+
'LD_AUDIT',
|
|
10
|
+
'LD_LIBRARY_PATH',
|
|
11
|
+
'LD_PRELOAD',
|
|
12
|
+
'LOCPATH',
|
|
13
|
+
'NODE_OPTIONS',
|
|
14
|
+
'NODE_PATH',
|
|
15
|
+
'PROMPT_COMMAND',
|
|
16
|
+
'PS4',
|
|
17
|
+
'RIPGREP_CONFIG_PATH',
|
|
18
|
+
'SHELLOPTS',
|
|
19
|
+
'DYLD_FRAMEWORK_PATH',
|
|
20
|
+
'DYLD_INSERT_LIBRARIES',
|
|
21
|
+
'DYLD_LIBRARY_PATH',
|
|
22
|
+
]);
|
|
23
|
+
const EXECUTABLE_RESOLUTION_ENV = ['PATH', 'HOME', 'SHELL'];
|
|
24
|
+
export function isShellCodeInjectionEnvironmentName(name) {
|
|
25
|
+
return SHELL_CODE_INJECTION_ENV.has(name)
|
|
26
|
+
|| name.startsWith('BASH_FUNC_')
|
|
27
|
+
|| name.startsWith('LD_')
|
|
28
|
+
|| name.startsWith('DYLD_');
|
|
29
|
+
}
|
|
30
|
+
export function sanitizeShellExecutionEnvironment(input, options = {}) {
|
|
31
|
+
const sanitized = { ...input };
|
|
32
|
+
for (const name of Object.keys(sanitized)) {
|
|
33
|
+
if (isShellCodeInjectionEnvironmentName(name))
|
|
34
|
+
delete sanitized[name];
|
|
35
|
+
}
|
|
36
|
+
if (options.lockExecutableResolution) {
|
|
37
|
+
const baseline = options.baseline ?? process.env;
|
|
38
|
+
for (const name of EXECUTABLE_RESOLUTION_ENV) {
|
|
39
|
+
if (baseline[name] === undefined)
|
|
40
|
+
delete sanitized[name];
|
|
41
|
+
else
|
|
42
|
+
sanitized[name] = baseline[name];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return sanitized;
|
|
46
|
+
}
|