evolcore 0.0.20 → 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 +65 -0
- package/README.md +58 -9
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +3 -1
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +393 -108
- package/dist/agents/codex-app-server-client.js +41 -7
- package/dist/agents/codex-runner.js +1292 -220
- package/dist/agents/ecagent-runner.js +171 -61
- package/dist/agents/gemini-runner.js +130 -30
- package/dist/agents/request-identity.js +25 -0
- package/dist/agents/runner-types.js +19 -0
- package/dist/aun/aid/agentmd.js +59 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +72 -6
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +5 -0
- package/dist/aun/outbox.js +189 -80
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +618 -123
- package/dist/channels/daemon.js +6 -1
- package/dist/cli/agent-command.js +4 -3
- package/dist/cli/agent.js +66 -56
- package/dist/cli/aun-commands.js +177 -42
- package/dist/cli/command-log.js +10 -11
- package/dist/cli/contact.js +1 -0
- package/dist/cli/daemon-commands.js +98 -123
- package/dist/cli/init.js +27 -15
- package/dist/cli/task-context.js +50 -0
- package/dist/cli/trigger-command.js +14 -5
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +19 -5
- package/dist/config/config-manager.js +167 -22
- package/dist/config/contact-book-store.js +25 -3
- package/dist/config/contact-operation-service.js +32 -1
- package/dist/config/contact-request-service.js +44 -0
- package/dist/config/daemon-services.js +186 -0
- package/dist/config/gateway-config.js +20 -9
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +151 -9
- package/dist/core/agent-application-service.js +279 -0
- package/dist/core/audit/log-integrity.js +102 -0
- package/dist/core/auth/agent-delegation.js +43 -1
- package/dist/core/auth/auth-gateway.js +41 -4
- package/dist/core/auth/authorization-audit.js +216 -8
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +99 -27
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +311 -44
- package/dist/core/command/connect-menu.js +3 -4
- package/dist/core/command/group-menu.js +5 -7
- package/dist/core/command/menu-handler.js +288 -80
- package/dist/core/command/menu-protocol.js +1 -1
- package/dist/core/command/role-menu.js +21 -11
- package/dist/core/command/slash-gate.js +85 -18
- package/dist/core/command/slash-handler.js +377 -36
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +37 -0
- package/dist/core/evolagent.js +4 -0
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +33 -3
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +90 -87
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +184 -12
- package/dist/core/message/message-log.js +47 -7
- package/dist/core/message/message-queue.js +227 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +658 -109
- package/dist/core/message/send-receipt.js +1 -0
- package/dist/core/message/stream-debouncer.js +9 -2
- package/dist/core/model/model-catalog.js +23 -15
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/approval-gateway.js +180 -6
- package/dist/core/permission/ec-command-parser.js +627 -69
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +27 -14
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +159 -1
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +618 -23
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +329 -30
- package/dist/core/session/session-renew.js +37 -13
- package/dist/core/session/session-turn-coordinator.js +16 -5
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/index.js +316 -50
- package/dist/ipc.js +459 -29
- package/dist/paths.js +82 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +70 -7
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +13 -3
- package/dist/trigger/scheduler.js +20 -3
- package/dist/trigger/validation.js +6 -1
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/ecweb-utils.js +16 -2
- package/dist/utils/error-utils.js +4 -1
- package/dist/utils/logger.js +30 -6
- package/dist/utils/process-tree-stats.js +24 -4
- package/dist/utils/process-tree-worker.js +31 -0
- package/dist/utils/project-path.js +1 -2
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +3 -3
- package/kits/docs/evolcore/agent-create.md +146 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/contact.md +7 -1
- 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/msg.md +16 -0
- 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 +7 -4
- package/kits/schemas/agent-config.schema.11.json +13 -0
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/schemas/daemon.schema.5.json +0 -1
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +15 -3
- package/kits/schemas/migrations/README.md +3 -1
- package/kits/schemas/relation-config.schema.8.json +13 -0
- package/kits/schemas/role-config.schema.1.json +1 -2
- package/kits/schemas/single-session.schema.3.json +32 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +2 -1
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +6 -3
- package/skills/eclink/SKILL.md +2 -0
- package/dist/config/aun-gateway-config.js +0 -2
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
export const PUBLIC_PERMISSION_MODES = ['readonly', 'auto', 'request', 'bypass'];
|
|
2
|
+
export const FULL_ACCESS_PERMISSION_MODE = 'fullaccess';
|
|
2
3
|
export function isPublicPermissionMode(value) {
|
|
3
4
|
return typeof value === 'string'
|
|
4
5
|
&& PUBLIC_PERMISSION_MODES.includes(value);
|
|
5
6
|
}
|
|
7
|
+
export function isExecutionPermissionMode(value) {
|
|
8
|
+
return value === FULL_ACCESS_PERMISSION_MODE || isPublicPermissionMode(value);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Normalize a trusted execution-call mode without exposing fullaccess as a
|
|
12
|
+
* role/session configuration value. Legacy persisted public values retain the
|
|
13
|
+
* same migration behavior as normalizePermissionMode().
|
|
14
|
+
*/
|
|
15
|
+
export function normalizeExecutionPermissionMode(value) {
|
|
16
|
+
if (value === FULL_ACCESS_PERMISSION_MODE)
|
|
17
|
+
return FULL_ACCESS_PERMISSION_MODE;
|
|
18
|
+
return normalizePermissionMode(value).mode;
|
|
19
|
+
}
|
|
6
20
|
/**
|
|
7
21
|
* Normalize persisted legacy values at the runtime boundary.
|
|
8
22
|
*
|
|
@@ -27,13 +41,14 @@ export function normalizePermissionMode(value) {
|
|
|
27
41
|
* owner running in bypass mode. Every other combination remains fail-closed.
|
|
28
42
|
*/
|
|
29
43
|
export function resolvePhaseOneExecutionSandbox(permissionMode, role) {
|
|
30
|
-
const normalizedMode =
|
|
44
|
+
const normalizedMode = normalizeExecutionPermissionMode(permissionMode);
|
|
31
45
|
const normalizedRole = role ?? undefined;
|
|
32
|
-
const
|
|
46
|
+
const fullAccess = normalizedMode === FULL_ACCESS_PERMISSION_MODE;
|
|
47
|
+
const sandboxOff = fullAccess || (normalizedMode === 'bypass' && normalizedRole === 'owner');
|
|
33
48
|
return {
|
|
34
49
|
permissionMode: normalizedMode,
|
|
35
50
|
role: normalizedRole,
|
|
36
51
|
state: sandboxOff ? 'off' : 'active',
|
|
37
|
-
reason: sandboxOff ? 'owner-bypass' : 'phase-one-required',
|
|
52
|
+
reason: fullAccess ? 'fullaccess' : sandboxOff ? 'owner-bypass' : 'phase-one-required',
|
|
38
53
|
};
|
|
39
54
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { resolveRoot } from '
|
|
3
|
+
import { resolveRoot } from '../../paths.js';
|
|
4
4
|
const H_CLASS_RELATIVE_PATTERNS = [
|
|
5
5
|
/^daemon\.json$/,
|
|
6
6
|
/^agents\/defaults\.json$/,
|
|
@@ -28,13 +28,13 @@ const H_CLASS_RELATIVE_PATTERNS = [
|
|
|
28
28
|
/^data\/outbox(?:\/|$)/,
|
|
29
29
|
/^data\/handoff(?:\/|$)/,
|
|
30
30
|
/^agents\/[^/]+\/sessions(?:\/|$)/,
|
|
31
|
-
/^agents\/[^/]+\/data\/(?:channels(?:\/|$)|handoff(?:\/|$)|outbox\.jsonl(?:_|__|\.tmp-.*)?$|contact-audit\.jsonl$)/,
|
|
31
|
+
/^agents\/[^/]+\/data\/(?:channels(?:\/|$)|handoff(?:\/|$)|(?:outbox|activity-outbox)\.jsonl(?:_|__|\.tmp-.*)?$|contact-audit\.jsonl$)/,
|
|
32
32
|
];
|
|
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
36
|
/(?:^|[^A-Za-z0-9_.-])(?:backups[\\/]config|\.snapshots|CA|(?:AIDs|aids)[\\/][^/\\\s"']+[\\/](?:cert|keys|private))(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
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"';&|<>)},])/,
|
|
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|activity-outbox)\.jsonl|contact-audit\.jsonl)))(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
38
38
|
/(?:^|[/\\\s"'=<>])data[\\/]triggers(?:[\\/]|$|[\s"';&|<>)},])/,
|
|
39
39
|
/(?:^|[/\\\s"'=<>])(?:[^/\\\s"']+\.json_|[^/\\\s"']+\.json\.migrated|defaults_\d+\.json)(?=$|[\s"';&|<>)},])/,
|
|
40
40
|
];
|
|
@@ -268,25 +268,26 @@ 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', '**'),
|
|
289
289
|
path.join('agents', '*', 'data', 'outbox.jsonl*'),
|
|
290
|
+
path.join('agents', '*', 'data', 'activity-outbox.jsonl*'),
|
|
290
291
|
path.join('agents', '*', 'data', 'contact-audit.jsonl'),
|
|
291
292
|
];
|
|
292
293
|
return entries.map(entry => path.join(absoluteRoot, entry));
|
|
@@ -295,8 +296,8 @@ export function getLClassWritePatterns(root = resolveRoot()) {
|
|
|
295
296
|
const absoluteRoot = resolveThroughExistingAncestor(root);
|
|
296
297
|
return [
|
|
297
298
|
path.join(absoluteRoot, 'agents', '*', 'triggers'),
|
|
298
|
-
path.join(absoluteRoot, 'agents', '*', 'triggers'
|
|
299
|
-
path.join(absoluteRoot, 'agents', '*', 'roles'
|
|
299
|
+
path.join(absoluteRoot, 'agents', '*', 'triggers') + '/**',
|
|
300
|
+
path.join(absoluteRoot, 'agents', '*', 'roles') + '/**',
|
|
300
301
|
];
|
|
301
302
|
}
|
|
302
303
|
const CLAUDE_PROTECTED_TARGET_LIMIT = 8192;
|
|
@@ -369,6 +370,7 @@ export function getExistingHClassMaskTargets(root = resolveRoot()) {
|
|
|
369
370
|
addExistingTarget(targets, path.join(agentRoot, 'data', 'channels'));
|
|
370
371
|
addExistingTarget(targets, path.join(agentRoot, 'data', 'handoff'));
|
|
371
372
|
addExistingTarget(targets, path.join(agentRoot, 'data', 'outbox.jsonl'));
|
|
373
|
+
addExistingTarget(targets, path.join(agentRoot, 'data', 'activity-outbox.jsonl'));
|
|
372
374
|
addExistingTarget(targets, path.join(agentRoot, 'data', 'contact-audit.jsonl'));
|
|
373
375
|
}
|
|
374
376
|
}
|
|
@@ -435,12 +437,23 @@ export function buildCodexHClassFilesystemRules(root = resolveRoot()) {
|
|
|
435
437
|
export function buildCodexProtectedFilesystemRules(root = resolveRoot(), options = {}) {
|
|
436
438
|
const lClassRules = {};
|
|
437
439
|
const lClassAccess = options.denyLClassRead ? 'deny' : 'read';
|
|
440
|
+
// Codex's Linux sandbox expands glob deny rules into per-file mount targets.
|
|
441
|
+
// When an outer H-class guard has already mounted the L-class directory
|
|
442
|
+
// read-only, that expansion can race with the mount and fail before the
|
|
443
|
+
// command starts (for example while creating triggers/history.jsonl).
|
|
444
|
+
// Directory-level deny is sufficient for the policy bridge and avoids the
|
|
445
|
+
// nested bwrap per-file projection. Callers on platforms without that
|
|
446
|
+
// outer guard retain the historical expansion by default.
|
|
447
|
+
const expandLClassReadDeny = options.expandLClassReadDeny ?? true;
|
|
438
448
|
for (const target of getExistingLClassReadOnlyTargets(root)) {
|
|
439
449
|
if (target.kind === 'directory') {
|
|
440
|
-
if (options.denyLClassRead || path.basename(target.path) === 'triggers') {
|
|
450
|
+
if (!expandLClassReadDeny || options.denyLClassRead || path.basename(target.path) === 'triggers') {
|
|
441
451
|
lClassRules[target.path] = lClassAccess;
|
|
442
452
|
}
|
|
443
|
-
|
|
453
|
+
if (expandLClassReadDeny || !options.denyLClassRead) {
|
|
454
|
+
// Use '/**' suffix instead of path.join to satisfy Codex's glob requirement
|
|
455
|
+
lClassRules[target.path + '/**'] = lClassAccess;
|
|
456
|
+
}
|
|
444
457
|
}
|
|
445
458
|
else {
|
|
446
459
|
lClassRules[target.path] = lClassAccess;
|
|
@@ -3,7 +3,8 @@ import { parseLiteralShellArgv } from './ec-command-parser.js';
|
|
|
3
3
|
const MAX_QUERY_LISTS = 8;
|
|
4
4
|
const MAX_PIPELINE_STAGES = 3;
|
|
5
5
|
const SUPPORTED_EXECUTABLES = new Set([
|
|
6
|
-
'echo', 'pwd', 'ls', 'cat', 'grep', 'rg', 'find', 'sed', 'head', 'sort', 'git', 'wc', 'stat', 'lstat',
|
|
6
|
+
'echo', 'pwd', 'ls', 'cat', 'grep', 'rg', 'find', 'sed', 'head', 'sort', 'git', 'wc', 'stat', 'lstat', 'awk',
|
|
7
|
+
'bash', 'sh',
|
|
7
8
|
]);
|
|
8
9
|
const FIND_MUTATING_PRIMARY_RE = /^-(?:delete|exec|execdir|ok|okdir|fprint|fprint0|fprintf|fls)$/;
|
|
9
10
|
const RG_PROCESS_SPAWNING_OPTION_RE = /^(?:--pre|--hostname-bin|--pre-glob|--search-zip)(?:=|$)/;
|
|
@@ -113,6 +114,22 @@ function exact(value) {
|
|
|
113
114
|
function recursive(value) {
|
|
114
115
|
return { value, access: 'recursive' };
|
|
115
116
|
}
|
|
117
|
+
function isLiteralGitPathspec(value) {
|
|
118
|
+
// Git pathspec magic and wildcards can expand a seemingly harmless token to
|
|
119
|
+
// protected files elsewhere in the repository. The readonly diagnostic
|
|
120
|
+
// capability accepts only a literal path or directory scope.
|
|
121
|
+
return !!value
|
|
122
|
+
&& !value.startsWith('-')
|
|
123
|
+
&& !value.startsWith(':')
|
|
124
|
+
&& !/[\0*?[\\]/.test(value);
|
|
125
|
+
}
|
|
126
|
+
function isLikelyGitPath(value) {
|
|
127
|
+
// Before `--`, Git accepts both revisions and pathspecs. Only classify
|
|
128
|
+
// tokens with an unmistakable path shape here; bare names such as `HEAD`
|
|
129
|
+
// and `main` remain revision selectors and do not become filesystem scope.
|
|
130
|
+
return isLiteralGitPathspec(value)
|
|
131
|
+
&& (value === '.' || value === '..' || value.includes('/') || value.startsWith('.') || /\.[A-Za-z0-9_-]+$/.test(value));
|
|
132
|
+
}
|
|
116
133
|
function parseOptionsAndPaths(args, valueOptions, recursiveOptions = new Set()) {
|
|
117
134
|
const paths = [];
|
|
118
135
|
let optionsEnded = false;
|
|
@@ -472,9 +489,10 @@ function analyzeSort(args) {
|
|
|
472
489
|
/**
|
|
473
490
|
* Git metadata queries do not expose file contents and their output paths are
|
|
474
491
|
* controlled by the repository, not by arbitrary shell operands. Keep this
|
|
475
|
-
* deliberately small: status
|
|
476
|
-
* used for workspace diagnostics. Mutating
|
|
477
|
-
* unproven so
|
|
492
|
+
* deliberately small: status, bounded diff summaries, pickaxe log queries,
|
|
493
|
+
* and blame are the patterns used for workspace diagnostics. Mutating
|
|
494
|
+
* subcommands and unrecognised options remain unproven so normal H/L checks
|
|
495
|
+
* still apply.
|
|
478
496
|
*/
|
|
479
497
|
function analyzeGit(args) {
|
|
480
498
|
if (args.length === 0)
|
|
@@ -502,6 +520,7 @@ function analyzeGit(args) {
|
|
|
502
520
|
}
|
|
503
521
|
if (subcommand === 'diff') {
|
|
504
522
|
let sawSummary = false;
|
|
523
|
+
let noIndex = false;
|
|
505
524
|
let optionsEnded = false;
|
|
506
525
|
const paths = [];
|
|
507
526
|
const allowedFlags = new Set([
|
|
@@ -518,17 +537,152 @@ function analyzeGit(args) {
|
|
|
518
537
|
if (arg === '--stat' || arg === '--shortstat' || arg === '--numstat'
|
|
519
538
|
|| arg === '--name-only' || arg === '--name-status')
|
|
520
539
|
sawSummary = true;
|
|
540
|
+
if (arg === '--no-index')
|
|
541
|
+
noIndex = true;
|
|
521
542
|
if (!allowedFlags.has(arg) && !/^[0-9a-f]{7,64}$/.test(arg))
|
|
522
543
|
return null;
|
|
523
544
|
continue;
|
|
524
545
|
}
|
|
525
|
-
//
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
546
|
+
// A Git pathspec may name a directory or wildcard scope, so model it as
|
|
547
|
+
// recursive even when the token itself is literal. This keeps `-- .`
|
|
548
|
+
// from bypassing nested protected paths.
|
|
549
|
+
if (optionsEnded) {
|
|
550
|
+
if (!isLiteralGitPathspec(arg))
|
|
551
|
+
return null;
|
|
552
|
+
paths.push(recursive(arg));
|
|
553
|
+
}
|
|
554
|
+
else if (noIndex) {
|
|
555
|
+
if (!isLiteralGitPathspec(arg))
|
|
556
|
+
return null;
|
|
557
|
+
// `--no-index` compares two filesystem paths directly, so both
|
|
558
|
+
// operands must participate in workspace/H-class checks.
|
|
559
|
+
paths.push(recursive(arg));
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
if (!/^[0-9A-Za-z._~^/:-]+$/.test(arg))
|
|
563
|
+
return null;
|
|
564
|
+
if (isLikelyGitPath(arg))
|
|
565
|
+
paths.push(recursive(arg));
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (!sawSummary)
|
|
569
|
+
return null;
|
|
570
|
+
if (noIndex && paths.length !== 2)
|
|
571
|
+
return null;
|
|
572
|
+
return paths;
|
|
573
|
+
}
|
|
574
|
+
if (subcommand === 'log') {
|
|
575
|
+
let optionsEnded = false;
|
|
576
|
+
let sawSearch = false;
|
|
577
|
+
const paths = [];
|
|
578
|
+
const valueOptions = new Set([
|
|
579
|
+
'-S', '-G', '--grep', '--author', '--committer', '--since', '--after', '--until', '--before',
|
|
580
|
+
'--format', '--pretty', '--max-count', '-n', '--skip', '--decorate-refs',
|
|
581
|
+
]);
|
|
582
|
+
const allowedFlags = new Set([
|
|
583
|
+
'--all', '--branches', '--tags', '--remotes', '--oneline', '--no-patch', '--no-decorate', '--decorate',
|
|
584
|
+
'--follow', '--pickaxe-all', '--pickaxe-regex', '--reverse', '--topo-order', '--date-order',
|
|
585
|
+
'--author-date-order', '--first-parent', '--no-merges', '--merges', '--stat', '--shortstat',
|
|
586
|
+
'--name-only', '--name-status', '--no-renames', '--no-ext-diff', '--no-textconv', '--no-color',
|
|
587
|
+
'--color=never',
|
|
588
|
+
]);
|
|
589
|
+
for (let index = 1; index < args.length; index++) {
|
|
590
|
+
const arg = args[index];
|
|
591
|
+
if (!optionsEnded && arg === '--') {
|
|
592
|
+
optionsEnded = true;
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (optionsEnded) {
|
|
596
|
+
if (!isLiteralGitPathspec(arg))
|
|
597
|
+
return null;
|
|
598
|
+
paths.push(recursive(arg));
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
if (arg.startsWith('-')) {
|
|
602
|
+
const option = arg.split('=', 1)[0];
|
|
603
|
+
if (arg === '-S' || arg === '-G' || /^-[SG].+/.test(arg)) {
|
|
604
|
+
sawSearch = true;
|
|
605
|
+
if (arg.length === 2) {
|
|
606
|
+
if (++index >= args.length)
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
if (valueOptions.has(option)) {
|
|
612
|
+
if (!arg.includes('=') && !arg.startsWith('-S') && !arg.startsWith('-G')) {
|
|
613
|
+
if (++index >= args.length)
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
if (!allowedFlags.has(arg) && !/^(?:[0-9A-Fa-f]{7,64}|HEAD(?:~[0-9]+)?|[A-Za-z0-9._/-]+\.\.\.?)$/.test(arg))
|
|
619
|
+
return null;
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
// Revisions before `--` are metadata selectors, never filesystem paths.
|
|
623
|
+
if (!/^[A-Za-z0-9._~^/:@+-]+$/.test(arg))
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
return sawSearch && paths.length > 0 ? paths : null;
|
|
627
|
+
}
|
|
628
|
+
if (subcommand === 'blame') {
|
|
629
|
+
let optionsEnded = false;
|
|
630
|
+
let sawPath = false;
|
|
631
|
+
const paths = [];
|
|
632
|
+
const valueOptions = new Set(['-L', '--abbrev', '--date', '--ignore-rev']);
|
|
633
|
+
const pathValueOptions = new Set(['--ignore-revs-file', '--contents']);
|
|
634
|
+
const allowedFlags = new Set([
|
|
635
|
+
'--porcelain', '--line-porcelain', '--incremental', '--root', '--show-email', '--show-number',
|
|
636
|
+
'--show-name', '--reverse', '--progress', '--score-debug', '--encoding', '--no-progress',
|
|
637
|
+
]);
|
|
638
|
+
for (let index = 1; index < args.length; index++) {
|
|
639
|
+
const arg = args[index];
|
|
640
|
+
if (!optionsEnded && arg === '--') {
|
|
641
|
+
optionsEnded = true;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (optionsEnded) {
|
|
645
|
+
if (!isLiteralGitPathspec(arg) || sawPath)
|
|
646
|
+
return null;
|
|
647
|
+
paths.push(exact(arg));
|
|
648
|
+
sawPath = true;
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (arg.startsWith('-')) {
|
|
652
|
+
const option = arg.split('=', 1)[0];
|
|
653
|
+
if (option === '-L' || option === '-C') {
|
|
654
|
+
// `-L` takes a line range; `-C` is a repeatable flag with an
|
|
655
|
+
// optional score and therefore must not consume the following path.
|
|
656
|
+
if (option === '-L' && !arg.includes('=') && arg.length === 2
|
|
657
|
+
&& ++index >= args.length)
|
|
658
|
+
return null;
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (pathValueOptions.has(option)) {
|
|
662
|
+
const value = arg.includes('=') ? arg.slice(arg.indexOf('=') + 1) : args[++index];
|
|
663
|
+
if (!value)
|
|
664
|
+
return null;
|
|
665
|
+
paths.push(exact(value));
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
if (valueOptions.has(option)) {
|
|
669
|
+
if (!arg.includes('=') && ++index >= args.length)
|
|
670
|
+
return null;
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
if (!allowedFlags.has(arg))
|
|
674
|
+
return null;
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
// The common diagnostic form is `git blame <path>`. Keep one literal
|
|
678
|
+
// path operand; callers that need a revision should use `-- <path>` so
|
|
679
|
+
// the boundary is unambiguous to the policy parser.
|
|
680
|
+
if (sawPath || !isLiteralGitPathspec(arg))
|
|
529
681
|
return null;
|
|
682
|
+
paths.push(exact(arg));
|
|
683
|
+
sawPath = true;
|
|
530
684
|
}
|
|
531
|
-
return
|
|
685
|
+
return paths.length === 1 ? paths : null;
|
|
532
686
|
}
|
|
533
687
|
return null;
|
|
534
688
|
}
|
|
@@ -574,6 +728,99 @@ function analyzeStat(args) {
|
|
|
574
728
|
}
|
|
575
729
|
return paths.length > 0 ? paths : null;
|
|
576
730
|
}
|
|
731
|
+
function analyzeAwk(args) {
|
|
732
|
+
let index = 0;
|
|
733
|
+
while (index < args.length && args[index].startsWith('-')) {
|
|
734
|
+
const option = args[index];
|
|
735
|
+
if (option === '--') {
|
|
736
|
+
index++;
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
if (option === '-F') {
|
|
740
|
+
if (++index >= args.length)
|
|
741
|
+
return null;
|
|
742
|
+
index++;
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
if (option.startsWith('-F') && !option.startsWith('--') && option.length > 2) {
|
|
746
|
+
index++;
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
if (option === '-v' || option === '--assign') {
|
|
750
|
+
if (++index >= args.length)
|
|
751
|
+
return null;
|
|
752
|
+
index++;
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (option.startsWith('-v') && !option.startsWith('--') && option.length > 2) {
|
|
756
|
+
index++;
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (option === '--field-separator') {
|
|
760
|
+
if (++index >= args.length)
|
|
761
|
+
return null;
|
|
762
|
+
index++;
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
if (option.startsWith('--field-separator=') || option.startsWith('--assign=')) {
|
|
766
|
+
index++;
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
const program = args[index++];
|
|
772
|
+
// Permit the common formatting/filtering form while excluding awk's
|
|
773
|
+
// execution, file-redirection and shell escape features.
|
|
774
|
+
if (!program || !/^\{[\s\S]*\}$/.test(program)
|
|
775
|
+
|| /\b(?:system|getline|close|nextfile)\b|(?:>>?|\|)|\b(?:delete|exit)\b/.test(program)
|
|
776
|
+
|| /(^|[^=!<>])=([^=]|$)/.test(program))
|
|
777
|
+
return null;
|
|
778
|
+
const paths = args.slice(index).filter(value => value !== '-').map(exact);
|
|
779
|
+
return paths;
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* `bash -n`/`sh -n` only parses a script and never executes it. Treat the
|
|
783
|
+
* script operands as exact reads, but keep every other shell option and
|
|
784
|
+
* nested command fail-closed. This is intentionally narrower than allowing
|
|
785
|
+
* arbitrary `bash -c`, which would re-introduce an opaque shell carrier.
|
|
786
|
+
*/
|
|
787
|
+
function analyzeShellSyntaxCheck(args) {
|
|
788
|
+
if (args.length < 2)
|
|
789
|
+
return null;
|
|
790
|
+
let index = 0;
|
|
791
|
+
let sawNoExec = false;
|
|
792
|
+
while (index < args.length && args[index].startsWith('-') && args[index] !== '-') {
|
|
793
|
+
const option = args[index++];
|
|
794
|
+
if (option === '--') {
|
|
795
|
+
// The increment above already consumed the delimiter; the following
|
|
796
|
+
// token is the script path, not another option.
|
|
797
|
+
break;
|
|
798
|
+
}
|
|
799
|
+
if (option === '-n' || option === '--noexec') {
|
|
800
|
+
sawNoExec = true;
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
// `-n` may be combined with other bash flags, but only when the whole
|
|
804
|
+
// cluster is made of harmless parser flags. Do not accept flags that can
|
|
805
|
+
// select an inline command, source files, or alter execution semantics.
|
|
806
|
+
if (/^-[A-Za-z]*n[A-Za-z]*$/.test(option)
|
|
807
|
+
&& !/[cCeEirsSx]/.test(option.replace(/^-/, ''))) {
|
|
808
|
+
sawNoExec = true;
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
if (!sawNoExec || index >= args.length)
|
|
814
|
+
return null;
|
|
815
|
+
const paths = [];
|
|
816
|
+
for (; index < args.length; index++) {
|
|
817
|
+
const value = args[index];
|
|
818
|
+
if (!value || value === '-' || value.startsWith('-'))
|
|
819
|
+
return null;
|
|
820
|
+
paths.push(exact(value));
|
|
821
|
+
}
|
|
822
|
+
return paths;
|
|
823
|
+
}
|
|
577
824
|
function analyzeCommand(argv) {
|
|
578
825
|
const executable = normalizeExecutable(argv[0]);
|
|
579
826
|
if (!executable)
|
|
@@ -623,6 +870,13 @@ function analyzeCommand(argv) {
|
|
|
623
870
|
pathOperands = analyzeStat(args);
|
|
624
871
|
accessKind = 'metadata';
|
|
625
872
|
break;
|
|
873
|
+
case 'awk':
|
|
874
|
+
pathOperands = analyzeAwk(args);
|
|
875
|
+
break;
|
|
876
|
+
case 'bash':
|
|
877
|
+
case 'sh':
|
|
878
|
+
pathOperands = analyzeShellSyntaxCheck(args);
|
|
879
|
+
break;
|
|
626
880
|
}
|
|
627
881
|
if (!pathOperands)
|
|
628
882
|
return null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { resolveCommandPath } from '../../utils/cross-platform.js';
|
|
4
|
-
import { getExistingHClassMaskTargets, getExistingLClassReadOnlyTargets, isSameOrDescendant, } from '
|
|
4
|
+
import { getExistingHClassMaskTargets, getExistingLClassReadOnlyTargets, isSameOrDescendant, } from './protected-paths.js';
|
|
5
5
|
import { resolveRoot } from '../../paths.js';
|
|
6
6
|
let cachedBubblewrapPath;
|
|
7
7
|
let sandboxInitializationCircuit;
|
|
@@ -9,6 +9,7 @@ let sandboxCircuitNoticeAt = 0;
|
|
|
9
9
|
export const SANDBOX_INITIALIZATION_FAILED = 'sandbox_initialization_failed';
|
|
10
10
|
export const SANDBOX_INITIALIZATION_COOLDOWN_MS = 5 * 60 * 1000;
|
|
11
11
|
export const SANDBOX_CIRCUIT_NOTICE_INTERVAL_MS = 60 * 1000;
|
|
12
|
+
const SANDBOX_DIAGNOSTIC_TARGET_LIMIT = 256;
|
|
12
13
|
/** Open a process-wide fail-closed circuit after a confirmed host bootstrap failure. */
|
|
13
14
|
export function recordSandboxInitializationFailure(error, stderr = [], now = Date.now()) {
|
|
14
15
|
const detail = createSandboxInitializationError(error, stderr).message.slice(0, 512);
|
|
@@ -218,6 +219,163 @@ function pushMaskedDirectory(args, candidate) {
|
|
|
218
219
|
return;
|
|
219
220
|
args.push('--tmpfs', candidate);
|
|
220
221
|
}
|
|
222
|
+
function decodeMountInfoPath(value) {
|
|
223
|
+
// mountinfo escapes whitespace, backslashes, and newlines as octal bytes.
|
|
224
|
+
return value.replace(/\\([0-7]{3})/g, (_match, octal) => {
|
|
225
|
+
const code = Number.parseInt(octal, 8);
|
|
226
|
+
return Number.isNaN(code) ? _match : String.fromCharCode(code);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
function readMountInfoForPaths(paths, priorityPaths = []) {
|
|
230
|
+
if (process.platform !== 'linux' || paths.length === 0)
|
|
231
|
+
return [];
|
|
232
|
+
let contents;
|
|
233
|
+
try {
|
|
234
|
+
contents = fs.readFileSync('/proc/self/mountinfo', 'utf8');
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
return [`mountinfo unavailable: ${error instanceof Error ? error.message : String(error)}`];
|
|
238
|
+
}
|
|
239
|
+
const wanted = paths.map(candidate => path.resolve(candidate));
|
|
240
|
+
const priority = priorityPaths.map(candidate => path.resolve(candidate));
|
|
241
|
+
const matches = [];
|
|
242
|
+
for (const [order, line] of contents.split(/\r?\n/).filter(Boolean).entries()) {
|
|
243
|
+
const mountPointField = line.split(' - ', 1)[0]?.split(' ')[4];
|
|
244
|
+
if (!mountPointField)
|
|
245
|
+
continue;
|
|
246
|
+
const mountPoint = path.resolve(decodeMountInfoPath(mountPointField));
|
|
247
|
+
if (!wanted.some(target => isSameOrDescendant(target, mountPoint)
|
|
248
|
+
|| isSameOrDescendant(mountPoint, target)))
|
|
249
|
+
continue;
|
|
250
|
+
const isPriority = priority.some(target => target === mountPoint);
|
|
251
|
+
const isPriorityAncestor = !isPriority && priority.some(target => isSameOrDescendant(target, mountPoint));
|
|
252
|
+
matches.push({ line, score: isPriority ? 0 : isPriorityAncestor ? 1 : 2, order });
|
|
253
|
+
}
|
|
254
|
+
return matches
|
|
255
|
+
.sort((left, right) => left.score - right.score || left.order - right.order)
|
|
256
|
+
.slice(0, 32)
|
|
257
|
+
.map(match => match.line);
|
|
258
|
+
}
|
|
259
|
+
function snapshotSandboxPath(candidate) {
|
|
260
|
+
const resolved = path.resolve(candidate);
|
|
261
|
+
try {
|
|
262
|
+
const stat = fs.lstatSync(resolved);
|
|
263
|
+
let kind = 'other';
|
|
264
|
+
if (stat.isFile())
|
|
265
|
+
kind = 'file';
|
|
266
|
+
else if (stat.isDirectory())
|
|
267
|
+
kind = 'directory';
|
|
268
|
+
else if (stat.isSymbolicLink())
|
|
269
|
+
kind = 'symlink';
|
|
270
|
+
let realpath;
|
|
271
|
+
try {
|
|
272
|
+
realpath = fs.realpathSync(resolved);
|
|
273
|
+
}
|
|
274
|
+
catch { }
|
|
275
|
+
return {
|
|
276
|
+
path: resolved,
|
|
277
|
+
exists: true,
|
|
278
|
+
kind,
|
|
279
|
+
dev: stat.dev,
|
|
280
|
+
ino: stat.ino,
|
|
281
|
+
mode: stat.mode & 0o7777,
|
|
282
|
+
uid: stat.uid,
|
|
283
|
+
gid: stat.gid,
|
|
284
|
+
nlink: stat.nlink,
|
|
285
|
+
size: stat.size,
|
|
286
|
+
mtimeMs: stat.mtimeMs,
|
|
287
|
+
ctimeMs: stat.ctimeMs,
|
|
288
|
+
...(realpath ? { realpath } : {}),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
const code = error.code;
|
|
293
|
+
return {
|
|
294
|
+
path: resolved,
|
|
295
|
+
exists: false,
|
|
296
|
+
...(code ? { error: code } : { error: error instanceof Error ? error.message : String(error) }),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/** Capture the host-side state used to construct an H-class guard. */
|
|
301
|
+
export function collectSandboxGuardDiagnostics(root = resolveRoot()) {
|
|
302
|
+
const absoluteRoot = path.resolve(root);
|
|
303
|
+
const candidates = [
|
|
304
|
+
path.join(absoluteRoot, 'data', 'causation-aun.json'),
|
|
305
|
+
path.join(absoluteRoot, 'data', 'causation-aun.json_'),
|
|
306
|
+
path.join(absoluteRoot, 'data', 'causation-aun.json__'),
|
|
307
|
+
];
|
|
308
|
+
let targets = [];
|
|
309
|
+
try {
|
|
310
|
+
targets = getExistingHClassMaskTargets(absoluteRoot).map(target => target.path);
|
|
311
|
+
}
|
|
312
|
+
catch { }
|
|
313
|
+
// Keep the causation file and its atomic-write sidecars first. The full H
|
|
314
|
+
// target list can grow with the number of agents and must not turn every
|
|
315
|
+
// launch into an unbounded log payload.
|
|
316
|
+
const paths = [...new Set([...candidates, ...targets])];
|
|
317
|
+
const selectedPaths = paths.slice(0, SANDBOX_DIAGNOSTIC_TARGET_LIMIT);
|
|
318
|
+
return {
|
|
319
|
+
root: absoluteRoot,
|
|
320
|
+
targetCount: paths.length,
|
|
321
|
+
targetsTruncated: paths.length > selectedPaths.length,
|
|
322
|
+
targets: selectedPaths.map(snapshotSandboxPath),
|
|
323
|
+
mountInfo: readMountInfoForPaths(selectedPaths, candidates),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/** Reduce path and mount diagnostics to metadata suitable for INFO logs. */
|
|
327
|
+
export function summarizeSandboxGuardDiagnostics(diagnostics) {
|
|
328
|
+
let existingTargetCount = 0;
|
|
329
|
+
let missingTargetCount = 0;
|
|
330
|
+
const targetKindCounts = {};
|
|
331
|
+
for (const target of diagnostics.targets) {
|
|
332
|
+
if (!target.exists) {
|
|
333
|
+
missingTargetCount += 1;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
existingTargetCount += 1;
|
|
337
|
+
const kind = target.kind ?? 'unknown';
|
|
338
|
+
targetKindCounts[kind] = (targetKindCounts[kind] ?? 0) + 1;
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
root: diagnostics.root,
|
|
342
|
+
targetCount: diagnostics.targetCount,
|
|
343
|
+
targetsTruncated: diagnostics.targetsTruncated,
|
|
344
|
+
existingTargetCount,
|
|
345
|
+
missingTargetCount,
|
|
346
|
+
targetKindCounts,
|
|
347
|
+
mountInfoCount: diagnostics.mountInfo.length,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
/** Keep the exact mount portion of a bwrap argv while summarizing child argv. */
|
|
351
|
+
export function summarizeBubblewrapArgs(args) {
|
|
352
|
+
const separator = args.indexOf('--');
|
|
353
|
+
const mountArgs = [...args.slice(0, separator >= 0 ? separator : args.length)];
|
|
354
|
+
const child = separator >= 0 ? args.slice(separator + 1) : [];
|
|
355
|
+
return {
|
|
356
|
+
mountArgs,
|
|
357
|
+
...(child[0] ? { childCommand: child[0] } : {}),
|
|
358
|
+
childArgCount: Math.max(0, child.length - 1),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
/** Keep only argument counts and option names for routine INFO logs. */
|
|
362
|
+
export function summarizeBubblewrapArgsForLog(args) {
|
|
363
|
+
const separator = args.indexOf('--');
|
|
364
|
+
const mountArgs = args.slice(0, separator >= 0 ? separator : args.length);
|
|
365
|
+
const child = separator >= 0 ? args.slice(separator + 1) : [];
|
|
366
|
+
const mountOptionCounts = {};
|
|
367
|
+
for (const arg of mountArgs) {
|
|
368
|
+
if (!arg.startsWith('--'))
|
|
369
|
+
continue;
|
|
370
|
+
mountOptionCounts[arg] = (mountOptionCounts[arg] ?? 0) + 1;
|
|
371
|
+
}
|
|
372
|
+
return {
|
|
373
|
+
mountArgCount: mountArgs.length,
|
|
374
|
+
mountOptionCounts,
|
|
375
|
+
...(child[0] ? { childCommand: child[0] } : {}),
|
|
376
|
+
childArgCount: Math.max(0, child.length - 1),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
221
379
|
function appendRuntimeBackedEtcFiles(args) {
|
|
222
380
|
if (process.platform !== 'linux' || !fs.existsSync('/run'))
|
|
223
381
|
return;
|
|
@@ -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')
|