@guilz-dev/belay 0.3.0 → 0.6.0
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/README.md +7 -1
- package/dist/adapters/cursor/runtime-entry.js +1 -0
- package/dist/adapters/layouts/scope.js +3 -2
- package/dist/adapters/shared/gate-runtime.d.ts +12 -16
- package/dist/adapters/shared/gate-runtime.js +499 -43
- package/dist/bundle/claude-runtime.mjs +8004 -2676
- package/dist/bundle/codex-runtime.mjs +8021 -2683
- package/dist/bundle/cursor-runtime.mjs +7993 -2667
- package/dist/cli.js +180 -6
- package/dist/commands/approve.d.ts +1 -0
- package/dist/commands/approve.js +45 -26
- package/dist/commands/classify-for-report.js +1 -1
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.js +264 -58
- package/dist/commands/doctor.js +47 -2
- package/dist/commands/explain.js +17 -2
- package/dist/commands/harvest.d.ts +29 -0
- package/dist/commands/harvest.js +72 -0
- package/dist/commands/judge.d.ts +11 -1
- package/dist/commands/judge.js +24 -4
- package/dist/commands/metrics.js +34 -2
- package/dist/commands/quality.d.ts +38 -0
- package/dist/commands/quality.js +105 -0
- package/dist/commands/session.d.ts +20 -0
- package/dist/commands/session.js +51 -0
- package/dist/commands/simulate.d.ts +1 -0
- package/dist/commands/simulate.js +7 -2
- package/dist/commands/standing-allow.d.ts +10 -0
- package/dist/commands/standing-allow.js +26 -0
- package/dist/commands/tui.d.ts +48 -0
- package/dist/commands/tui.js +150 -0
- package/dist/config-io.js +59 -17
- package/dist/conformance/guarantee-posture.d.ts +21 -0
- package/dist/conformance/guarantee-posture.js +46 -0
- package/dist/conformance/guarantee-table.d.ts +1 -0
- package/dist/conformance/guarantee-table.js +14 -9
- package/dist/core/approval-replay-cli.d.ts +7 -0
- package/dist/core/approval-replay-cli.js +36 -0
- package/dist/core/approval-replay.d.ts +45 -0
- package/dist/core/approval-replay.js +141 -0
- package/dist/core/approval-service.d.ts +15 -0
- package/dist/core/approval-service.js +53 -4
- package/dist/core/approval.d.ts +26 -3
- package/dist/core/approval.js +61 -7
- package/dist/core/audit-analysis.d.ts +7 -1
- package/dist/core/audit-analysis.js +111 -0
- package/dist/core/audit-metrics.d.ts +7 -0
- package/dist/core/audit-metrics.js +24 -4
- package/dist/core/audit-query.d.ts +1 -0
- package/dist/core/audit-query.js +3 -0
- package/dist/core/audit-replay-context.d.ts +35 -0
- package/dist/core/audit-replay-context.js +88 -0
- package/dist/core/audit-types.d.ts +24 -1
- package/dist/core/audit-types.js +1 -1
- package/dist/core/capability/approval-state-mutation.d.ts +13 -0
- package/dist/core/capability/approval-state-mutation.js +92 -0
- package/dist/core/capability/approval-v3.d.ts +15 -0
- package/dist/core/capability/approval-v3.js +74 -0
- package/dist/core/capability/attestation.d.ts +13 -0
- package/dist/core/capability/attestation.js +55 -0
- package/dist/core/capability/boundary-attestation-sign.d.ts +21 -0
- package/dist/core/capability/boundary-attestation-sign.js +84 -0
- package/dist/core/capability/boundary-driver-container.d.ts +7 -0
- package/dist/core/capability/boundary-driver-container.js +122 -0
- package/dist/core/capability/boundary-driver.d.ts +29 -0
- package/dist/core/capability/boundary-driver.js +43 -0
- package/dist/core/capability/boundary-egress.d.ts +21 -0
- package/dist/core/capability/boundary-egress.js +109 -0
- package/dist/core/capability/boundary-grant-materialize.d.ts +13 -0
- package/dist/core/capability/boundary-grant-materialize.js +78 -0
- package/dist/core/capability/boundary-profile.d.ts +15 -0
- package/dist/core/capability/boundary-profile.js +25 -0
- package/dist/core/capability/boundary-run.d.ts +23 -0
- package/dist/core/capability/boundary-run.js +23 -0
- package/dist/core/capability/boundary-session.d.ts +57 -0
- package/dist/core/capability/boundary-session.js +129 -0
- package/dist/core/capability/capability-request-hash.d.ts +2 -0
- package/dist/core/capability/capability-request-hash.js +8 -0
- package/dist/core/capability/gate-latency-ratchet.d.ts +7 -0
- package/dist/core/capability/gate-latency-ratchet.js +18 -0
- package/dist/core/capability/gate-policy-shadow.d.ts +25 -0
- package/dist/core/capability/gate-policy-shadow.js +108 -0
- package/dist/core/capability/gate-shadow-audit.d.ts +1 -0
- package/dist/core/capability/gate-shadow-audit.js +1 -0
- package/dist/core/capability/gate-shadow-ratchet.d.ts +14 -0
- package/dist/core/capability/gate-shadow-ratchet.js +81 -0
- package/dist/core/capability/grant-lease.d.ts +9 -0
- package/dist/core/capability/grant-lease.js +46 -0
- package/dist/core/capability/grant-loader.d.ts +12 -0
- package/dist/core/capability/grant-loader.js +23 -0
- package/dist/core/capability/grant-match.d.ts +6 -0
- package/dist/core/capability/grant-match.js +93 -0
- package/dist/core/capability/grant.d.ts +16 -0
- package/dist/core/capability/grant.js +16 -0
- package/dist/core/capability/index.d.ts +10 -1
- package/dist/core/capability/index.js +8 -0
- package/dist/core/capability/limits.d.ts +7 -0
- package/dist/core/capability/limits.js +55 -0
- package/dist/core/capability/paths.d.ts +2 -2
- package/dist/core/capability/paths.js +9 -9
- package/dist/core/capability/policy-bridge.d.ts +18 -0
- package/dist/core/capability/policy-bridge.js +51 -0
- package/dist/core/capability/policy-engine.d.ts +70 -0
- package/dist/core/capability/policy-engine.js +476 -0
- package/dist/core/capability/policy-types.d.ts +20 -0
- package/dist/core/capability/policy-types.js +1 -0
- package/dist/core/capability/request.d.ts +45 -0
- package/dist/core/capability/request.js +1 -0
- package/dist/core/capability/resolver.d.ts +4 -0
- package/dist/core/capability/resolver.js +4 -0
- package/dist/core/capability/trusted-workspace-roots.d.ts +25 -0
- package/dist/core/capability/trusted-workspace-roots.js +149 -0
- package/dist/core/capability/types.d.ts +11 -1
- package/dist/core/capability-approval.d.ts +2 -1
- package/dist/core/capability-approval.js +100 -2
- package/dist/core/classify-subagent.d.ts +2 -1
- package/dist/core/classify-subagent.js +40 -22
- package/dist/core/classify-tool.js +157 -64
- package/dist/core/config.d.ts +32 -3
- package/dist/core/config.js +105 -3
- package/dist/core/gate-contract.d.ts +3 -0
- package/dist/core/gate-contract.js +3 -0
- package/dist/core/gate-engine.js +12 -7
- package/dist/core/harvest.d.ts +53 -0
- package/dist/core/harvest.js +276 -0
- package/dist/core/index.d.ts +9 -3
- package/dist/core/index.js +6 -2
- package/dist/core/judge-doctor.d.ts +3 -0
- package/dist/core/judge-doctor.js +90 -11
- package/dist/core/judge-fallback-hints.d.ts +13 -0
- package/dist/core/judge-fallback-hints.js +108 -0
- package/dist/core/path-utils.d.ts +12 -0
- package/dist/core/path-utils.js +60 -7
- package/dist/core/reclassify.d.ts +3 -0
- package/dist/core/reclassify.js +44 -14
- package/dist/core/recovery/capability.d.ts +3 -0
- package/dist/core/recovery/capability.js +22 -0
- package/dist/core/recovery/fail-closed.d.ts +6 -0
- package/dist/core/recovery/fail-closed.js +25 -0
- package/dist/core/recovery/index.d.ts +3 -0
- package/dist/core/recovery/index.js +2 -0
- package/dist/core/recovery/types.d.ts +20 -0
- package/dist/core/recovery/types.js +1 -0
- package/dist/core/replay-scrub.d.ts +9 -0
- package/dist/core/replay-scrub.js +43 -0
- package/dist/core/shell-tokenizer.d.ts +2 -0
- package/dist/core/shell-tokenizer.js +82 -23
- package/dist/core/standing-allow.d.ts +50 -0
- package/dist/core/standing-allow.js +178 -0
- package/dist/core/transactional/diff-evaluator.js +4 -1
- package/dist/core/transactional/eligibility.js +4 -0
- package/dist/core/transactional/git-worktree.d.ts +10 -3
- package/dist/core/transactional/git-worktree.js +81 -11
- package/dist/core/transactional/reasons.d.ts +1 -0
- package/dist/core/transactional/reasons.js +4 -0
- package/dist/core/transactional/runner.js +22 -6
- package/dist/core/transactional/types.d.ts +4 -0
- package/dist/core/types.d.ts +36 -3
- package/dist/core/verdict/adapter.d.ts +3 -4
- package/dist/core/verdict/adapter.js +19 -6
- package/dist/core/verdict/containment.d.ts +4 -3
- package/dist/core/verdict/containment.js +17 -12
- package/dist/core/verdict/cursor-acp-client.d.ts +67 -0
- package/dist/core/verdict/cursor-acp-client.js +345 -0
- package/dist/core/verdict/egress-classify.d.ts +2 -2
- package/dist/core/verdict/egress-classify.js +2 -2
- package/dist/core/verdict/git-classifier.d.ts +26 -0
- package/dist/core/verdict/git-classifier.js +533 -0
- package/dist/core/verdict/judge-audit.d.ts +1 -0
- package/dist/core/verdict/judge-audit.js +33 -1
- package/dist/core/verdict/judge-baseline.d.ts +20 -0
- package/dist/core/verdict/judge-baseline.js +49 -0
- package/dist/core/verdict/judge-broker-service.d.ts +42 -0
- package/dist/core/verdict/judge-broker-service.js +291 -0
- package/dist/core/verdict/judge-catalog.js +1 -0
- package/dist/core/verdict/judge-cli-fingerprint.d.ts +6 -0
- package/dist/core/verdict/judge-cli-fingerprint.js +67 -0
- package/dist/core/verdict/judge-cli.d.ts +23 -0
- package/dist/core/verdict/judge-cli.js +139 -16
- package/dist/core/verdict/judge-factory.js +13 -4
- package/dist/core/verdict/judge-provider-matrix.d.ts +20 -0
- package/dist/core/verdict/judge-provider-matrix.js +66 -0
- package/dist/core/verdict/judge-runtime-config.d.ts +44 -0
- package/dist/core/verdict/judge-runtime-config.js +96 -0
- package/dist/core/verdict/judge-session-broker.d.ts +48 -0
- package/dist/core/verdict/judge-session-broker.js +200 -0
- package/dist/core/verdict/judge-session-guard.d.ts +27 -0
- package/dist/core/verdict/judge-session-guard.js +91 -0
- package/dist/core/verdict/judge-session-kill-switch.d.ts +11 -0
- package/dist/core/verdict/judge-session-kill-switch.js +43 -0
- package/dist/core/verdict/judge-session-mutex.d.ts +9 -0
- package/dist/core/verdict/judge-session-mutex.js +23 -0
- package/dist/core/verdict/judge-shadow.d.ts +24 -0
- package/dist/core/verdict/judge-shadow.js +116 -0
- package/dist/core/verdict/judge-transport.d.ts +44 -0
- package/dist/core/verdict/judge-transport.js +268 -0
- package/dist/core/verdict/judge.d.ts +14 -14
- package/dist/core/verdict/judge.js +5 -67
- package/dist/core/verdict/launcher-resolve.d.ts +2 -0
- package/dist/core/verdict/launcher-resolve.js +16 -0
- package/dist/core/verdict/parser.js +6 -1
- package/dist/core/verdict/prescan.d.ts +15 -0
- package/dist/core/verdict/prescan.js +68 -0
- package/dist/core/verdict/shell-policy.d.ts +25 -0
- package/dist/core/verdict/shell-policy.js +40 -0
- package/dist/core/verdict/shell-semantics.d.ts +24 -0
- package/dist/core/verdict/shell-semantics.js +225 -0
- package/dist/core/verdict/types.d.ts +26 -1
- package/dist/core/verdict/verdict.js +300 -179
- package/dist/corpus/adversarial-probe.d.ts +86 -0
- package/dist/corpus/adversarial-probe.js +220 -0
- package/dist/corpus/evaluate.d.ts +45 -12
- package/dist/corpus/evaluate.js +61 -8
- package/dist/corpus/gate-latency-budget.d.ts +39 -0
- package/dist/corpus/gate-latency-budget.js +33 -0
- package/dist/corpus/gates.d.ts +35 -0
- package/dist/corpus/gates.js +81 -0
- package/dist/corpus/judge-accuracy.d.ts +8 -0
- package/dist/corpus/judge-accuracy.js +39 -0
- package/dist/corpus/must-allow-commands.d.ts +5 -0
- package/dist/corpus/must-allow-commands.js +13 -0
- package/dist/corpus/mutators.d.ts +28 -0
- package/dist/corpus/mutators.js +162 -0
- package/dist/corpus/ratchet.d.ts +42 -0
- package/dist/corpus/ratchet.js +116 -0
- package/dist/corpus/runtime-match.d.ts +19 -0
- package/dist/corpus/runtime-match.js +43 -0
- package/dist/corpus/standing-allow-catalog.generated.d.ts +13 -0
- package/dist/corpus/standing-allow-catalog.generated.js +99 -0
- package/dist/corpus/types.d.ts +47 -0
- package/dist/corpus/types.js +157 -0
- package/dist/installer.js +4 -1
- package/dist/judge-broker-daemon.d.ts +1 -0
- package/dist/judge-broker-daemon.js +137 -0
- package/dist/services/sandbox-service.d.ts +5 -0
- package/dist/services/sandbox-service.js +30 -2
- package/dist/types.d.ts +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -3
- package/skills/belay/SKILL.md +7 -1
- package/skills/belay/belay-approve.md +17 -0
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { policyDecisionToLegacyReason } from '../capability/policy-bridge.js';
|
|
3
|
+
import { policyDecisionRequiresAsk } from '../capability/policy-engine.js';
|
|
4
|
+
import { canonicalPath, resolveMutationTarget } from '../path-utils.js';
|
|
5
|
+
import { extractRedirectTargets, isFdDuplication, isRedirectOperator, tokenizeShell, } from '../shell-tokenizer.js';
|
|
3
6
|
import { analyzePathTargets, cwdRelative, isDestructiveHighStakesMutation, resolveTrustedPath, touchesProtectedRoot, } from './containment.js';
|
|
4
7
|
import { classifyEgressTool } from './egress-classify.js';
|
|
5
8
|
import { verdictFingerprint } from './fingerprint.js';
|
|
6
|
-
import {
|
|
7
|
-
import { isRoutineLauncher, resolveLauncherRecipe } from './launcher-resolve.js';
|
|
9
|
+
import { isReadOnlyLauncherInvocation, isRoutineLauncher, resolveLauncherRecipe, } from './launcher-resolve.js';
|
|
8
10
|
import { allowFromCustomOverride, askFromCustomExternal, customAllowMatch, customExternalMatch, } from './overrides.js';
|
|
9
11
|
import { extractRecursiveScript, isBareInterpreter, isVariableIndirectHead, parseSegment, peelTransparentWrappers, redactCommand, segmentOpacity, splitTopLevelSegments, substitutionInners, } from './parser.js';
|
|
12
|
+
import { mutationPrescanRequiresAsk, prescanInterpreterCode, tier1RequiresAsk } from './prescan.js';
|
|
13
|
+
import { evaluateSegmentShellPolicy, shellPolicyMetadata, } from './shell-policy.js';
|
|
14
|
+
import { analyzeShellCommandSemantics, isPureReadOnlySemantics, LOCAL_MUTATION_KEYS, } from './shell-semantics.js';
|
|
10
15
|
const DEFAULT_MAX_DEPTH = 8;
|
|
11
16
|
const TIER0_EXTERNAL_KEYS = new Set([
|
|
12
17
|
'git push',
|
|
@@ -20,7 +25,6 @@ const TIER0_EXTERNAL_KEYS = new Set([
|
|
|
20
25
|
'supabase',
|
|
21
26
|
'scp',
|
|
22
27
|
'ssh',
|
|
23
|
-
'rsync',
|
|
24
28
|
]);
|
|
25
29
|
const TIER0_EXTERNAL_HEADS = new Set([
|
|
26
30
|
'dropdb',
|
|
@@ -30,66 +34,6 @@ const TIER0_EXTERNAL_HEADS = new Set([
|
|
|
30
34
|
'mongosh',
|
|
31
35
|
'redis-cli',
|
|
32
36
|
]);
|
|
33
|
-
const READ_ONLY_KEYS = new Set([
|
|
34
|
-
'cat',
|
|
35
|
-
'cd',
|
|
36
|
-
'echo',
|
|
37
|
-
'git diff',
|
|
38
|
-
'git log',
|
|
39
|
-
'git rev-parse',
|
|
40
|
-
'git show',
|
|
41
|
-
'git status',
|
|
42
|
-
'head',
|
|
43
|
-
'ls',
|
|
44
|
-
'pwd',
|
|
45
|
-
'rg',
|
|
46
|
-
'sort',
|
|
47
|
-
'tail',
|
|
48
|
-
'wc',
|
|
49
|
-
'which',
|
|
50
|
-
'find',
|
|
51
|
-
]);
|
|
52
|
-
const PURE_READ_ONLY_KEYS = new Set([
|
|
53
|
-
'echo',
|
|
54
|
-
'git diff',
|
|
55
|
-
'git log',
|
|
56
|
-
'git rev-parse',
|
|
57
|
-
'git show',
|
|
58
|
-
'git status',
|
|
59
|
-
'pwd',
|
|
60
|
-
'which',
|
|
61
|
-
]);
|
|
62
|
-
const LOCAL_MUTATION_KEYS = new Set([
|
|
63
|
-
'chmod',
|
|
64
|
-
'cp',
|
|
65
|
-
'git add',
|
|
66
|
-
'git clean',
|
|
67
|
-
'git commit',
|
|
68
|
-
'git mv',
|
|
69
|
-
'git reset',
|
|
70
|
-
'mkdir',
|
|
71
|
-
'mv',
|
|
72
|
-
'rm',
|
|
73
|
-
'sed',
|
|
74
|
-
'tee',
|
|
75
|
-
'touch',
|
|
76
|
-
'truncate',
|
|
77
|
-
]);
|
|
78
|
-
/** Routine local build/test runners resolved from launcher recipes. */
|
|
79
|
-
const LOCAL_ROUTINE_HEADS = new Set([
|
|
80
|
-
'tsc',
|
|
81
|
-
'vitest',
|
|
82
|
-
'vite',
|
|
83
|
-
'webpack',
|
|
84
|
-
'esbuild',
|
|
85
|
-
'rollup',
|
|
86
|
-
'jest',
|
|
87
|
-
'mocha',
|
|
88
|
-
'cargo',
|
|
89
|
-
'go',
|
|
90
|
-
'make',
|
|
91
|
-
'cmake',
|
|
92
|
-
]);
|
|
93
37
|
const BELAY_SELF_COMMANDS = new Set(['approve', 'revoke']);
|
|
94
38
|
const FIND_DANGEROUS_FLAGS = new Set(['-delete', '-exec', '-execdir', '-ok', '-okdir']);
|
|
95
39
|
function isFindDangerous(tokens) {
|
|
@@ -126,7 +70,7 @@ async function evaluateSubstitutions(command, context, depth) {
|
|
|
126
70
|
}
|
|
127
71
|
worst = worst ? combineInternal(worst, innerVerdict) : innerVerdict;
|
|
128
72
|
}
|
|
129
|
-
if (!worst) {
|
|
73
|
+
if (!worst || worst.permission === 'allow') {
|
|
130
74
|
return null;
|
|
131
75
|
}
|
|
132
76
|
return {
|
|
@@ -177,26 +121,46 @@ function combineInternal(left, right) {
|
|
|
177
121
|
: right.reason,
|
|
178
122
|
signals: [...new Set([...left.signals, ...right.signals])],
|
|
179
123
|
judgeTrace: right.judgeTrace ?? left.judgeTrace,
|
|
124
|
+
capabilityRequests: [...(left.capabilityRequests ?? []), ...(right.capabilityRequests ?? [])],
|
|
125
|
+
authorizationDecision: worsePermission(left.permission, right.permission) === 'ask'
|
|
126
|
+
? right.permission === 'ask'
|
|
127
|
+
? (right.authorizationDecision ?? left.authorizationDecision)
|
|
128
|
+
: left.authorizationDecision
|
|
129
|
+
: (right.authorizationDecision ?? left.authorizationDecision),
|
|
180
130
|
};
|
|
181
131
|
}
|
|
182
132
|
function askVerdict(params) {
|
|
183
133
|
return { ...params, permission: 'ask' };
|
|
184
134
|
}
|
|
185
|
-
function
|
|
186
|
-
|
|
135
|
+
function resolveShellPathTargets(pathArgs, context) {
|
|
136
|
+
if (!context.cwd) {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
return pathArgs.map((target) => {
|
|
140
|
+
const resolved = (context.trustedCwd ? resolveTrustedPath(target, context.cwd, context.trustedCwd) : null) ??
|
|
141
|
+
resolveMutationTarget(target, context.cwd);
|
|
142
|
+
if (!resolved) {
|
|
143
|
+
const joined = path.isAbsolute(target) ? target : path.join(context.cwd, target);
|
|
144
|
+
return canonicalPath(joined);
|
|
145
|
+
}
|
|
146
|
+
return canonicalPath(resolved);
|
|
147
|
+
});
|
|
187
148
|
}
|
|
188
|
-
function
|
|
189
|
-
if (
|
|
190
|
-
return
|
|
149
|
+
function withShellPolicyMetadata(input, existing = {}) {
|
|
150
|
+
if (existing.capabilityRequests?.length) {
|
|
151
|
+
return existing;
|
|
191
152
|
}
|
|
192
|
-
return
|
|
153
|
+
return shellPolicyMetadata(input);
|
|
154
|
+
}
|
|
155
|
+
function allowVerdict(params) {
|
|
156
|
+
return { ...params, permission: 'allow' };
|
|
193
157
|
}
|
|
194
158
|
function extractPathArgs(tokens) {
|
|
195
159
|
const redirects = extractRedirectTargets(tokens);
|
|
196
160
|
const args = [...redirects];
|
|
197
161
|
for (let index = 1; index < tokens.length; index += 1) {
|
|
198
162
|
const token = tokens[index];
|
|
199
|
-
if (!token || token.startsWith('-') || token
|
|
163
|
+
if (!token || token.startsWith('-') || isRedirectOperator(token) || isFdDuplication(token)) {
|
|
200
164
|
continue;
|
|
201
165
|
}
|
|
202
166
|
if (redirects.includes(token)) {
|
|
@@ -209,8 +173,15 @@ function extractPathArgs(tokens) {
|
|
|
209
173
|
function isVariableOrOpaquePathToken(token) {
|
|
210
174
|
return token.includes('$') || token.includes('`');
|
|
211
175
|
}
|
|
212
|
-
function
|
|
213
|
-
|
|
176
|
+
function pathContextFromSemantics(context, semantics) {
|
|
177
|
+
const cwd = semantics.gitWorkTree ?? semantics.effectiveCwd;
|
|
178
|
+
if (!cwd || cwd === context.cwd) {
|
|
179
|
+
return context;
|
|
180
|
+
}
|
|
181
|
+
return { ...context, cwd };
|
|
182
|
+
}
|
|
183
|
+
function isPureReadOnlySegment(segment, semantics) {
|
|
184
|
+
return isPureReadOnlySemantics(segment, semantics);
|
|
214
185
|
}
|
|
215
186
|
function updateChainState(command, state) {
|
|
216
187
|
const segment = parseSegment(command);
|
|
@@ -233,10 +204,13 @@ function updateChainState(command, state) {
|
|
|
233
204
|
trustedCwd: true,
|
|
234
205
|
};
|
|
235
206
|
}
|
|
236
|
-
function tier0ExternalMatch(key, head, tokens) {
|
|
207
|
+
function tier0ExternalMatch(key, head, tokens, signals = []) {
|
|
237
208
|
if (TIER0_EXTERNAL_KEYS.has(key)) {
|
|
238
209
|
return true;
|
|
239
210
|
}
|
|
211
|
+
if (signals.includes('git.push')) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
240
214
|
if (TIER0_EXTERNAL_HEADS.has(head)) {
|
|
241
215
|
return true;
|
|
242
216
|
}
|
|
@@ -248,9 +222,6 @@ function tier0ExternalMatch(key, head, tokens) {
|
|
|
248
222
|
tokens.some((t) => t === '--push' || t.startsWith('--output=type=registry')))) {
|
|
249
223
|
return true;
|
|
250
224
|
}
|
|
251
|
-
if (head === 'git' && tokens[1] === 'push') {
|
|
252
|
-
return true;
|
|
253
|
-
}
|
|
254
225
|
if (head === 'terraform' && tokens[1] === 'apply') {
|
|
255
226
|
return true;
|
|
256
227
|
}
|
|
@@ -304,6 +275,7 @@ function tier0HighStakesRm(tokens, context) {
|
|
|
304
275
|
cwd: context.cwd,
|
|
305
276
|
repoRoot: context.repoRoot,
|
|
306
277
|
trustedCwd: context.trustedCwd,
|
|
278
|
+
trustedWorkspaceRoots: context.trustedWorkspaceRoots,
|
|
307
279
|
sensitivePaths: context.sensitivePaths,
|
|
308
280
|
protectedArtifactRoots: context.protectedArtifactRoots,
|
|
309
281
|
});
|
|
@@ -336,7 +308,7 @@ function tier0HighStakesRm(tokens, context) {
|
|
|
336
308
|
const resolved = resolveTrustedPath(target, context.cwd, context.trustedCwd) ??
|
|
337
309
|
resolveMutationTarget(target, context.cwd);
|
|
338
310
|
if (resolved &&
|
|
339
|
-
isDestructiveHighStakesMutation(head, resolved, context.repoRoot, context.sensitivePaths, context.protectedArtifactRoots)) {
|
|
311
|
+
isDestructiveHighStakesMutation(head, resolved, context.repoRoot, context.sensitivePaths, context.protectedArtifactRoots, context.trustedWorkspaceRoots)) {
|
|
340
312
|
destructiveHighStakes = true;
|
|
341
313
|
break;
|
|
342
314
|
}
|
|
@@ -354,6 +326,39 @@ function tier0HighStakesRm(tokens, context) {
|
|
|
354
326
|
}
|
|
355
327
|
return null;
|
|
356
328
|
}
|
|
329
|
+
function verdictFromOpacityPolicy(params) {
|
|
330
|
+
const { request, decision } = evaluateSegmentShellPolicy({
|
|
331
|
+
command: params.command,
|
|
332
|
+
segmentHead: params.segmentHead,
|
|
333
|
+
effect: params.effect,
|
|
334
|
+
location: params.location,
|
|
335
|
+
opacity: params.opacity,
|
|
336
|
+
pathArgs: params.pathArgs,
|
|
337
|
+
signals: params.signals,
|
|
338
|
+
context: params.context,
|
|
339
|
+
});
|
|
340
|
+
const base = {
|
|
341
|
+
location: params.location,
|
|
342
|
+
opacity: params.opacity,
|
|
343
|
+
effect: params.effect,
|
|
344
|
+
confidence: 'deterministic',
|
|
345
|
+
signals: [...params.signals, ...decision.signals],
|
|
346
|
+
capabilityRequests: [request],
|
|
347
|
+
authorizationDecision: decision,
|
|
348
|
+
};
|
|
349
|
+
if (policyDecisionRequiresAsk(decision)) {
|
|
350
|
+
return {
|
|
351
|
+
permission: 'ask',
|
|
352
|
+
reason: policyDecisionToLegacyReason(decision),
|
|
353
|
+
...base,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
permission: 'allow',
|
|
358
|
+
reason: policyDecisionToLegacyReason(decision),
|
|
359
|
+
...base,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
357
362
|
async function evaluateSegment(command, context, depth) {
|
|
358
363
|
const maxDepth = context.maxRecursionDepth ?? DEFAULT_MAX_DEPTH;
|
|
359
364
|
if (depth > maxDepth) {
|
|
@@ -368,23 +373,15 @@ async function evaluateSegment(command, context, depth) {
|
|
|
368
373
|
}
|
|
369
374
|
const opacity = segmentOpacity(command);
|
|
370
375
|
if (opacity === 'unparseable') {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
opacity: 'unparseable',
|
|
375
|
-
effect: 'unknown',
|
|
376
|
-
confidence: 'deterministic',
|
|
377
|
-
reason: 'unparseable_shell',
|
|
378
|
-
signals: ['unparseable_shell'],
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
return allowVerdict({
|
|
382
|
-
location: 'unknown',
|
|
376
|
+
return verdictFromOpacityPolicy({
|
|
377
|
+
command,
|
|
378
|
+
segmentHead: parseSegment(command).head,
|
|
383
379
|
opacity: 'unparseable',
|
|
384
380
|
effect: 'unknown',
|
|
385
|
-
|
|
386
|
-
|
|
381
|
+
location: 'unknown',
|
|
382
|
+
pathArgs: [],
|
|
387
383
|
signals: ['unparseable_shell'],
|
|
384
|
+
context,
|
|
388
385
|
});
|
|
389
386
|
}
|
|
390
387
|
const substitutionResult = await evaluateSubstitutions(command, context, depth);
|
|
@@ -394,13 +391,15 @@ async function evaluateSegment(command, context, depth) {
|
|
|
394
391
|
const tokens = tokenizeShell(command);
|
|
395
392
|
const { tokens: peeled, xargsStdinOpaque } = peelTransparentWrappers(tokens);
|
|
396
393
|
if (xargsStdinOpaque || isBareInterpreter(tokens)) {
|
|
397
|
-
return
|
|
398
|
-
|
|
394
|
+
return verdictFromOpacityPolicy({
|
|
395
|
+
command,
|
|
396
|
+
segmentHead: parseSegment(command).head,
|
|
399
397
|
opacity: 'opaque',
|
|
400
398
|
effect: 'unknown',
|
|
401
|
-
|
|
402
|
-
|
|
399
|
+
location: 'unknown',
|
|
400
|
+
pathArgs: [],
|
|
403
401
|
signals: ['opaque_execution'],
|
|
402
|
+
context,
|
|
404
403
|
});
|
|
405
404
|
}
|
|
406
405
|
const segment = parseSegment(command);
|
|
@@ -429,13 +428,28 @@ async function evaluateSegment(command, context, depth) {
|
|
|
429
428
|
if (recursiveScript) {
|
|
430
429
|
const prescan = prescanInterpreterCode(recursiveScript);
|
|
431
430
|
if (prescan && tier1RequiresAsk(prescan)) {
|
|
431
|
+
const { request, decision } = evaluateSegmentShellPolicy({
|
|
432
|
+
command: recursiveScript,
|
|
433
|
+
segmentHead: segment.head,
|
|
434
|
+
effect: 'unknown',
|
|
435
|
+
location: 'unknown',
|
|
436
|
+
opacity: 'recursive',
|
|
437
|
+
pathArgs: [],
|
|
438
|
+
signals: ['interpreter_secret_prescan', prescan.reason],
|
|
439
|
+
context,
|
|
440
|
+
});
|
|
441
|
+
const legacyReason = policyDecisionRequiresAsk(decision)
|
|
442
|
+
? policyDecisionToLegacyReason(decision)
|
|
443
|
+
: 'interpreter_secret_prescan';
|
|
432
444
|
return askVerdict({
|
|
433
445
|
location: 'unknown',
|
|
434
446
|
opacity: 'recursive',
|
|
435
447
|
effect: 'unknown',
|
|
436
448
|
confidence: 'deterministic',
|
|
437
|
-
reason:
|
|
438
|
-
signals: ['interpreter_secret_prescan'],
|
|
449
|
+
reason: legacyReason,
|
|
450
|
+
signals: ['interpreter_secret_prescan', prescan.reason, ...decision.signals],
|
|
451
|
+
capabilityRequests: [request],
|
|
452
|
+
authorizationDecision: decision,
|
|
439
453
|
});
|
|
440
454
|
}
|
|
441
455
|
const innerVerdict = await evaluateSegment(recursiveScript, context, depth + 1);
|
|
@@ -480,8 +494,10 @@ async function evaluateSegment(command, context, depth) {
|
|
|
480
494
|
}
|
|
481
495
|
let innerVerdict = null;
|
|
482
496
|
for (const recipe of resolution.recipes) {
|
|
483
|
-
const
|
|
484
|
-
|
|
497
|
+
for (const sub of splitTopLevelSegments(recipe)) {
|
|
498
|
+
const evaluated = await evaluateSegment(sub, context, depth + 1);
|
|
499
|
+
innerVerdict = innerVerdict ? combineInternal(innerVerdict, evaluated) : evaluated;
|
|
500
|
+
}
|
|
485
501
|
}
|
|
486
502
|
if (!innerVerdict) {
|
|
487
503
|
return askVerdict({
|
|
@@ -499,38 +515,78 @@ async function evaluateSegment(command, context, depth) {
|
|
|
499
515
|
signals: [...innerVerdict.signals, resolution.reason],
|
|
500
516
|
};
|
|
501
517
|
}
|
|
502
|
-
const
|
|
518
|
+
const semantics = analyzeShellCommandSemantics(peeled, segment, context.cwd);
|
|
519
|
+
const pathContext = pathContextFromSemantics(context, semantics);
|
|
520
|
+
if (semantics.requiresAsk) {
|
|
521
|
+
const { request, decision } = evaluateSegmentShellPolicy({
|
|
522
|
+
command,
|
|
523
|
+
segmentHead: segment.head,
|
|
524
|
+
effect: 'local_mutation',
|
|
525
|
+
location: 'repo_local',
|
|
526
|
+
opacity: 'transparent',
|
|
527
|
+
pathArgs: semantics.pathTargets,
|
|
528
|
+
signals: semantics.requiresAsk.signals,
|
|
529
|
+
context: pathContext,
|
|
530
|
+
});
|
|
531
|
+
return askVerdict({
|
|
532
|
+
location: 'repo_local',
|
|
533
|
+
opacity: 'transparent',
|
|
534
|
+
effect: 'local_mutation',
|
|
535
|
+
confidence: 'deterministic',
|
|
536
|
+
reason: semantics.requiresAsk.reason,
|
|
537
|
+
signals: semantics.requiresAsk.signals,
|
|
538
|
+
capabilityRequests: [request],
|
|
539
|
+
authorizationDecision: decision,
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
const egressClass = semantics.egressClass ?? classifyEgressTool(segment.head, peeled);
|
|
503
543
|
if (egressClass === 'destructive') {
|
|
544
|
+
const { request, decision } = evaluateSegmentShellPolicy({
|
|
545
|
+
command,
|
|
546
|
+
segmentHead: segment.head,
|
|
547
|
+
effect: 'remote_mutation',
|
|
548
|
+
location: 'external',
|
|
549
|
+
opacity: 'transparent',
|
|
550
|
+
egressClass: 'destructive',
|
|
551
|
+
pathArgs: [],
|
|
552
|
+
signals: ['tier0_external', segment.head],
|
|
553
|
+
context,
|
|
554
|
+
});
|
|
504
555
|
return askVerdict({
|
|
505
556
|
location: 'external',
|
|
506
557
|
opacity: 'transparent',
|
|
507
558
|
effect: 'remote_mutation',
|
|
508
559
|
confidence: 'deterministic',
|
|
509
560
|
reason: 'tier0_external',
|
|
510
|
-
signals: ['tier0_external', segment.head],
|
|
561
|
+
signals: ['tier0_external', segment.head, ...decision.signals],
|
|
562
|
+
capabilityRequests: [request],
|
|
563
|
+
authorizationDecision: decision,
|
|
511
564
|
});
|
|
512
565
|
}
|
|
513
|
-
if (
|
|
514
|
-
|
|
566
|
+
if (tier0ExternalMatch(semantics.normalizedKey ?? segment.key, segment.head, peeled, semantics.signals)) {
|
|
567
|
+
const tier0Key = semantics.normalizedKey ?? segment.key;
|
|
568
|
+
const { request, decision } = evaluateSegmentShellPolicy({
|
|
569
|
+
command,
|
|
570
|
+
segmentHead: segment.head,
|
|
571
|
+
effect: 'remote_mutation',
|
|
515
572
|
location: 'external',
|
|
516
573
|
opacity: 'transparent',
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
signals: ['egress_read', segment.head],
|
|
574
|
+
pathArgs: [],
|
|
575
|
+
signals: ['tier0_external', tier0Key, ...semantics.signals],
|
|
576
|
+
context: pathContext,
|
|
521
577
|
});
|
|
522
|
-
}
|
|
523
|
-
if (tier0ExternalMatch(segment.key, segment.head, peeled)) {
|
|
524
578
|
return askVerdict({
|
|
525
579
|
location: 'external',
|
|
526
580
|
opacity: 'transparent',
|
|
527
581
|
effect: 'remote_mutation',
|
|
528
582
|
confidence: 'deterministic',
|
|
529
583
|
reason: 'tier0_external',
|
|
530
|
-
signals: ['tier0_external',
|
|
584
|
+
signals: ['tier0_external', tier0Key, ...decision.signals],
|
|
585
|
+
capabilityRequests: [request],
|
|
586
|
+
authorizationDecision: decision,
|
|
531
587
|
});
|
|
532
588
|
}
|
|
533
|
-
const rmVerdict = tier0HighStakesRm(peeled,
|
|
589
|
+
const rmVerdict = tier0HighStakesRm(peeled, pathContext);
|
|
534
590
|
if (rmVerdict) {
|
|
535
591
|
return rmVerdict;
|
|
536
592
|
}
|
|
@@ -544,28 +600,29 @@ async function evaluateSegment(command, context, depth) {
|
|
|
544
600
|
signals: ['belay_control_plane_command', segment.head],
|
|
545
601
|
});
|
|
546
602
|
}
|
|
547
|
-
let effect =
|
|
548
|
-
if (
|
|
603
|
+
let effect = semantics.effect;
|
|
604
|
+
if (isReadOnlyLauncherInvocation(peeled)) {
|
|
549
605
|
effect = 'read_only';
|
|
550
606
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
else if (LOCAL_ROUTINE_HEADS.has(segment.head)) {
|
|
555
|
-
effect = 'local_mutation';
|
|
556
|
-
}
|
|
557
|
-
const pathArgs = extractPathArgs(peeled);
|
|
558
|
-
if (extractRedirectTargets(peeled).length > 0 && effect === 'read_only') {
|
|
559
|
-
effect = 'local_mutation';
|
|
560
|
-
}
|
|
607
|
+
const pathArgs = semantics.pathTargets;
|
|
608
|
+
const policyPathArgs = [...new Set([...pathArgs, ...(semantics.scopeTargets ?? [])])];
|
|
561
609
|
const pathAnalysis = analyzePathTargets({
|
|
562
|
-
targets:
|
|
563
|
-
cwd:
|
|
564
|
-
repoRoot:
|
|
565
|
-
trustedCwd:
|
|
566
|
-
|
|
567
|
-
|
|
610
|
+
targets: policyPathArgs,
|
|
611
|
+
cwd: pathContext.cwd,
|
|
612
|
+
repoRoot: pathContext.repoRoot,
|
|
613
|
+
trustedCwd: pathContext.trustedCwd,
|
|
614
|
+
trustedWorkspaceRoots: pathContext.trustedWorkspaceRoots,
|
|
615
|
+
sensitivePaths: pathContext.sensitivePaths,
|
|
616
|
+
protectedArtifactRoots: pathContext.protectedArtifactRoots,
|
|
568
617
|
});
|
|
618
|
+
// Path-less read/mutation in a trusted cwd: assume repo-local (e.g. git status, tsc --noEmit).
|
|
619
|
+
const effectiveLocation = policyPathArgs.length === 0 &&
|
|
620
|
+
pathContext.trustedCwd &&
|
|
621
|
+
pathContext.cwd &&
|
|
622
|
+
(effect === 'read_only' || effect === 'local_mutation') &&
|
|
623
|
+
pathAnalysis.location === 'unknown'
|
|
624
|
+
? 'repo_local'
|
|
625
|
+
: pathAnalysis.location;
|
|
569
626
|
if (!context.trustedCwd || !context.cwd) {
|
|
570
627
|
if (opacity === 'opaque' || effect === 'unknown' || effect === 'local_mutation') {
|
|
571
628
|
return askVerdict({
|
|
@@ -577,7 +634,7 @@ async function evaluateSegment(command, context, depth) {
|
|
|
577
634
|
signals: ['missing_trusted_cwd'],
|
|
578
635
|
});
|
|
579
636
|
}
|
|
580
|
-
if (effect === 'read_only' && !isPureReadOnlySegment(segment)) {
|
|
637
|
+
if (effect === 'read_only' && !isPureReadOnlySegment(segment, semantics)) {
|
|
581
638
|
return askVerdict({
|
|
582
639
|
location: 'unknown',
|
|
583
640
|
opacity,
|
|
@@ -592,9 +649,9 @@ async function evaluateSegment(command, context, depth) {
|
|
|
592
649
|
context.protectedArtifactRoots &&
|
|
593
650
|
context.protectedArtifactRoots.length > 0) {
|
|
594
651
|
for (const target of pathArgs) {
|
|
595
|
-
const resolved = resolveTrustedPath(target,
|
|
596
|
-
resolveMutationTarget(target,
|
|
597
|
-
if (resolved && touchesProtectedRoot(resolved,
|
|
652
|
+
const resolved = resolveTrustedPath(target, pathContext.cwd, pathContext.trustedCwd) ??
|
|
653
|
+
resolveMutationTarget(target, pathContext.cwd);
|
|
654
|
+
if (resolved && touchesProtectedRoot(resolved, pathContext.protectedArtifactRoots ?? [])) {
|
|
598
655
|
return askVerdict({
|
|
599
656
|
location: pathAnalysis.location,
|
|
600
657
|
opacity: 'transparent',
|
|
@@ -609,10 +666,10 @@ async function evaluateSegment(command, context, depth) {
|
|
|
609
666
|
if (effect === 'local_mutation' || effect === 'unknown') {
|
|
610
667
|
let destructiveHighStakes = false;
|
|
611
668
|
for (const target of pathArgs) {
|
|
612
|
-
const resolved = resolveTrustedPath(target,
|
|
613
|
-
resolveMutationTarget(target,
|
|
669
|
+
const resolved = resolveTrustedPath(target, pathContext.cwd, pathContext.trustedCwd) ??
|
|
670
|
+
resolveMutationTarget(target, pathContext.cwd);
|
|
614
671
|
if (resolved &&
|
|
615
|
-
isDestructiveHighStakesMutation(segment.head, resolved,
|
|
672
|
+
isDestructiveHighStakesMutation(segment.head, resolved, pathContext.repoRoot, pathContext.sensitivePaths, pathContext.protectedArtifactRoots, pathContext.trustedWorkspaceRoots)) {
|
|
616
673
|
destructiveHighStakes = true;
|
|
617
674
|
break;
|
|
618
675
|
}
|
|
@@ -661,17 +718,43 @@ async function evaluateSegment(command, context, depth) {
|
|
|
661
718
|
signals: ['unknown_location_mutation'],
|
|
662
719
|
});
|
|
663
720
|
}
|
|
721
|
+
const resolvedPathTargets = resolveShellPathTargets(policyPathArgs, pathContext);
|
|
722
|
+
const semanticSignals = semantics.signals;
|
|
723
|
+
const buildPolicyInput = (overrides) => ({
|
|
724
|
+
command: recursiveScript ?? command,
|
|
725
|
+
segmentHead: segment.head,
|
|
726
|
+
opacity,
|
|
727
|
+
egressClass: egressClass ?? undefined,
|
|
728
|
+
pathArgs: policyPathArgs,
|
|
729
|
+
resolvedPathTargets,
|
|
730
|
+
signals: [...semanticSignals, ...pathAnalysis.signals],
|
|
731
|
+
context: pathContext,
|
|
732
|
+
...overrides,
|
|
733
|
+
});
|
|
664
734
|
const outsideMutation = pathAnalysis.location === 'repo_outside' || pathAnalysis.location === 'mixed';
|
|
665
735
|
const mutationPrescan = (effect === 'local_mutation' || effect === 'unknown') && pathArgs.length > 0
|
|
666
736
|
? mutationPrescanRequiresAsk({
|
|
667
737
|
targets: pathArgs,
|
|
668
|
-
cwd:
|
|
669
|
-
repoRoot:
|
|
670
|
-
trustedCwd:
|
|
671
|
-
|
|
738
|
+
cwd: pathContext.cwd,
|
|
739
|
+
repoRoot: pathContext.repoRoot,
|
|
740
|
+
trustedCwd: pathContext.trustedCwd,
|
|
741
|
+
trustedWorkspaceRoots: pathContext.trustedWorkspaceRoots,
|
|
742
|
+
sensitivePaths: pathContext.sensitivePaths,
|
|
672
743
|
})
|
|
673
744
|
: null;
|
|
674
745
|
if (mutationPrescan) {
|
|
746
|
+
const { request, decision } = evaluateSegmentShellPolicy(buildPolicyInput({
|
|
747
|
+
effect: 'local_mutation',
|
|
748
|
+
location: pathAnalysis.location === 'unknown'
|
|
749
|
+
? 'unknown'
|
|
750
|
+
: pathAnalysis.location === 'repo_outside' || pathAnalysis.location === 'mixed'
|
|
751
|
+
? pathAnalysis.location
|
|
752
|
+
: 'repo_local',
|
|
753
|
+
signals: ['tier1_catastrophic', mutationPrescan.reason, ...pathAnalysis.signals],
|
|
754
|
+
}));
|
|
755
|
+
const legacyReason = policyDecisionRequiresAsk(decision)
|
|
756
|
+
? policyDecisionToLegacyReason(decision, { outsideMutation, effect: 'local_mutation' })
|
|
757
|
+
: 'tier1_catastrophic';
|
|
675
758
|
return askVerdict({
|
|
676
759
|
location: pathAnalysis.location === 'unknown'
|
|
677
760
|
? 'unknown'
|
|
@@ -681,24 +764,28 @@ async function evaluateSegment(command, context, depth) {
|
|
|
681
764
|
opacity,
|
|
682
765
|
effect: 'local_mutation',
|
|
683
766
|
confidence: 'deterministic',
|
|
684
|
-
reason:
|
|
685
|
-
signals: ['tier1_catastrophic', mutationPrescan.reason],
|
|
767
|
+
reason: legacyReason,
|
|
768
|
+
signals: ['tier1_catastrophic', mutationPrescan.reason, ...decision.signals],
|
|
769
|
+
capabilityRequests: [request],
|
|
770
|
+
authorizationDecision: decision,
|
|
686
771
|
});
|
|
687
772
|
}
|
|
688
|
-
const
|
|
773
|
+
const needsPolicy = effect === 'unknown' ||
|
|
689
774
|
TIER0_EXTERNAL_HEADS.has(segment.head) ||
|
|
690
775
|
egressClass === 'ambiguous' ||
|
|
776
|
+
egressClass === 'read' ||
|
|
691
777
|
(outsideMutation && effect !== 'read_only');
|
|
692
|
-
let
|
|
693
|
-
if (
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
778
|
+
let policyMetadata = {};
|
|
779
|
+
if (needsPolicy) {
|
|
780
|
+
const { request, decision } = evaluateSegmentShellPolicy(buildPolicyInput({
|
|
781
|
+
effect,
|
|
782
|
+
location: pathAnalysis.location,
|
|
783
|
+
}));
|
|
784
|
+
policyMetadata = {
|
|
785
|
+
capabilityRequests: [request],
|
|
786
|
+
authorizationDecision: decision,
|
|
787
|
+
};
|
|
788
|
+
if (policyDecisionRequiresAsk(decision)) {
|
|
702
789
|
return askVerdict({
|
|
703
790
|
location: pathAnalysis.location === 'unknown'
|
|
704
791
|
? 'unknown'
|
|
@@ -706,72 +793,104 @@ async function evaluateSegment(command, context, depth) {
|
|
|
706
793
|
? pathAnalysis.location
|
|
707
794
|
: 'repo_local',
|
|
708
795
|
opacity,
|
|
709
|
-
effect:
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
796
|
+
effect: decision.reason === 'external_effect' || decision.reason === 'network_connect'
|
|
797
|
+
? 'remote_mutation'
|
|
798
|
+
: effect,
|
|
799
|
+
confidence: 'deterministic',
|
|
800
|
+
reason: policyDecisionToLegacyReason(decision, { outsideMutation, effect }),
|
|
801
|
+
signals: ['policy_required', ...decision.signals],
|
|
802
|
+
...policyMetadata,
|
|
714
803
|
});
|
|
715
804
|
}
|
|
716
805
|
}
|
|
717
806
|
if (outsideMutation && effect !== 'read_only') {
|
|
718
|
-
|
|
807
|
+
const policyAllowsOutside = policyMetadata.authorizationDecision?.outcome === 'allow' &&
|
|
808
|
+
(policyMetadata.authorizationDecision.matchedRule === 'grant.exact' ||
|
|
809
|
+
policyMetadata.authorizationDecision.matchedRule === 'boundary.verified' ||
|
|
810
|
+
policyMetadata.authorizationDecision.matchedRule === 'builtin.trusted_workspace');
|
|
811
|
+
if (!policyAllowsOutside) {
|
|
812
|
+
const legacyReason = policyMetadata.authorizationDecision &&
|
|
813
|
+
policyDecisionRequiresAsk(policyMetadata.authorizationDecision)
|
|
814
|
+
? policyDecisionToLegacyReason(policyMetadata.authorizationDecision, {
|
|
815
|
+
outsideMutation,
|
|
816
|
+
effect,
|
|
817
|
+
})
|
|
818
|
+
: 'outside_repo_mutation';
|
|
819
|
+
return askVerdict({
|
|
820
|
+
location: pathAnalysis.location,
|
|
821
|
+
opacity,
|
|
822
|
+
effect: 'local_mutation',
|
|
823
|
+
confidence: 'deterministic',
|
|
824
|
+
reason: legacyReason,
|
|
825
|
+
signals: ['outside_repo_mutation', ...pathAnalysis.signals],
|
|
826
|
+
...policyMetadata,
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
return allowVerdict({
|
|
719
830
|
location: pathAnalysis.location,
|
|
720
831
|
opacity,
|
|
721
832
|
effect: 'local_mutation',
|
|
722
|
-
confidence:
|
|
833
|
+
confidence: 'deterministic',
|
|
723
834
|
reason: 'repo_outside_local_mutation',
|
|
724
835
|
signals: ['repo_outside_local_mutation', ...pathAnalysis.signals],
|
|
725
|
-
|
|
836
|
+
...policyMetadata,
|
|
837
|
+
});
|
|
726
838
|
}
|
|
727
|
-
if (
|
|
839
|
+
if (effectiveLocation === 'repo_local' &&
|
|
728
840
|
(effect === 'read_only' || effect === 'local_mutation') &&
|
|
729
841
|
opacity !== 'opaque') {
|
|
730
|
-
return
|
|
842
|
+
return allowVerdict({
|
|
731
843
|
location: 'repo_local',
|
|
732
844
|
opacity,
|
|
733
845
|
effect,
|
|
734
846
|
confidence: 'assumed_repo_local',
|
|
735
847
|
reason: effect === 'read_only' ? 'read_only' : 'repo_local_mutation',
|
|
736
848
|
signals: effect === 'read_only' ? ['read_only'] : ['repo_local_mutation'],
|
|
737
|
-
|
|
849
|
+
...withShellPolicyMetadata(buildPolicyInput({ effect, location: 'repo_local' }), policyMetadata),
|
|
850
|
+
});
|
|
738
851
|
}
|
|
739
852
|
if (effect === 'read_only') {
|
|
740
|
-
const readOnlyLocation =
|
|
741
|
-
?
|
|
853
|
+
const readOnlyLocation = pathContext.trustedCwd && pathContext.cwd
|
|
854
|
+
? effectiveLocation === 'unknown'
|
|
742
855
|
? 'repo_local'
|
|
743
|
-
:
|
|
856
|
+
: effectiveLocation
|
|
744
857
|
: 'unknown';
|
|
745
|
-
return
|
|
858
|
+
return allowVerdict({
|
|
746
859
|
location: readOnlyLocation,
|
|
747
860
|
opacity,
|
|
748
861
|
effect: 'read_only',
|
|
749
862
|
confidence: context.trustedCwd && context.cwd ? 'assumed_repo_local' : 'deterministic',
|
|
750
863
|
reason: 'read_only',
|
|
751
864
|
signals: ['read_only'],
|
|
752
|
-
|
|
865
|
+
...withShellPolicyMetadata(buildPolicyInput({ effect: 'read_only', location: readOnlyLocation }), policyMetadata),
|
|
866
|
+
});
|
|
753
867
|
}
|
|
754
868
|
if (allowOverride) {
|
|
755
|
-
return
|
|
869
|
+
return allowFromCustomOverride(opacity);
|
|
756
870
|
}
|
|
757
871
|
if (context.unknownLocalEffect === 'allow_flagged') {
|
|
758
|
-
return
|
|
759
|
-
location:
|
|
872
|
+
return allowVerdict({
|
|
873
|
+
location: effectiveLocation === 'unknown' ? 'repo_local' : effectiveLocation,
|
|
760
874
|
opacity,
|
|
761
875
|
effect: 'unknown',
|
|
762
876
|
confidence: 'assumed_repo_local',
|
|
763
877
|
reason: 'unknown_local_effect',
|
|
764
878
|
signals: ['unknown_local_effect'],
|
|
765
|
-
|
|
879
|
+
...withShellPolicyMetadata(buildPolicyInput({
|
|
880
|
+
effect: 'unknown',
|
|
881
|
+
location: effectiveLocation === 'unknown' ? 'repo_local' : effectiveLocation,
|
|
882
|
+
}), policyMetadata),
|
|
883
|
+
});
|
|
766
884
|
}
|
|
767
|
-
return
|
|
768
|
-
location:
|
|
885
|
+
return askVerdict({
|
|
886
|
+
location: effectiveLocation,
|
|
769
887
|
opacity,
|
|
770
888
|
effect,
|
|
771
889
|
confidence: 'deterministic',
|
|
772
890
|
reason: 'unknown_local_effect',
|
|
773
891
|
signals: ['unknown_local_effect'],
|
|
774
|
-
|
|
892
|
+
...withShellPolicyMetadata(buildPolicyInput({ effect, location: pathAnalysis.location }), policyMetadata),
|
|
893
|
+
});
|
|
775
894
|
}
|
|
776
895
|
function toVerdictResult(internal, command, context, fingerprintCwd = context.cwd) {
|
|
777
896
|
const commandRedacted = redactCommand(command);
|
|
@@ -787,6 +906,8 @@ function toVerdictResult(internal, command, context, fingerprintCwd = context.cw
|
|
|
787
906
|
fingerprint: verdictFingerprint(relative, commandRedacted),
|
|
788
907
|
signals: internal.signals,
|
|
789
908
|
judgeTrace: internal.judgeTrace,
|
|
909
|
+
capabilityRequests: internal.capabilityRequests,
|
|
910
|
+
authorizationDecision: internal.authorizationDecision,
|
|
790
911
|
};
|
|
791
912
|
}
|
|
792
913
|
export async function verdict(command, context) {
|