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
|
@@ -3,8 +3,9 @@ import path from 'path';
|
|
|
3
3
|
import { randomUUID } from 'crypto';
|
|
4
4
|
import { logger } from '../../utils/logger.js';
|
|
5
5
|
import { resolveRoot } from '../../paths.js';
|
|
6
|
-
import { containsHClassReference, containsLClassReference, hClassGrantIncludesProtectedPath, isHClassPath, isSameOrDescendant, isLClassPath, lClassGrantIncludesProtectedPath, resolveProtectedCandidate, } from '../protected-paths.js';
|
|
7
|
-
import { classifyEvolcoreShellCommand, parseBoundedOutputShellCommand, parseLiteralShellArgv, } from './ec-command-parser.js';
|
|
6
|
+
import { containsHClassReference, containsLClassReference, getExistingHClassMaskTargets, hClassGrantIncludesProtectedPath, isHClassPath, isSameOrDescendant, isLClassPath, lClassGrantIncludesProtectedPath, resolveProtectedCandidate, } from '../protected-paths.js';
|
|
7
|
+
import { classifyEvolcoreShellCommand, parseCodexToolCommandArgv, parseBoundedOutputShellCommand, parseLiteralShellArgv, unwrapCodexShellCommandArgv, } from './ec-command-parser.js';
|
|
8
|
+
import { analyzeReadonlyShellQuery, } from './readonly-shell-query.js';
|
|
8
9
|
// 绝对禁止命令(所有角色、所有权限模式下都禁止,不可授权)
|
|
9
10
|
// 这些是系统级破坏操作,任何情况下都不应该允许
|
|
10
11
|
const ABSOLUTE_FORBIDDEN = [
|
|
@@ -14,19 +15,6 @@ const ABSOLUTE_FORBIDDEN = [
|
|
|
14
15
|
/\bformat\s+[a-zA-Z]:/i, // format C: (格式化磁盘)
|
|
15
16
|
/\bdd\s+if=.*of=\/dev/, // dd 写入磁盘设备(读取操作允许)
|
|
16
17
|
];
|
|
17
|
-
const READONLY_SHELL_EXECUTABLES = new Set([
|
|
18
|
-
'ls', '/bin/ls', '/usr/bin/ls',
|
|
19
|
-
'cat', '/bin/cat', '/usr/bin/cat',
|
|
20
|
-
]);
|
|
21
|
-
const L_CLASS_READONLY_SHELL_EXECUTABLES = new Set([
|
|
22
|
-
...READONLY_SHELL_EXECUTABLES,
|
|
23
|
-
'grep', '/bin/grep', '/usr/bin/grep',
|
|
24
|
-
'rg', '/bin/rg', '/usr/bin/rg',
|
|
25
|
-
'find', '/bin/find', '/usr/bin/find',
|
|
26
|
-
'sed', '/bin/sed', '/usr/bin/sed',
|
|
27
|
-
]);
|
|
28
|
-
const FIND_MUTATING_PRIMARY_RE = /^-(?:delete|exec|execdir|ok|okdir|fprint|fprint0|fprintf|fls)$/;
|
|
29
|
-
const RG_PROCESS_SPAWNING_OPTION_RE = /^--pre(?:=|$)|^--pre-glob(?:=|$)/;
|
|
30
18
|
// 危险操作(需要用户审批才能执行)
|
|
31
19
|
// 这些操作有合理使用场景,但需要用户明确授权
|
|
32
20
|
const DANGEROUS_PATTERNS = [
|
|
@@ -331,19 +319,163 @@ function containsNetStopCommand(command, depth = 0) {
|
|
|
331
319
|
&& containsNetStopCommand(parsed.args.slice(commandIndex + 1).join(' '), depth + 1);
|
|
332
320
|
});
|
|
333
321
|
}
|
|
322
|
+
function queryPathOperands(analysis) {
|
|
323
|
+
return analysis.kind === 'proven-readonly' ? analysis.ir.pathOperands : [];
|
|
324
|
+
}
|
|
325
|
+
function shellOperandMatchesClass(operand, className, options) {
|
|
326
|
+
const reference = className === 'h' ? containsHClassReference(operand.value) : containsLClassReference(operand.value);
|
|
327
|
+
if (reference)
|
|
328
|
+
return true;
|
|
329
|
+
if (operand.access === 'recursive') {
|
|
330
|
+
return className === 'h'
|
|
331
|
+
? hClassReadScopeIncludesProtectedPath(operand.value, options)
|
|
332
|
+
: lClassGrantIncludesProtectedPath(operand.value, options);
|
|
333
|
+
}
|
|
334
|
+
return className === 'h'
|
|
335
|
+
? isHClassPath(operand.value, options)
|
|
336
|
+
: isLClassPath(operand.value, options);
|
|
337
|
+
}
|
|
334
338
|
/**
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
+
* A read-only recursive query only needs to cover H-class targets that exist
|
|
340
|
+
* now. This deliberately differs from a filesystem *grant*: a grant below
|
|
341
|
+
* any directory could later create a protected backup name, while a proven
|
|
342
|
+
* query cannot write and should still be able to inspect an ordinary logs/
|
|
343
|
+
* directory.
|
|
344
|
+
*/
|
|
345
|
+
function hClassReadScopeIncludesProtectedPath(value, options) {
|
|
346
|
+
const root = resolveProtectedCandidate(options.root ?? resolveRoot());
|
|
347
|
+
const scope = resolveProtectedCandidate(value, options.cwd);
|
|
348
|
+
if (isSameOrDescendant(root, scope))
|
|
349
|
+
return true;
|
|
350
|
+
if (!isSameOrDescendant(scope, root))
|
|
351
|
+
return false;
|
|
352
|
+
return getExistingHClassMaskTargets(root).some(target => isSameOrDescendant(target.path, scope));
|
|
353
|
+
}
|
|
354
|
+
function analyzeShellProtectedOperands(command, options) {
|
|
355
|
+
const analysis = analyzeReadonlyShellQuery(command);
|
|
356
|
+
if (analysis.kind === 'unproven') {
|
|
357
|
+
return {
|
|
358
|
+
analysis,
|
|
359
|
+
hClass: containsHClassReference(command),
|
|
360
|
+
lClass: containsLClassReference(command),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
const operands = queryPathOperands(analysis);
|
|
364
|
+
return {
|
|
365
|
+
analysis,
|
|
366
|
+
hClass: operands.some(operand => shellOperandMatchesClass(operand, 'h', options)),
|
|
367
|
+
lClass: operands.some(operand => shellOperandMatchesClass(operand, 'l', options)),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function protectedReadToolPaths(toolName, input) {
|
|
371
|
+
if (toolName === 'Read') {
|
|
372
|
+
return typeof input.file_path === 'string' ? [input.file_path] : [];
|
|
373
|
+
}
|
|
374
|
+
if (toolName === 'Glob') {
|
|
375
|
+
return [input.pattern, input.path].filter((value) => typeof value === 'string' && value.length > 0);
|
|
376
|
+
}
|
|
377
|
+
if (toolName === 'Grep') {
|
|
378
|
+
return typeof input.path === 'string' ? [input.path] : [];
|
|
379
|
+
}
|
|
380
|
+
return [];
|
|
381
|
+
}
|
|
382
|
+
function protectedReadToolMatchesLClass(toolName, input, options) {
|
|
383
|
+
const paths = protectedReadToolPaths(toolName, input);
|
|
384
|
+
if (paths.some(filePath => containsLClassReference(filePath) || isLClassPath(filePath, options))) {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
if (toolName === 'Grep') {
|
|
388
|
+
const scope = typeof input.path === 'string' && input.path ? input.path : '.';
|
|
389
|
+
return lClassGrantIncludesProtectedPath(scope, options);
|
|
390
|
+
}
|
|
391
|
+
if (toolName === 'Glob') {
|
|
392
|
+
const scope = globReadScope(input, options);
|
|
393
|
+
return lClassGrantIncludesProtectedPath(scope, { root: options.root });
|
|
394
|
+
}
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
function protectedReadToolMatchesHClass(toolName, input, options) {
|
|
398
|
+
const paths = protectedReadToolPaths(toolName, input);
|
|
399
|
+
if (paths.some(filePath => containsHClassReference(filePath) || isHClassPath(filePath, options))) {
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
if (toolName === 'Grep') {
|
|
403
|
+
const scope = typeof input.path === 'string' && input.path ? input.path : '.';
|
|
404
|
+
return hClassReadScopeIncludesProtectedPath(scope, options);
|
|
405
|
+
}
|
|
406
|
+
if (toolName === 'Glob') {
|
|
407
|
+
const scope = globReadScope(input, options);
|
|
408
|
+
return hClassReadScopeIncludesProtectedPath(scope, options);
|
|
409
|
+
}
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
function globLiteralPrefix(pattern) {
|
|
413
|
+
const normalized = pattern.replace(/\\/g, '/');
|
|
414
|
+
const globIndex = normalized.search(/[*?[{]/);
|
|
415
|
+
return globIndex < 0 ? normalized : normalized.slice(0, globIndex).replace(/\/+$/, '');
|
|
416
|
+
}
|
|
417
|
+
function globReadScope(input, options) {
|
|
418
|
+
const pattern = typeof input.pattern === 'string' && input.pattern ? input.pattern : '**/*';
|
|
419
|
+
const baseValue = typeof input.path === 'string' && input.path ? input.path : '.';
|
|
420
|
+
const base = resolveProtectedCandidate(baseValue, options.cwd);
|
|
421
|
+
const prefix = globLiteralPrefix(pattern);
|
|
422
|
+
return path.isAbsolute(pattern)
|
|
423
|
+
? resolveProtectedCandidate(prefix || path.parse(pattern).root)
|
|
424
|
+
: resolveProtectedCandidate(prefix || '.', base);
|
|
425
|
+
}
|
|
426
|
+
function protectedReadToolStaysInWorkspace(toolName, input, projectPath) {
|
|
427
|
+
const workspace = resolveProtectedCandidate(projectPath);
|
|
428
|
+
if (toolName === 'Read') {
|
|
429
|
+
if (typeof input.file_path !== 'string' || !input.file_path)
|
|
430
|
+
return false;
|
|
431
|
+
return isSameOrDescendant(resolveProtectedCandidate(input.file_path, projectPath), workspace);
|
|
432
|
+
}
|
|
433
|
+
if (toolName === 'Grep') {
|
|
434
|
+
const scope = typeof input.path === 'string' && input.path ? input.path : '.';
|
|
435
|
+
return isSameOrDescendant(resolveProtectedCandidate(scope, projectPath), workspace);
|
|
436
|
+
}
|
|
437
|
+
if (toolName === 'Glob') {
|
|
438
|
+
const baseValue = typeof input.path === 'string' && input.path ? input.path : '.';
|
|
439
|
+
const base = resolveProtectedCandidate(baseValue, projectPath);
|
|
440
|
+
if (!isSameOrDescendant(base, workspace))
|
|
441
|
+
return false;
|
|
442
|
+
const scope = globReadScope(input, { cwd: projectPath });
|
|
443
|
+
return isSameOrDescendant(scope, workspace);
|
|
444
|
+
}
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
function readonlyShellStaysInWorkspace(analysis, projectPath) {
|
|
448
|
+
if (analysis.kind !== 'proven-readonly')
|
|
449
|
+
return false;
|
|
450
|
+
const workspace = resolveProtectedCandidate(projectPath);
|
|
451
|
+
return analysis.ir.pathOperands.every(operand => isSameOrDescendant(resolveProtectedCandidate(operand.value, projectPath), workspace));
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* 只读模式检查(用于 PreToolUse hook 和 canUseTool callback)。普通
|
|
455
|
+
* 路径上的受限查询 Bash 可自动执行;H/L-class 和未建模 Shell 仍拒绝。
|
|
339
456
|
*/
|
|
340
457
|
export function checkReadonly(toolName, input, projectPath, context) {
|
|
341
458
|
const readOnlyTools = new Set([
|
|
342
459
|
'Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch',
|
|
343
460
|
'TaskList', 'TaskGet', 'ToolSearch',
|
|
344
461
|
]);
|
|
345
|
-
if (readOnlyTools.has(toolName))
|
|
462
|
+
if (readOnlyTools.has(toolName)) {
|
|
463
|
+
const paths = protectedReadToolPaths(toolName, input);
|
|
464
|
+
const pathOptions = { cwd: projectPath, root: context?.root };
|
|
465
|
+
if (!protectedReadToolStaysInWorkspace(toolName, input, projectPath)) {
|
|
466
|
+
return { behavior: 'deny', message: '🔒 只读模式:文件读取范围不能越出当前项目目录' };
|
|
467
|
+
}
|
|
468
|
+
if (paths.some(filePath => containsHClassReference(filePath) || isHClassPath(filePath, pathOptions))) {
|
|
469
|
+
return { behavior: 'deny', message: '🔒 只读模式:不允许读取 H 类受保护路径' };
|
|
470
|
+
}
|
|
471
|
+
if (protectedReadToolMatchesHClass(toolName, input, pathOptions)) {
|
|
472
|
+
return { behavior: 'deny', message: '🔒 只读模式:不允许读取 H 类受保护路径' };
|
|
473
|
+
}
|
|
474
|
+
if (protectedReadToolMatchesLClass(toolName, input, pathOptions)) {
|
|
475
|
+
return { behavior: 'deny', message: '🔒 只读模式:visitor 最低权限不允许读取 L-class 路径' };
|
|
476
|
+
}
|
|
346
477
|
return { behavior: 'allow' };
|
|
478
|
+
}
|
|
347
479
|
if (toolName === 'Write' || toolName === 'Edit' || toolName === 'NotebookEdit') {
|
|
348
480
|
const filePath = (input.file_path || input.notebook_path);
|
|
349
481
|
logger.warn(`[ReadonlyCheck] 🔒 File write blocked: tool=${toolName} path=${filePath} project=${projectPath} session=${context?.sessionId} channel=${context?.channel} peer=${context?.peerId} role=${context?.role}`);
|
|
@@ -351,25 +483,27 @@ export function checkReadonly(toolName, input, projectPath, context) {
|
|
|
351
483
|
}
|
|
352
484
|
if (toolName === 'Bash') {
|
|
353
485
|
const cmd = input.command || '';
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
|
|
486
|
+
const protectedOperands = analyzeShellProtectedOperands(cmd, { cwd: projectPath, root: context?.root });
|
|
487
|
+
if (protectedOperands.analysis.kind === 'proven-readonly'
|
|
488
|
+
&& !protectedOperands.hClass
|
|
489
|
+
&& !protectedOperands.lClass
|
|
490
|
+
&& readonlyShellStaysInWorkspace(protectedOperands.analysis, projectPath)) {
|
|
491
|
+
return { behavior: 'allow' };
|
|
492
|
+
}
|
|
493
|
+
if (protectedOperands.hClass) {
|
|
494
|
+
logger.warn(`[ReadonlyCheck] 🔒 H-class path blocked in readonly shell: cmd="${cmd}" session=${context?.sessionId} channel=${context?.channel} peer=${context?.peerId} role=${context?.role}`);
|
|
495
|
+
return { behavior: 'deny', message: '🔒 只读模式:不允许读取 H 类受保护路径' };
|
|
496
|
+
}
|
|
497
|
+
if (protectedOperands.lClass) {
|
|
498
|
+
logger.warn(`[ReadonlyCheck] 🔒 L-class path blocked in readonly shell: cmd="${cmd}" session=${context?.sessionId} channel=${context?.channel} peer=${context?.peerId} role=${context?.role}`);
|
|
499
|
+
return { behavior: 'deny', message: '🔒 只读模式:visitor 最低权限不允许读取 L-class 路径' };
|
|
500
|
+
}
|
|
501
|
+
if (protectedOperands.analysis.kind === 'proven-readonly') {
|
|
502
|
+
return { behavior: 'deny', message: '🔒 只读模式:Shell 查询范围不能越出当前项目目录' };
|
|
369
503
|
}
|
|
370
504
|
const cmdPreview = cmd.length > 80 ? cmd.substring(0, 80) + '...' : cmd;
|
|
371
505
|
logger.warn(`[ReadonlyCheck] 🔒 Bash blocked by default: cmd="${cmdPreview}" session=${context?.sessionId} channel=${context?.channel} peer=${context?.peerId} role=${context?.role}`);
|
|
372
|
-
return { behavior: 'deny', message: '🔒 只读模式:Shell
|
|
506
|
+
return { behavior: 'deny', message: '🔒 只读模式:Shell 仅允许受限、可证明只读的普通路径查询' };
|
|
373
507
|
}
|
|
374
508
|
logger.warn(`[ReadonlyCheck] 🔒 Unknown tool blocked: tool=${toolName} session=${context?.sessionId} channel=${context?.channel} peer=${context?.peerId} role=${context?.role}`);
|
|
375
509
|
return { behavior: 'deny', message: `🔒 只读模式:未声明为只读的工具 ${toolName} 已拒绝执行` };
|
|
@@ -577,53 +711,6 @@ function collectFileChangePaths(value, output) {
|
|
|
577
711
|
}
|
|
578
712
|
}
|
|
579
713
|
}
|
|
580
|
-
function shellCommandReferencesLClass(command, options) {
|
|
581
|
-
const normalized = command.replace(/\\/g, '/');
|
|
582
|
-
if (containsLClassReference(normalized))
|
|
583
|
-
return true;
|
|
584
|
-
const argv = parseLiteralShellArgv(command);
|
|
585
|
-
return argv?.slice(1).some(argument => {
|
|
586
|
-
if (argument === '-' || argument === '--' || argument.startsWith('-'))
|
|
587
|
-
return false;
|
|
588
|
-
return isLClassPath(argument, options);
|
|
589
|
-
}) ?? false;
|
|
590
|
-
}
|
|
591
|
-
function isVerifiedLClassReadOnlyShellCommand(command) {
|
|
592
|
-
const argv = parseLiteralShellArgv(command);
|
|
593
|
-
if (!argv || !L_CLASS_READONLY_SHELL_EXECUTABLES.has(argv[0]))
|
|
594
|
-
return false;
|
|
595
|
-
switch (argv[0].replace(/^.*\//, '')) {
|
|
596
|
-
case 'grep':
|
|
597
|
-
// grep never mutates its operands. Shell composition is rejected by the
|
|
598
|
-
// literal parser before this point.
|
|
599
|
-
return true;
|
|
600
|
-
case 'rg':
|
|
601
|
-
// ripgrep's --pre hooks may execute an arbitrary process.
|
|
602
|
-
return !argv.slice(1).some(argument => RG_PROCESS_SPAWNING_OPTION_RE.test(argument));
|
|
603
|
-
case 'find':
|
|
604
|
-
// Keep only the predicates/actions which cannot create, mutate, or
|
|
605
|
-
// execute files. Other find options remain fail-closed.
|
|
606
|
-
return !argv.slice(1).some(argument => FIND_MUTATING_PRIMARY_RE.test(argument));
|
|
607
|
-
case 'sed': {
|
|
608
|
-
// sed can write via -i or a `w` command. Permit only the bounded
|
|
609
|
-
// inspection form used for line-range reads: sed -n 'START[,END]p'.
|
|
610
|
-
const quiet = argv[1] === '-n' || argv[1] === '--quiet' || argv[1] === '--silent';
|
|
611
|
-
const program = quiet ? argv[2] : undefined;
|
|
612
|
-
return !!program
|
|
613
|
-
&& /^(?:\d+(?:,\d+)?|\$)?p$/.test(program)
|
|
614
|
-
// GNU sed accepts options after the script as well. Reject every
|
|
615
|
-
// later option so -i/-e/-f cannot be smuggled into this read form.
|
|
616
|
-
&& !argv.slice(3).some(argument => argument.startsWith('-'));
|
|
617
|
-
}
|
|
618
|
-
default:
|
|
619
|
-
return true;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
function isUnverifiedLClassShellCommand(command, options) {
|
|
623
|
-
if (!shellCommandReferencesLClass(command, options))
|
|
624
|
-
return false;
|
|
625
|
-
return !isVerifiedLClassReadOnlyShellCommand(command);
|
|
626
|
-
}
|
|
627
714
|
function requestsFilesystemRoot(value) {
|
|
628
715
|
if (Array.isArray(value))
|
|
629
716
|
return value.some(requestsFilesystemRoot);
|
|
@@ -647,8 +734,11 @@ export function checkHClassWrite(toolName, input, context) {
|
|
|
647
734
|
const paths = [];
|
|
648
735
|
const grantPaths = [];
|
|
649
736
|
const projectRootGrantSubpaths = [];
|
|
650
|
-
if (['Read', '
|
|
651
|
-
|
|
737
|
+
if (['Read', 'Glob', 'Grep'].includes(toolName)) {
|
|
738
|
+
paths.push(...protectedReadToolPaths(toolName, input));
|
|
739
|
+
}
|
|
740
|
+
else if (['Write', 'Edit', 'NotebookEdit'].includes(toolName)) {
|
|
741
|
+
const filePath = input.file_path ?? input.notebook_path ?? input.path;
|
|
652
742
|
if (typeof filePath === 'string' && filePath)
|
|
653
743
|
paths.push(filePath);
|
|
654
744
|
}
|
|
@@ -661,9 +751,13 @@ export function checkHClassWrite(toolName, input, context) {
|
|
|
661
751
|
collectFilesystemGrantPaths(input.permissions, grantPaths, projectRootGrantSubpaths);
|
|
662
752
|
}
|
|
663
753
|
else if (toolName === 'Bash') {
|
|
664
|
-
const command = typeof input.command === 'string' ? input.command
|
|
665
|
-
|
|
666
|
-
|
|
754
|
+
const command = typeof input.command === 'string' ? input.command : '';
|
|
755
|
+
const protectedOperands = analyzeShellProtectedOperands(command, {
|
|
756
|
+
cwd: context?.projectPath,
|
|
757
|
+
root: context?.root,
|
|
758
|
+
});
|
|
759
|
+
if (protectedOperands.hClass) {
|
|
760
|
+
logger.warn(`[H-Class Protection] 🔒 Protected filesystem operand in shell command: tool=${toolName} ` +
|
|
667
761
|
`session=${context?.sessionId} channel=${context?.channel} peer=${context?.peerId} role=${context?.role}`);
|
|
668
762
|
return {
|
|
669
763
|
behavior: 'deny',
|
|
@@ -686,6 +780,15 @@ export function checkHClassWrite(toolName, input, context) {
|
|
|
686
780
|
// filesystem operation is checked again when it reaches Read/Write/Bash,
|
|
687
781
|
// FileChange, or PermissionGrant above.
|
|
688
782
|
const pathOptions = { cwd: context?.projectPath, root: context?.root };
|
|
783
|
+
if (['Read', 'Glob', 'Grep'].includes(toolName)
|
|
784
|
+
&& protectedReadToolMatchesHClass(toolName, input, pathOptions)) {
|
|
785
|
+
logger.warn(`[H-Class Protection] 🔒 Protected filesystem read scope blocked: tool=${toolName} ` +
|
|
786
|
+
`session=${context?.sessionId} channel=${context?.channel} peer=${context?.peerId} role=${context?.role}`);
|
|
787
|
+
return {
|
|
788
|
+
behavior: 'deny',
|
|
789
|
+
message: '🔒 文件读取范围包含受保护的 H 类配置/证书/快照路径,agent 不可直接操作',
|
|
790
|
+
};
|
|
791
|
+
}
|
|
689
792
|
if (requestsFilesystemRoot(input.permissions) || requestsFilesystemRoot(input.additionalPermissions)) {
|
|
690
793
|
logger.warn(`[H-Class Protection] 🔒 Permission grant covers filesystem root: tool=${toolName} ` +
|
|
691
794
|
`session=${context?.sessionId} channel=${context?.channel} peer=${context?.peerId} role=${context?.role}`);
|
|
@@ -771,8 +874,14 @@ export function checkHClassWrite(toolName, input, context) {
|
|
|
771
874
|
* operations may mutate, delete, move, replace, or grant write access to them.
|
|
772
875
|
*/
|
|
773
876
|
export function checkLClassWrite(toolName, input, context) {
|
|
774
|
-
if (['Read', 'Glob', 'Grep'].includes(toolName))
|
|
877
|
+
if (['Read', 'Glob', 'Grep'].includes(toolName)) {
|
|
878
|
+
const pathOptions = { cwd: context?.projectPath, root: context?.root };
|
|
879
|
+
const lClassRead = protectedReadToolMatchesLClass(toolName, input, pathOptions);
|
|
880
|
+
if (lClassRead && context?.permissionMode === 'readonly') {
|
|
881
|
+
return { behavior: 'deny', message: '🔒 只读模式:visitor 最低权限不允许读取 L-class 路径' };
|
|
882
|
+
}
|
|
775
883
|
return { behavior: 'allow' };
|
|
884
|
+
}
|
|
776
885
|
const paths = [];
|
|
777
886
|
const writeGrantPaths = [];
|
|
778
887
|
const projectRootWriteSubpaths = [];
|
|
@@ -794,14 +903,19 @@ export function checkLClassWrite(toolName, input, context) {
|
|
|
794
903
|
collectFilesystemWriteGrantPaths(input.permissions, writeGrantPaths, projectRootWriteSubpaths);
|
|
795
904
|
}
|
|
796
905
|
else if (toolName === 'Bash') {
|
|
797
|
-
const command = typeof input.command === 'string' ? input.command
|
|
906
|
+
const command = typeof input.command === 'string' ? input.command : '';
|
|
798
907
|
const pathOptions = { cwd: context?.projectPath, root: context?.root };
|
|
799
|
-
|
|
800
|
-
|
|
908
|
+
const protectedOperands = analyzeShellProtectedOperands(command, pathOptions);
|
|
909
|
+
const lClassDenied = protectedOperands.lClass
|
|
910
|
+
&& (context?.permissionMode === 'readonly' || protectedOperands.analysis.kind === 'unproven');
|
|
911
|
+
if (lClassDenied) {
|
|
912
|
+
logger.warn(`[L-Class Protection] L-class shell access denied: tool=${toolName} ` +
|
|
801
913
|
`session=${context?.sessionId} channel=${context?.channel} peer=${context?.peerId} role=${context?.role}`);
|
|
802
914
|
return {
|
|
803
915
|
behavior: 'deny',
|
|
804
|
-
message:
|
|
916
|
+
message: context?.permissionMode === 'readonly'
|
|
917
|
+
? '🔒 只读模式:visitor 最低权限不允许读取 L-class 路径'
|
|
918
|
+
: '🔒 L-class 路径仅允许受限、可证明只读的命令;未知 Shell 语法失败关闭。修改必须通过受控的 EvolCore 命令',
|
|
805
919
|
};
|
|
806
920
|
}
|
|
807
921
|
collectFilesystemWriteGrantPaths(input.additionalPermissions, writeGrantPaths, projectRootWriteSubpaths);
|
|
@@ -957,7 +1071,57 @@ function prepareBoundedOutputInput(input, command, kind, context) {
|
|
|
957
1071
|
export function evaluateToolPreflight(toolName, input, context) {
|
|
958
1072
|
if (toolName === 'Bash') {
|
|
959
1073
|
const command = typeof input.command === 'string' ? input.command : '';
|
|
960
|
-
const
|
|
1074
|
+
const explicitCommandArgv = Array.isArray(input.commandArgv)
|
|
1075
|
+
&& input.commandArgv.every(value => typeof value === 'string')
|
|
1076
|
+
? input.commandArgv
|
|
1077
|
+
: undefined;
|
|
1078
|
+
const outerArgv = explicitCommandArgv ?? parseLiteralShellArgv(command) ?? undefined;
|
|
1079
|
+
const wrappedCommand = outerArgv ? unwrapCodexShellCommandArgv(outerArgv) : undefined;
|
|
1080
|
+
const policyCommand = wrappedCommand ?? command;
|
|
1081
|
+
if (context.sessionId && /^\s*(?:command\s+-v|which|type)\s+ec\s*$/i.test(policyCommand)) {
|
|
1082
|
+
return {
|
|
1083
|
+
behavior: 'deny',
|
|
1084
|
+
input,
|
|
1085
|
+
message: '🔒 EvolCore 托管会话不需要探测 `ec` 是否存在;请不要调用 `command -v`/`which`/`type`,也不要拼接其它命令。通信必须走当前 session 的 delegated sender。',
|
|
1086
|
+
policyCode: 'managed_ec_discovery_forbidden',
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
const argv = context.sessionId ? parseCodexToolCommandArgv(input) : null;
|
|
1090
|
+
const managedAidArgv = argv?.at(-2) === '--format' && argv.at(-1) === 'json'
|
|
1091
|
+
? argv.slice(0, -2)
|
|
1092
|
+
: argv;
|
|
1093
|
+
const ownAid = !!managedAidArgv
|
|
1094
|
+
&& !!context.selfAid
|
|
1095
|
+
&& managedAidArgv.at(-1)?.replace(/^@/, '') === context.selfAid.replace(/^@/, '');
|
|
1096
|
+
const isSelfManagedAidRead = !!managedAidArgv
|
|
1097
|
+
&& managedAidArgv[0] === 'ec'
|
|
1098
|
+
&& managedAidArgv[1] === 'aid'
|
|
1099
|
+
&& ownAid
|
|
1100
|
+
&& ((managedAidArgv.length === 4 && ['show', 'lookup'].includes(managedAidArgv[2]))
|
|
1101
|
+
|| (managedAidArgv.length === 5
|
|
1102
|
+
&& managedAidArgv[2] === 'agentmd'
|
|
1103
|
+
&& managedAidArgv[3] === 'get'));
|
|
1104
|
+
const isSelfAgentMdPublish = !!managedAidArgv
|
|
1105
|
+
&& managedAidArgv.length === 5
|
|
1106
|
+
&& managedAidArgv[0] === 'ec'
|
|
1107
|
+
&& managedAidArgv[1] === 'aid'
|
|
1108
|
+
&& managedAidArgv[2] === 'agentmd'
|
|
1109
|
+
&& managedAidArgv[3] === 'put'
|
|
1110
|
+
&& ownAid;
|
|
1111
|
+
const canPublishSelfAgentMd = isSelfAgentMdPublish
|
|
1112
|
+
&& ['request', 'bypass'].includes(context.permissionMode ?? '');
|
|
1113
|
+
const isAllowedManagedAidCommand = isSelfManagedAidRead || canPublishSelfAgentMd;
|
|
1114
|
+
if (context.sessionId && /^\s*ec\s+aid(?:\s|$)/i.test(policyCommand) && !isAllowedManagedAidCommand) {
|
|
1115
|
+
return {
|
|
1116
|
+
behavior: 'deny',
|
|
1117
|
+
input,
|
|
1118
|
+
message: isSelfAgentMdPublish && (context.permissionMode === 'readonly' || context.permissionMode === 'auto')
|
|
1119
|
+
? `🔒 ${context.permissionMode} 模式禁止发布 agent.md;请切换到 request 或 bypass 后再执行 \`ec aid agentmd put <当前 self AID>\`。`
|
|
1120
|
+
: '🔒 EvolCore 托管会话仅允许针对当前 self AID 精确调用 `ec aid show`、`ec aid lookup` 或 `ec aid agentmd get`;`ec aid agentmd put` 还要求 request 或 bypass。可选后缀仅限 `--format json`。禁止枚举、切换或操作其他身份。',
|
|
1121
|
+
policyCode: 'managed_ec_aid_scope_forbidden',
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
const ecCommand = classifyEvolcoreShellCommand(policyCommand);
|
|
961
1125
|
if (ecCommand.kind === 'literal') {
|
|
962
1126
|
return { behavior: 'allow', input, reason: 'ec-command' };
|
|
963
1127
|
}
|
|
@@ -965,25 +1129,40 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
965
1129
|
return prepareBoundedOutputInput(input, ecCommand.command, 'ec', context);
|
|
966
1130
|
}
|
|
967
1131
|
if (ecCommand.kind === 'composite') {
|
|
968
|
-
|
|
1132
|
+
// Older app-server approval callbacks may provide only the rendered
|
|
1133
|
+
// `/bin/bash -lc ...` command string. A composite inside that carrier
|
|
1134
|
+
// is not eligible for a delegation ticket, but owner bypass still
|
|
1135
|
+
// handles it as an ordinary shell request. Structured `commandArgv`
|
|
1136
|
+
// callbacks remain fail-closed below because their command boundary is
|
|
1137
|
+
// authoritative.
|
|
1138
|
+
const legacyRenderedCarrier = !explicitCommandArgv && wrappedCommand !== undefined;
|
|
1139
|
+
const allowLegacyBypassComposition = legacyRenderedCarrier
|
|
1140
|
+
&& context.permissionMode === 'bypass'
|
|
1141
|
+
&& ecCommand.issue === 'shell-composition';
|
|
1142
|
+
if (!allowLegacyBypassComposition) {
|
|
1143
|
+
if (ecCommand.issue === 'unsafe-expansion') {
|
|
1144
|
+
return {
|
|
1145
|
+
behavior: 'deny',
|
|
1146
|
+
input,
|
|
1147
|
+
message: '🔒 EC 双引号正文包含未转义的 Shell 展开;请将反引号写成 \\`,将 $()、${}、$变量中的 $ 写成 \\$。Shell 传给 ec 时会还原为原文字面量',
|
|
1148
|
+
policyCode: 'ec_shell_unsafe_expansion',
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
if (ecCommand.issue === 'invalid-quote') {
|
|
1152
|
+
return {
|
|
1153
|
+
behavior: 'deny',
|
|
1154
|
+
input,
|
|
1155
|
+
message: '🔒 EC 命令引号未闭合或存在错误嵌套;正文中的双引号请写成 \\"',
|
|
1156
|
+
policyCode: 'ec_shell_invalid_quote',
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
969
1159
|
return {
|
|
970
1160
|
behavior: 'deny',
|
|
971
1161
|
input,
|
|
972
|
-
message: '🔒 EC
|
|
1162
|
+
message: '🔒 EC 命令调用被拒绝:一次 Bash/exec_command 只能执行一条完整的 `ec` 命令。请直接调用 `ec ...`,不要先用 `command -v`/`ec aid` 探测,也不要使用 `&&`、`||`、`;`、`|`、重定向、变量展开、子 shell、`sh -c` 或 `bash -lc` 拼接其它命令。',
|
|
1163
|
+
policyCode: 'ec_shell_composite_command',
|
|
973
1164
|
};
|
|
974
1165
|
}
|
|
975
|
-
if (ecCommand.issue === 'invalid-quote') {
|
|
976
|
-
return {
|
|
977
|
-
behavior: 'deny',
|
|
978
|
-
input,
|
|
979
|
-
message: '🔒 EC 命令引号未闭合或存在错误嵌套;正文中的双引号请写成 \\"',
|
|
980
|
-
};
|
|
981
|
-
}
|
|
982
|
-
return {
|
|
983
|
-
behavior: 'deny',
|
|
984
|
-
input,
|
|
985
|
-
message: '🔒 EC 命令必须作为单个字面量进程调用;请移除管道、重定向、变量展开或命令拼接',
|
|
986
|
-
};
|
|
987
1166
|
}
|
|
988
1167
|
const boundedOutput = parseBoundedOutputShellCommand(command);
|
|
989
1168
|
const hostProcessCommand = boundedOutput
|
|
@@ -1000,7 +1179,7 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
1000
1179
|
}
|
|
1001
1180
|
const blacklist = checkBlacklist(toolName, input);
|
|
1002
1181
|
if (blacklist.behavior === 'deny') {
|
|
1003
|
-
return { behavior: 'deny', input, message: blacklist.message };
|
|
1182
|
+
return { behavior: 'deny', input, message: blacklist.message, policyCode: 'tool_blacklist' };
|
|
1004
1183
|
}
|
|
1005
1184
|
const checkedInput = blacklist.updatedInput;
|
|
1006
1185
|
const hClass = checkHClassWrite(toolName, checkedInput, {
|
|
@@ -1013,19 +1192,20 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
1013
1192
|
root: context.root,
|
|
1014
1193
|
});
|
|
1015
1194
|
if (hClass.behavior === 'deny') {
|
|
1016
|
-
return { behavior: 'deny', input: checkedInput, message: hClass.message };
|
|
1195
|
+
return { behavior: 'deny', input: checkedInput, message: hClass.message, policyCode: 'h_class_protection' };
|
|
1017
1196
|
}
|
|
1018
1197
|
const lClass = checkLClassWrite(toolName, checkedInput, {
|
|
1019
1198
|
sessionId: context.sessionId,
|
|
1020
1199
|
channel: context.channel,
|
|
1021
1200
|
peerId: context.userId,
|
|
1022
1201
|
role: context.role,
|
|
1202
|
+
permissionMode: context.permissionMode,
|
|
1023
1203
|
projectPath: context.projectPath,
|
|
1024
1204
|
workspacePath: context.workspacePath,
|
|
1025
1205
|
root: context.root,
|
|
1026
1206
|
});
|
|
1027
1207
|
if (lClass.behavior === 'deny') {
|
|
1028
|
-
return { behavior: 'deny', input: checkedInput, message: lClass.message };
|
|
1208
|
+
return { behavior: 'deny', input: checkedInput, message: lClass.message, policyCode: 'l_class_protection' };
|
|
1029
1209
|
}
|
|
1030
1210
|
const dangerous = checkDangerousCommand(toolName, checkedInput);
|
|
1031
1211
|
if (dangerous.isDangerous
|
|
@@ -1036,6 +1216,7 @@ export function evaluateToolPreflight(toolName, input, context) {
|
|
|
1036
1216
|
behavior: 'deny',
|
|
1037
1217
|
input: checkedInput,
|
|
1038
1218
|
message: '🔒 Git 破坏性操作仅允许 owner/admin 调用;当前角色无权执行',
|
|
1219
|
+
policyCode: 'role_git_destructive_forbidden',
|
|
1039
1220
|
};
|
|
1040
1221
|
}
|
|
1041
1222
|
return { behavior: 'continue', input: checkedInput };
|
|
@@ -11,7 +11,7 @@ const H_CLASS_RELATIVE_PATTERNS = [
|
|
|
11
11
|
/^backups\/config(?:\/|$)/,
|
|
12
12
|
/^\.snapshots(?:\/|$)/,
|
|
13
13
|
/^CA(?:\/|$)/,
|
|
14
|
-
/^(?:AIDs|aids)\/[^/]+\/(?:cert|keys)(?:\/|$)/,
|
|
14
|
+
/^(?:AIDs|aids)\/[^/]+\/(?:cert|keys|private)(?:\/|$)/,
|
|
15
15
|
/^\.device_id$/,
|
|
16
16
|
/^\.env$/,
|
|
17
17
|
/^\.seed(?:\.|$)/,
|
|
@@ -33,7 +33,7 @@ const H_CLASS_RELATIVE_PATTERNS = [
|
|
|
33
33
|
const H_CLASS_REFERENCE_PATTERNS = [
|
|
34
34
|
/(?:^|[^A-Za-z0-9_.-])daemon\.json(?=$|[^A-Za-z0-9_.-])/,
|
|
35
35
|
/(?:^|[^A-Za-z0-9_.-])agents[\\/](?:defaults\.json|[^/\\\s"']+[\\/](?:(?:config|contact)\.json|contact-operations\.jsonl|relations[\\/][^/\\\s"']+[\\/]config\.json))(?=$|[\s"';&|<>)},])/,
|
|
36
|
-
/(?:^|[^A-Za-z0-9_.-])(?:backups[\\/]config|\.snapshots|CA|(?:AIDs|aids)[\\/][^/\\\s"']+[\\/](?:cert|keys))(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
36
|
+
/(?:^|[^A-Za-z0-9_.-])(?:backups[\\/]config|\.snapshots|CA|(?:AIDs|aids)[\\/][^/\\\s"']+[\\/](?:cert|keys|private))(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
37
37
|
/(?:^|[/\\\s"'=<>])(?:\.device_id|\.env|\.seed(?:\.[^/\\\s"']*)?|\.migrated-[^/\\\s"']*|\.lock|daemon\.pid|data[\\/](?:causation-aun\.json(?:_|__)?|sessions|contact-book-audit\.jsonl|feishu-seen-[^/\\\s"']+\.jsonl|[a-z0-9_-]+-greeted-[^/\\\s"']+\.jsonl|message-queue\.json(?:_|__|\.tmp-[^/\\\s"']*)?|daemon[\\/](?:message-queue\.json(?:_|__|\.tmp-[^/\\\s"']*)?|control|migrations)|channel-state|instance[\\/]control\.token|outbox|handoff)|agents[\\/][^/\\\s"']+[\\/](?:sessions|data[\\/](?:channels|handoff|outbox\.jsonl|contact-audit\.jsonl)))(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
38
38
|
/(?:^|[/\\\s"'=<>])data[\\/]triggers(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
39
39
|
/(?:^|[/\\\s"'=<>])(?:[^/\\\s"']+\.json_|[^/\\\s"']+\.json\.migrated|defaults_\d+\.json)(?=$|[\s"';&|<>)},])/,
|
|
@@ -144,10 +144,14 @@ export function getHClassSandboxPatterns(root = resolveRoot()) {
|
|
|
144
144
|
path.join('AIDs', '*', 'cert', '**'),
|
|
145
145
|
path.join('AIDs', '*', 'keys'),
|
|
146
146
|
path.join('AIDs', '*', 'keys', '**'),
|
|
147
|
+
path.join('AIDs', '*', 'private'),
|
|
148
|
+
path.join('AIDs', '*', 'private', '**'),
|
|
147
149
|
path.join('aids', '*', 'cert'),
|
|
148
150
|
path.join('aids', '*', 'cert', '**'),
|
|
149
151
|
path.join('aids', '*', 'keys'),
|
|
150
152
|
path.join('aids', '*', 'keys', '**'),
|
|
153
|
+
path.join('aids', '*', 'private'),
|
|
154
|
+
path.join('aids', '*', 'private', '**'),
|
|
151
155
|
'.device_id',
|
|
152
156
|
'.env',
|
|
153
157
|
'.seed',
|
|
@@ -272,6 +276,7 @@ export function getExistingHClassMaskTargets(root = resolveRoot()) {
|
|
|
272
276
|
continue;
|
|
273
277
|
addExistingTarget(targets, path.join(aidsRoot, aid.name, 'cert'));
|
|
274
278
|
addExistingTarget(targets, path.join(aidsRoot, aid.name, 'keys'));
|
|
279
|
+
addExistingTarget(targets, path.join(aidsRoot, aid.name, 'private'));
|
|
275
280
|
}
|
|
276
281
|
}
|
|
277
282
|
catch { }
|
|
@@ -293,10 +298,11 @@ export function getExistingLClassReadOnlyTargets(root = resolveRoot()) {
|
|
|
293
298
|
catch { }
|
|
294
299
|
return [...targets.values()];
|
|
295
300
|
}
|
|
296
|
-
export function buildClaudeProtectedFilesystem(root = resolveRoot()) {
|
|
301
|
+
export function buildClaudeProtectedFilesystem(root = resolveRoot(), options = {}) {
|
|
297
302
|
const hClassPaths = getExistingHClassMaskTargets(root).map(target => target.path);
|
|
298
303
|
const lClassPaths = getExistingLClassReadOnlyTargets(root).map(target => target.path);
|
|
299
304
|
const allPaths = [...new Set([...hClassPaths, ...lClassPaths])];
|
|
305
|
+
const denyRead = options.denyLClassRead ? allPaths : hClassPaths;
|
|
300
306
|
if (allPaths.length > CLAUDE_PROTECTED_TARGET_LIMIT) {
|
|
301
307
|
throw new Error(`[ClaudeSandbox] protected target limit exceeded: ${allPaths.length} > ${CLAUDE_PROTECTED_TARGET_LIMIT}`);
|
|
302
308
|
}
|
|
@@ -309,7 +315,7 @@ export function buildClaudeProtectedFilesystem(root = resolveRoot()) {
|
|
|
309
315
|
}
|
|
310
316
|
}
|
|
311
317
|
return {
|
|
312
|
-
denyRead: [...new Set(
|
|
318
|
+
denyRead: [...new Set(denyRead)],
|
|
313
319
|
denyWrite: allPaths,
|
|
314
320
|
};
|
|
315
321
|
}
|
|
@@ -321,16 +327,18 @@ export function buildCodexHClassFilesystemRules(root = resolveRoot()) {
|
|
|
321
327
|
...Object.fromEntries(getHClassSandboxPatterns(root).map(pattern => [pattern, 'deny'])),
|
|
322
328
|
};
|
|
323
329
|
}
|
|
324
|
-
export function buildCodexProtectedFilesystemRules(root = resolveRoot()) {
|
|
330
|
+
export function buildCodexProtectedFilesystemRules(root = resolveRoot(), options = {}) {
|
|
325
331
|
const lClassRules = {};
|
|
332
|
+
const lClassAccess = options.denyLClassRead ? 'deny' : 'read';
|
|
326
333
|
for (const target of getExistingLClassReadOnlyTargets(root)) {
|
|
327
334
|
if (target.kind === 'directory') {
|
|
328
|
-
if (path.basename(target.path) === 'triggers')
|
|
329
|
-
lClassRules[target.path] =
|
|
330
|
-
|
|
335
|
+
if (options.denyLClassRead || path.basename(target.path) === 'triggers') {
|
|
336
|
+
lClassRules[target.path] = lClassAccess;
|
|
337
|
+
}
|
|
338
|
+
lClassRules[path.join(target.path, '**')] = lClassAccess;
|
|
331
339
|
}
|
|
332
340
|
else {
|
|
333
|
-
lClassRules[target.path] =
|
|
341
|
+
lClassRules[target.path] = lClassAccess;
|
|
334
342
|
}
|
|
335
343
|
}
|
|
336
344
|
return {
|