@guilz-dev/belay 0.6.0 → 0.8.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 +39 -20
- package/dist/adapters/claude/runtime-entry.js +54 -25
- package/dist/adapters/shared/gate-runtime.d.ts +7 -0
- package/dist/adapters/shared/gate-runtime.js +443 -225
- package/dist/bundle/claude-runtime.mjs +13027 -7271
- package/dist/bundle/codex-runtime.mjs +12700 -6977
- package/dist/bundle/cursor-runtime.mjs +12695 -6977
- package/dist/cli.js +37 -1
- package/dist/commands/approve.js +34 -10
- package/dist/commands/doctor.js +58 -0
- package/dist/commands/dogfood.js +1 -1
- package/dist/commands/explain.js +2 -2
- package/dist/commands/harvest.d.ts +0 -1
- package/dist/commands/harvest.js +1 -4
- package/dist/commands/metrics.js +6 -0
- package/dist/commands/quality.js +1 -3
- package/dist/commands/recovery-checkpoints.d.ts +164 -0
- package/dist/commands/recovery-checkpoints.js +347 -0
- package/dist/commands/revoke.js +19 -7
- package/dist/commands/status.js +7 -0
- package/dist/config-io.js +49 -18
- package/dist/conformance/guarantee-table.js +137 -9
- package/dist/conformance/types.d.ts +2 -0
- package/dist/core/approval-replay.d.ts +1 -1
- package/dist/core/approval-replay.js +4 -1
- package/dist/core/approval-service.d.ts +6 -1
- package/dist/core/approval-service.js +86 -27
- package/dist/core/approval.d.ts +2 -0
- package/dist/core/approval.js +6 -3
- package/dist/core/audit-metrics.d.ts +1 -0
- package/dist/core/audit-metrics.js +12 -4
- package/dist/core/audit-types.d.ts +16 -0
- package/dist/core/capability/approval-state-mutation.d.ts +19 -0
- package/dist/core/capability/approval-state-mutation.js +76 -3
- package/dist/core/capability/approval-v3.d.ts +18 -1
- package/dist/core/capability/approval-v3.js +63 -23
- package/dist/core/capability/attestation.d.ts +3 -0
- package/dist/core/capability/attestation.js +7 -0
- package/dist/core/capability/boundary-driver-container.d.ts +12 -0
- package/dist/core/capability/boundary-driver-container.js +101 -34
- package/dist/core/capability/boundary-driver.js +1 -0
- package/dist/core/capability/boundary-grant-materialize.d.ts +1 -0
- package/dist/core/capability/boundary-grant-materialize.js +15 -0
- package/dist/core/capability/boundary-run.d.ts +25 -0
- package/dist/core/capability/boundary-run.js +37 -9
- package/dist/core/capability/boundary-session.d.ts +4 -0
- package/dist/core/capability/boundary-session.js +9 -0
- package/dist/core/capability/boundary-workspace-mount.d.ts +5 -0
- package/dist/core/capability/boundary-workspace-mount.js +68 -0
- package/dist/core/capability/gate-policy-shadow.js +23 -1
- package/dist/core/capability/grant-consumption.d.ts +4 -0
- package/dist/core/capability/grant-consumption.js +11 -0
- package/dist/core/capability/grant-lease.d.ts +36 -1
- package/dist/core/capability/grant-lease.js +213 -16
- package/dist/core/capability/grant-match.js +3 -0
- package/dist/core/capability/index.d.ts +2 -2
- package/dist/core/capability/index.js +1 -1
- package/dist/core/capability/policy-engine.d.ts +38 -1
- package/dist/core/capability/policy-engine.js +388 -15
- package/dist/core/capability/request.d.ts +12 -0
- package/dist/core/capability/resolver.d.ts +0 -1
- package/dist/core/capability/resolver.js +0 -1
- package/dist/core/config.d.ts +20 -0
- package/dist/core/config.js +62 -2
- package/dist/core/effect-ir/audit.d.ts +9 -0
- package/dist/core/effect-ir/audit.js +87 -0
- package/dist/core/effect-ir/build.d.ts +31 -0
- package/dist/core/effect-ir/build.js +281 -0
- package/dist/core/effect-ir/index.d.ts +13 -0
- package/dist/core/effect-ir/index.js +9 -0
- package/dist/core/effect-ir/normalize.d.ts +4 -0
- package/dist/core/effect-ir/normalize.js +110 -0
- package/dist/core/effect-ir/package-exec-eval.d.ts +8 -0
- package/dist/core/effect-ir/package-exec-eval.js +152 -0
- package/dist/core/effect-ir/package-exec.d.ts +29 -0
- package/dist/core/effect-ir/package-exec.js +292 -0
- package/dist/core/effect-ir/policy.d.ts +14 -0
- package/dist/core/effect-ir/policy.js +164 -0
- package/dist/core/effect-ir/shell-build.d.ts +43 -0
- package/dist/core/effect-ir/shell-build.js +77 -0
- package/dist/core/effect-ir/shell-lower.d.ts +14 -0
- package/dist/core/effect-ir/shell-lower.js +1509 -0
- package/dist/core/effect-ir/types.d.ts +62 -0
- package/dist/core/effect-ir/types.js +1 -0
- package/dist/core/egress/allowlist.d.ts +1 -0
- package/dist/core/egress/allowlist.js +8 -0
- package/dist/core/egress-approval.js +45 -51
- package/dist/core/gate-contract.d.ts +2 -0
- package/dist/core/gate-contract.js +2 -0
- package/dist/core/gate-engine.js +82 -25
- package/dist/core/git-resource-identity.d.ts +26 -0
- package/dist/core/git-resource-identity.js +284 -0
- package/dist/core/harvest.d.ts +3 -7
- package/dist/core/harvest.js +4 -27
- package/dist/core/index.d.ts +5 -1
- package/dist/core/index.js +3 -1
- package/dist/core/judge-cloud-consent.js +29 -15
- package/dist/core/network-endpoint.d.ts +10 -0
- package/dist/core/network-endpoint.js +51 -0
- package/dist/core/path-utils.js +42 -23
- package/dist/core/process-runner.d.ts +10 -0
- package/dist/core/process-runner.js +124 -0
- package/dist/core/recover-advice.js +2 -1
- package/dist/core/recovery/artifact-store.d.ts +29 -0
- package/dist/core/recovery/artifact-store.js +336 -0
- package/dist/core/recovery/checkpoint.d.ts +25 -0
- package/dist/core/recovery/checkpoint.js +260 -0
- package/dist/core/recovery/index.d.ts +3 -1
- package/dist/core/recovery/index.js +2 -0
- package/dist/core/recovery/operator-guidance.d.ts +7 -0
- package/dist/core/recovery/operator-guidance.js +39 -0
- package/dist/core/recovery/reconcile.d.ts +3 -0
- package/dist/core/recovery/reconcile.js +48 -0
- package/dist/core/recovery/resource-identity.d.ts +4 -0
- package/dist/core/recovery/resource-identity.js +34 -0
- package/dist/core/recovery/restore.d.ts +13 -0
- package/dist/core/recovery/restore.js +114 -0
- package/dist/core/recovery/snapshot-node.d.ts +27 -0
- package/dist/core/recovery/snapshot-node.js +261 -0
- package/dist/core/recovery/types.d.ts +85 -1
- package/dist/core/shell-substitution.d.ts +1 -0
- package/dist/core/shell-substitution.js +61 -0
- package/dist/core/standing-allow.d.ts +1 -2
- package/dist/core/standing-allow.js +3 -20
- package/dist/core/transactional/apply-observed-changes.d.ts +13 -0
- package/dist/core/transactional/apply-observed-changes.js +275 -0
- package/dist/core/transactional/backend-selector.d.ts +8 -0
- package/dist/core/transactional/backend-selector.js +118 -0
- package/dist/core/transactional/backend.d.ts +55 -0
- package/dist/core/transactional/backend.js +1 -0
- package/dist/core/transactional/file-checkpoint-backend.d.ts +3 -0
- package/dist/core/transactional/file-checkpoint-backend.js +278 -0
- package/dist/core/transactional/file-checkpoint-git.d.ts +11 -0
- package/dist/core/transactional/file-checkpoint-git.js +217 -0
- package/dist/core/transactional/file-checkpoint-staging.d.ts +13 -0
- package/dist/core/transactional/file-checkpoint-staging.js +54 -0
- package/dist/core/transactional/file-clone.d.ts +15 -0
- package/dist/core/transactional/file-clone.js +139 -0
- package/dist/core/transactional/file-tree-path.d.ts +7 -0
- package/dist/core/transactional/file-tree-path.js +50 -0
- package/dist/core/transactional/file-tree.d.ts +41 -0
- package/dist/core/transactional/file-tree.js +186 -0
- package/dist/core/transactional/git-worktree-backend.d.ts +2 -0
- package/dist/core/transactional/git-worktree-backend.js +50 -0
- package/dist/core/transactional/git-worktree.d.ts +9 -11
- package/dist/core/transactional/git-worktree.js +24 -119
- package/dist/core/transactional/index.d.ts +11 -0
- package/dist/core/transactional/index.js +10 -0
- package/dist/core/transactional/reasons.js +1 -0
- package/dist/core/transactional/runner.js +157 -25
- package/dist/core/transactional/snapshot-node.d.ts +26 -0
- package/dist/core/transactional/snapshot-node.js +87 -0
- package/dist/core/transactional/types.d.ts +17 -1
- package/dist/core/types.d.ts +13 -1
- package/dist/core/verdict/adapter.js +23 -15
- package/dist/core/verdict/containment.js +4 -0
- package/dist/core/verdict/egress-classify.d.ts +12 -0
- package/dist/core/verdict/egress-classify.js +746 -0
- package/dist/core/verdict/git-classifier.d.ts +12 -0
- package/dist/core/verdict/git-classifier.js +320 -0
- package/dist/core/verdict/launcher-resolve.js +79 -37
- package/dist/core/verdict/parser.d.ts +2 -0
- package/dist/core/verdict/parser.js +95 -1
- package/dist/core/verdict/types.d.ts +6 -2
- package/dist/core/verdict/verdict.js +103 -935
- package/dist/corpus/must-allow-commands.d.ts +2 -1
- package/dist/corpus/must-allow-commands.js +2 -1
- package/dist/corpus/runtime-match.d.ts +2 -1
- package/dist/corpus/runtime-match.js +2 -1
- package/dist/corpus/types.d.ts +2 -2
- package/dist/templates.js +7 -2
- package/dist/types.d.ts +11 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/skills/belay/SKILL.md +8 -3
- package/skills/belay/belay-approve.md +10 -7
- package/dist/core/verdict/overrides.d.ts +0 -7
- package/dist/core/verdict/overrides.js +0 -37
- package/dist/core/verdict/shell-policy.d.ts +0 -25
- package/dist/core/verdict/shell-policy.js +0 -40
- package/dist/corpus/standing-allow-catalog.generated.d.ts +0 -13
- package/dist/corpus/standing-allow-catalog.generated.js +0 -99
|
@@ -1,12 +1,200 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { isGitMetadataPath } from '../git-resource-identity.js';
|
|
3
4
|
import { matchesSensitivePath } from '../glob.js';
|
|
5
|
+
import { parseNetworkEndpoint } from '../network-endpoint.js';
|
|
4
6
|
import { canonicalPath, pathWithinRoot, resolveWorkspaceRootMatch } from '../path-utils.js';
|
|
7
|
+
import { tokenizeShell } from '../shell-tokenizer.js';
|
|
5
8
|
import { BOUNDARY_GRANT_ISSUER_CONTAINER, isPathWithinBoundaryMount, materializeContainerBoundaryGrant, } from './boundary-grant-materialize.js';
|
|
6
9
|
import { boundaryVerifiedAllowEnabled } from './boundary-profile.js';
|
|
7
10
|
import { isGrantScopeTooBroad } from './grant.js';
|
|
8
11
|
import { broadGrantTargetsRequest, grantMatchesRequest } from './grant-match.js';
|
|
9
12
|
import { CAPABILITY_REQUEST_VERSION, } from './request.js';
|
|
13
|
+
const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '0:0:0:0:0:0:0:1']);
|
|
14
|
+
const DESTRUCTIVE_EFFECT_SIGNALS = new Set([
|
|
15
|
+
'git_history_destructive',
|
|
16
|
+
'git.destructive_history_write',
|
|
17
|
+
'git.destructive_worktree_write',
|
|
18
|
+
]);
|
|
19
|
+
const HIGH_STAKES_EFFECT_PATHS = [
|
|
20
|
+
/(?:^|[/\\])(?:\.env(?:\..*)?|credentials?(?:\.json)?|secrets?|authorized_keys|id_(?:rsa|dsa|ecdsa|ed25519)|[^/\\]+\.pem|\.ssh[/\\]config|\.zshrc|\.bashrc|\.git-credentials|\.npmrc|\.netrc|\.kube[/\\]config|\.docker[/\\]config\.json|\.gnupg(?:[/\\].*)?|\.pypirc)(?:$|[/\\])/i,
|
|
21
|
+
/(?:^|[/\\])application_default_credentials\.json$/i,
|
|
22
|
+
/(?:^|[/\\])\.config[/\\]gcloud[/\\](?:application_default_credentials\.json|credentials\.db|access_tokens\.db|legacy_credentials(?:[/\\].*)?)$/i,
|
|
23
|
+
/(?:^|[/\\])\.aws[/\\](?:credentials|config)$/i,
|
|
24
|
+
/(?:^|[/\\])\.azure[/\\](?:accessTokens\.json|azureProfile\.json|msal_token_cache\.(?:bin|json)|service_principal_entries\.json)$/i,
|
|
25
|
+
/(?:^|[/\\])\.terraform\.d[/\\]credentials\.tfrc\.json$/i,
|
|
26
|
+
/(?:^|[/\\])\.config[/\\]gh[/\\]hosts\.ya?ml$/i,
|
|
27
|
+
/(?:^|[/\\])\.oci[/\\]config$/i,
|
|
28
|
+
/(?:^|[/\\])\.config[/\\]doctl[/\\]config\.ya?ml$/i,
|
|
29
|
+
];
|
|
30
|
+
function effectDecision(effectDisposition, reason, matchedRule, signals) {
|
|
31
|
+
return {
|
|
32
|
+
effectDisposition,
|
|
33
|
+
outcome: effectDisposition === 'ask' ? 'require_approval' : 'allow',
|
|
34
|
+
reason,
|
|
35
|
+
signals: [...signals],
|
|
36
|
+
matchedRule,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function effectPathIsLocal(target, context) {
|
|
40
|
+
const resolved = resolveCapabilityPath(target, context.cwd);
|
|
41
|
+
return (resolveWorkspaceRootMatch(context.repoRoot, [...(context.trustedWorkspaceRoots ?? [])], resolved) !== null);
|
|
42
|
+
}
|
|
43
|
+
function effectPathIsHighStakes(target, context) {
|
|
44
|
+
const resolved = resolveCapabilityPath(target, context.cwd);
|
|
45
|
+
if (HIGH_STAKES_EFFECT_PATHS.some((pattern) => pattern.test(resolved))) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (context.protectedArtifactRoots?.some((root) => pathWithinRoot(canonicalPath(root), resolved))) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (isGitMetadataPath(resolved, context.repoRoot)) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
const workspaceMatch = resolveWorkspaceRootMatch(context.repoRoot, [...(context.trustedWorkspaceRoots ?? [])], resolved);
|
|
55
|
+
if (!workspaceMatch) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return matchesSensitivePath(workspaceMatch.relativePath.replaceAll('\\', '/'), [
|
|
59
|
+
...(context.sensitivePaths ?? []),
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Policy for a lowered shell effect. It intentionally ignores executable
|
|
64
|
+
* command names, segment heads, command text, and config allowlists.
|
|
65
|
+
*/
|
|
66
|
+
export function evaluateEffectRequirementPolicy(requirement, context) {
|
|
67
|
+
const signals = [...requirement.evidence.signals];
|
|
68
|
+
const capabilityRequest = context.capabilityRequest ?? {
|
|
69
|
+
version: CAPABILITY_REQUEST_VERSION,
|
|
70
|
+
principal: { repoRoot: context.repoRoot },
|
|
71
|
+
action: requirement.action,
|
|
72
|
+
resource: requirement.resource,
|
|
73
|
+
context: {
|
|
74
|
+
cwd: context.cwd,
|
|
75
|
+
inputFingerprint: 'effect-plan',
|
|
76
|
+
hookKind: 'shell',
|
|
77
|
+
analysisBasis: [...requirement.evidence.basis],
|
|
78
|
+
},
|
|
79
|
+
evidence: {
|
|
80
|
+
level: requirement.evidence.level,
|
|
81
|
+
signals,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
if (signals.includes('forged_grant')) {
|
|
85
|
+
return effectDecision('ask', 'grant_forgery', 'forbid.grant_forgery', [
|
|
86
|
+
...signals,
|
|
87
|
+
'grant_forgery',
|
|
88
|
+
]);
|
|
89
|
+
}
|
|
90
|
+
if (context.grants?.some((grant) => isGrantScopeTooBroad(grant) && broadGrantTargetsRequest(grant, capabilityRequest))) {
|
|
91
|
+
return effectDecision('ask', 'grant_scope_too_broad', 'forbid.broad_grant', [
|
|
92
|
+
...signals,
|
|
93
|
+
'grant_scope_too_broad',
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
const matchedGrant = requirement.resource.kind === 'executable'
|
|
97
|
+
? null
|
|
98
|
+
: findFreshGrant(capabilityRequest, [...(context.grants ?? [])]);
|
|
99
|
+
if (matchedGrant) {
|
|
100
|
+
return effectDecision('allow', 'capability_grant', 'grant.exact', grantDecisionSignals(capabilityRequest, matchedGrant));
|
|
101
|
+
}
|
|
102
|
+
if (requirement.evidence.level === 'indeterminate' ||
|
|
103
|
+
requirement.action === 'indeterminate' ||
|
|
104
|
+
requirement.tag === 'indeterminate') {
|
|
105
|
+
return effectDecision('ask', 'indeterminate_effect', 'effect.indeterminate', [
|
|
106
|
+
...signals,
|
|
107
|
+
'indeterminate',
|
|
108
|
+
]);
|
|
109
|
+
}
|
|
110
|
+
if (signals.some((signal) => DESTRUCTIVE_EFFECT_SIGNALS.has(signal))) {
|
|
111
|
+
return effectDecision('ask', 'git_history_destructive', 'effect.destructive', signals);
|
|
112
|
+
}
|
|
113
|
+
if (signals.includes('belay_control_plane_command')) {
|
|
114
|
+
return effectDecision('allow', 'belay_control_plane_command', 'effect.belay_control_plane_command', signals);
|
|
115
|
+
}
|
|
116
|
+
switch (requirement.action) {
|
|
117
|
+
case 'fs.read':
|
|
118
|
+
if (requirement.resource.kind === 'path' &&
|
|
119
|
+
effectPathIsHighStakes(requirement.resource.path, context)) {
|
|
120
|
+
return effectDecision('ask', 'high_stakes_path', 'effect.fs_read_high_stakes', [
|
|
121
|
+
...signals,
|
|
122
|
+
'sensitive_path_read',
|
|
123
|
+
]);
|
|
124
|
+
}
|
|
125
|
+
return effectDecision('allow', 'read_only', 'effect.fs_read', signals);
|
|
126
|
+
case 'fs.write':
|
|
127
|
+
if (requirement.resource.kind === 'package-cache') {
|
|
128
|
+
return effectDecision('allow_flagged', 'repo_local_mutation', 'effect.package_cache_write', signals);
|
|
129
|
+
}
|
|
130
|
+
if (requirement.resource.kind !== 'path') {
|
|
131
|
+
return effectDecision('ask', 'indeterminate_effect', 'effect.fs_write_unknown', signals);
|
|
132
|
+
}
|
|
133
|
+
if (effectPathIsHighStakes(requirement.resource.path, context)) {
|
|
134
|
+
return effectDecision('ask', 'high_stakes_path', 'effect.fs_write_high_stakes', [
|
|
135
|
+
...signals,
|
|
136
|
+
'tier1_catastrophic',
|
|
137
|
+
...(!effectPathIsLocal(requirement.resource.path, context)
|
|
138
|
+
? ['outside_repo_secret_credential_path']
|
|
139
|
+
: []),
|
|
140
|
+
]);
|
|
141
|
+
}
|
|
142
|
+
return effectPathIsLocal(requirement.resource.path, context)
|
|
143
|
+
? effectDecision('allow_flagged', 'repo_local_mutation', 'effect.fs_write_local', signals)
|
|
144
|
+
: effectDecision('ask', 'outside_repo_mutation', 'effect.fs_write_outside', signals);
|
|
145
|
+
case 'process.exec':
|
|
146
|
+
if (requirement.resource.kind !== 'executable') {
|
|
147
|
+
return effectDecision('ask', 'indeterminate_effect', 'effect.process_unknown', signals);
|
|
148
|
+
}
|
|
149
|
+
if (requirement.resource.operation === 'inspect') {
|
|
150
|
+
return effectDecision('allow', 'read_only', 'effect.process_inspect', signals);
|
|
151
|
+
}
|
|
152
|
+
if (requirement.resource.operation === 'spawn') {
|
|
153
|
+
return effectDecision('allow_flagged', 'repo_local_mutation', 'effect.process_spawn', signals);
|
|
154
|
+
}
|
|
155
|
+
return effectDecision('ask', 'high_stakes_path', 'effect.process_signal', signals);
|
|
156
|
+
case 'network.connect': {
|
|
157
|
+
if (requirement.tag === 'network.acquire' ||
|
|
158
|
+
requirement.resource.kind !== 'network' ||
|
|
159
|
+
!requirement.resource.mode ||
|
|
160
|
+
!requirement.resource.payload) {
|
|
161
|
+
return effectDecision('ask', 'external_effect', 'effect.network_unknown', signals);
|
|
162
|
+
}
|
|
163
|
+
const loopback = LOOPBACK_HOSTS.has(requirement.resource.host.replace(/^\[|\]$/g, '').toLowerCase());
|
|
164
|
+
if (requirement.resource.payload === 'secret') {
|
|
165
|
+
return effectDecision('ask', 'high_stakes_path', 'effect.network_secret_payload', [
|
|
166
|
+
...signals,
|
|
167
|
+
'secret_payload_send',
|
|
168
|
+
]);
|
|
169
|
+
}
|
|
170
|
+
if (loopback && requirement.resource.mode === 'mutate') {
|
|
171
|
+
return effectDecision('allow_flagged', 'repo_local_mutation', 'effect.network_loopback_mutation', signals);
|
|
172
|
+
}
|
|
173
|
+
if (requirement.resource.mode === 'read' && requirement.resource.payload === 'none') {
|
|
174
|
+
return effectDecision('allow', 'read_only', 'effect.network_read', signals);
|
|
175
|
+
}
|
|
176
|
+
return effectDecision('ask', 'external_effect', 'effect.network_remote_mutation', signals);
|
|
177
|
+
}
|
|
178
|
+
case 'git.ref.write':
|
|
179
|
+
if (requirement.resource.kind !== 'git-ref' || !requirement.resource.scope) {
|
|
180
|
+
return effectDecision('ask', 'indeterminate_effect', 'effect.git_ref_unknown', signals);
|
|
181
|
+
}
|
|
182
|
+
if (requirement.resource.scope === 'remote') {
|
|
183
|
+
return effectDecision('ask', 'external_effect', 'effect.git_ref_remote', signals);
|
|
184
|
+
}
|
|
185
|
+
if (requirement.resource.repoPath &&
|
|
186
|
+
!effectPathIsLocal(requirement.resource.repoPath, context)) {
|
|
187
|
+
return effectDecision('ask', 'outside_repo_mutation', 'effect.git_ref_outside', signals);
|
|
188
|
+
}
|
|
189
|
+
return effectDecision('allow_flagged', 'repo_local_mutation', 'effect.git_ref_local', signals);
|
|
190
|
+
case 'secret.read':
|
|
191
|
+
return effectDecision('ask', 'high_stakes_path', 'effect.secret_read', signals);
|
|
192
|
+
case 'control_plane.write':
|
|
193
|
+
return effectDecision('ask', 'control_plane_mutation', 'effect.control_plane_write', signals);
|
|
194
|
+
default:
|
|
195
|
+
return effectDecision('ask', 'indeterminate_effect', 'effect.unknown', signals);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
10
198
|
function enrichAuthWithMaterializedGrants(request, config, auth) {
|
|
11
199
|
if (!auth?.attestation || !boundaryVerifiedAllowEnabled(auth.attestation)) {
|
|
12
200
|
return auth;
|
|
@@ -116,21 +304,27 @@ function resourcePathForShellAnalysis(analysis) {
|
|
|
116
304
|
const resolved = targets.map((target) => analysis.resolvedPathTargets?.length
|
|
117
305
|
? canonicalPath(target)
|
|
118
306
|
: resolveCapabilityPath(target, analysis.cwd));
|
|
307
|
+
const [first] = resolved;
|
|
308
|
+
if (!first) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
119
311
|
if (analysis.effect === 'local_mutation') {
|
|
120
312
|
const outside = resolved.find((candidate) => !pathWithinRoot(repoRoot, candidate));
|
|
121
|
-
return outside ?? resolved
|
|
313
|
+
return outside ?? resolved.at(-1) ?? first;
|
|
122
314
|
}
|
|
123
|
-
return
|
|
315
|
+
return first;
|
|
124
316
|
}
|
|
125
317
|
function resourceForShellAnalysis(analysis) {
|
|
126
318
|
if (isGitRefWrite(analysis)) {
|
|
127
319
|
return { kind: 'git-ref', ref: 'push' };
|
|
128
320
|
}
|
|
129
|
-
if (analysis
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
321
|
+
if (isNetworkShellAnalysis(analysis)) {
|
|
322
|
+
const exact = exactNetworkResources(analysis);
|
|
323
|
+
return ((exact.length === 1 ? exact[0] : undefined) ?? {
|
|
324
|
+
kind: 'network',
|
|
325
|
+
host: '*',
|
|
326
|
+
protocol: 'unknown',
|
|
327
|
+
});
|
|
134
328
|
}
|
|
135
329
|
const resourcePath = resourcePathForShellAnalysis(analysis);
|
|
136
330
|
if (resourcePath) {
|
|
@@ -138,8 +332,114 @@ function resourceForShellAnalysis(analysis) {
|
|
|
138
332
|
}
|
|
139
333
|
return { kind: 'executable', command: analysis.segmentHead };
|
|
140
334
|
}
|
|
335
|
+
function isNetworkShellAnalysis(analysis) {
|
|
336
|
+
return (analysis.egressClass === 'ambiguous' ||
|
|
337
|
+
analysis.egressClass === 'read' ||
|
|
338
|
+
analysis.egressClass === 'destructive' ||
|
|
339
|
+
analysis.location === 'external');
|
|
340
|
+
}
|
|
341
|
+
const SCP_STYLE_COMMANDS = new Set(['git', 'rsync', 'scp']);
|
|
342
|
+
const PACKAGE_SPEC_COMMANDS = new Set(['bun', 'npm', 'npx', 'pnpm', 'yarn']);
|
|
343
|
+
const NETWORK_VALUE_OPTIONS = new Set(['--registry', '--url']);
|
|
344
|
+
const GIT_OPTIONS_WITH_VALUE = new Set([
|
|
345
|
+
'-C',
|
|
346
|
+
'-b',
|
|
347
|
+
'-c',
|
|
348
|
+
'-o',
|
|
349
|
+
'--branch',
|
|
350
|
+
'--config',
|
|
351
|
+
'--depth',
|
|
352
|
+
'--filter',
|
|
353
|
+
'--git-dir',
|
|
354
|
+
'--namespace',
|
|
355
|
+
'--origin',
|
|
356
|
+
'--reference',
|
|
357
|
+
'--separate-git-dir',
|
|
358
|
+
'--upload-pack',
|
|
359
|
+
'--work-tree',
|
|
360
|
+
]);
|
|
361
|
+
function gitRemoteOperand(tokens) {
|
|
362
|
+
let cursor = 1;
|
|
363
|
+
while (cursor < tokens.length) {
|
|
364
|
+
const token = tokens[cursor];
|
|
365
|
+
if (!token) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
if (GIT_OPTIONS_WITH_VALUE.has(token)) {
|
|
369
|
+
cursor += 2;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (token.startsWith('-')) {
|
|
373
|
+
cursor += 1;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
const subcommand = tokens[cursor];
|
|
379
|
+
if (!subcommand) {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
const positionals = [];
|
|
383
|
+
let skipOptionValue = false;
|
|
384
|
+
for (const token of tokens.slice(cursor + 1)) {
|
|
385
|
+
if (skipOptionValue) {
|
|
386
|
+
skipOptionValue = false;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (GIT_OPTIONS_WITH_VALUE.has(token)) {
|
|
390
|
+
skipOptionValue = true;
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (token.startsWith('-')) {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
positionals.push(token);
|
|
397
|
+
}
|
|
398
|
+
if (['clone', 'fetch', 'ls-remote', 'pull', 'push'].includes(subcommand)) {
|
|
399
|
+
return positionals[0] ?? null;
|
|
400
|
+
}
|
|
401
|
+
if (subcommand === 'remote' && positionals[0] === 'add') {
|
|
402
|
+
return positionals[2] ?? null;
|
|
403
|
+
}
|
|
404
|
+
if (subcommand === 'submodule' && positionals[0] === 'add') {
|
|
405
|
+
return positionals[1] ?? null;
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
function exactNetworkResources(analysis) {
|
|
410
|
+
const resources = new Map();
|
|
411
|
+
const shellTokens = tokenizeShell(analysis.command);
|
|
412
|
+
const gitRemote = analysis.segmentHead === 'git' ? gitRemoteOperand(shellTokens) : null;
|
|
413
|
+
for (const rawToken of shellTokens) {
|
|
414
|
+
const optionSeparator = rawToken.startsWith('-') ? rawToken.indexOf('=') : -1;
|
|
415
|
+
const optionName = optionSeparator === -1 ? null : rawToken.slice(0, optionSeparator);
|
|
416
|
+
const token = optionSeparator !== -1 && optionName && NETWORK_VALUE_OPTIONS.has(optionName)
|
|
417
|
+
? rawToken.slice(optionSeparator + 1)
|
|
418
|
+
: rawToken;
|
|
419
|
+
const endpoint = parseNetworkEndpoint(token, {
|
|
420
|
+
allowHostedGitShorthand: PACKAGE_SPEC_COMMANDS.has(analysis.segmentHead),
|
|
421
|
+
allowScpStyle: SCP_STYLE_COMMANDS.has(analysis.segmentHead) &&
|
|
422
|
+
(analysis.segmentHead !== 'git' || rawToken === gitRemote),
|
|
423
|
+
});
|
|
424
|
+
if (!endpoint) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
const resource = { kind: 'network', ...endpoint };
|
|
428
|
+
resources.set(`${resource.host}:${resource.port ?? ''}:${resource.protocol}`, resource);
|
|
429
|
+
}
|
|
430
|
+
return [...resources.values()];
|
|
431
|
+
}
|
|
141
432
|
export function buildShellCapabilityRequest(analysis) {
|
|
142
|
-
|
|
433
|
+
const [request] = buildShellCapabilityRequests(analysis);
|
|
434
|
+
if (!request) {
|
|
435
|
+
throw new Error('shell analysis produced no capability request');
|
|
436
|
+
}
|
|
437
|
+
return request;
|
|
438
|
+
}
|
|
439
|
+
export function buildShellCapabilityRequests(analysis) {
|
|
440
|
+
const resources = isNetworkShellAnalysis(analysis) ? exactNetworkResources(analysis) : [];
|
|
441
|
+
const selectedResources = resources.length > 0 ? resources : [resourceForShellAnalysis(analysis)];
|
|
442
|
+
return selectedResources.map((resource) => ({
|
|
143
443
|
version: CAPABILITY_REQUEST_VERSION,
|
|
144
444
|
principal: {
|
|
145
445
|
adapter: analysis.adapter,
|
|
@@ -147,7 +447,7 @@ export function buildShellCapabilityRequest(analysis) {
|
|
|
147
447
|
sessionHash: hashSession(`${analysis.repoRoot}:${analysis.cwd}`),
|
|
148
448
|
},
|
|
149
449
|
action: actionForShellAnalysis(analysis),
|
|
150
|
-
resource
|
|
450
|
+
resource,
|
|
151
451
|
context: {
|
|
152
452
|
cwd: analysis.cwd,
|
|
153
453
|
inputFingerprint: analysis.inputFingerprint,
|
|
@@ -163,7 +463,7 @@ export function buildShellCapabilityRequest(analysis) {
|
|
|
163
463
|
level: evidenceLevelForOpacity(analysis.opacity),
|
|
164
464
|
signals: [...analysis.signals],
|
|
165
465
|
},
|
|
166
|
-
};
|
|
466
|
+
}));
|
|
167
467
|
}
|
|
168
468
|
function actionForFileMutation(analysis) {
|
|
169
469
|
if (analysis.locationLabel === 'control_plane') {
|
|
@@ -233,6 +533,16 @@ function shellLocationFromRequest(request) {
|
|
|
233
533
|
function isRepoLocalShellLocation(request) {
|
|
234
534
|
return shellLocationFromRequest(request) === 'repo_local';
|
|
235
535
|
}
|
|
536
|
+
function isRepoLocalPackageExec(request) {
|
|
537
|
+
if (request.action !== 'process.exec' || request.resource.kind !== 'executable') {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
if (!request.evidence.signals.includes('package_exec.local_bin_resolved')) {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
const commandPath = canonicalPath(request.resource.command);
|
|
544
|
+
return path.isAbsolute(commandPath) && pathWithinRoot(request.principal.repoRoot, commandPath);
|
|
545
|
+
}
|
|
236
546
|
function isRepoLocalRoutineWrite(request, sensitivePaths) {
|
|
237
547
|
if (!isRepoLocalShellLocation(request)) {
|
|
238
548
|
return false;
|
|
@@ -297,6 +607,14 @@ function builtInRule(request, context) {
|
|
|
297
607
|
matchedRule: 'builtin.subagent',
|
|
298
608
|
};
|
|
299
609
|
}
|
|
610
|
+
if (isRepoLocalPackageExec(request)) {
|
|
611
|
+
return {
|
|
612
|
+
outcome: 'allow',
|
|
613
|
+
reason: 'read_only',
|
|
614
|
+
signals: [...request.evidence.signals, 'repo_local_exec'],
|
|
615
|
+
matchedRule: 'builtin.repo_local_exec',
|
|
616
|
+
};
|
|
617
|
+
}
|
|
300
618
|
if (request.action === 'secret.read' || request.action === 'git.ref.write') {
|
|
301
619
|
return {
|
|
302
620
|
outcome: 'require_approval',
|
|
@@ -456,14 +774,69 @@ export function getDefaultPolicyEngine() {
|
|
|
456
774
|
export function policyDecisionRequiresAsk(decision) {
|
|
457
775
|
return decision.outcome === 'require_approval' || decision.outcome === 'deny';
|
|
458
776
|
}
|
|
777
|
+
function policyOutcomeRank(outcome) {
|
|
778
|
+
switch (outcome) {
|
|
779
|
+
case 'deny':
|
|
780
|
+
return 3;
|
|
781
|
+
case 'require_approval':
|
|
782
|
+
return 2;
|
|
783
|
+
case 'allow':
|
|
784
|
+
return 1;
|
|
785
|
+
default:
|
|
786
|
+
return 0;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
export function combinePolicyDecisions(decisions) {
|
|
790
|
+
if (!decisions.length) {
|
|
791
|
+
return {
|
|
792
|
+
outcome: 'allow',
|
|
793
|
+
reason: 'read_only',
|
|
794
|
+
signals: [],
|
|
795
|
+
matchedRule: 'plan.conjunction',
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
const sorted = [...decisions].sort((left, right) => policyOutcomeRank(right.outcome) - policyOutcomeRank(left.outcome));
|
|
799
|
+
const worst = sorted[0];
|
|
800
|
+
if (!worst) {
|
|
801
|
+
return {
|
|
802
|
+
outcome: 'allow',
|
|
803
|
+
reason: 'read_only',
|
|
804
|
+
signals: [],
|
|
805
|
+
matchedRule: 'plan.conjunction',
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
return worst;
|
|
809
|
+
}
|
|
810
|
+
export function evaluateCapabilityRequestsPolicy(requests, config, auth, trustedWorkspaceRoots) {
|
|
811
|
+
let workingAuth = auth;
|
|
812
|
+
const decisions = [];
|
|
813
|
+
for (const request of requests) {
|
|
814
|
+
const enriched = enrichAuthWithMaterializedGrants(request, config, workingAuth);
|
|
815
|
+
const decision = getDefaultPolicyEngine().evaluate(request, buildAuthorizationContext(config, trustedWorkspaceRoots, enriched));
|
|
816
|
+
decisions.push(decision);
|
|
817
|
+
const previousCount = workingAuth?.grants?.length ?? 0;
|
|
818
|
+
const nextCount = enriched?.grants?.length ?? 0;
|
|
819
|
+
if (nextCount > previousCount) {
|
|
820
|
+
workingAuth = enriched;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return {
|
|
824
|
+
decisions,
|
|
825
|
+
decision: combinePolicyDecisions(decisions),
|
|
826
|
+
};
|
|
827
|
+
}
|
|
459
828
|
export function evaluateShellPolicy(analysis, config, auth) {
|
|
460
|
-
const
|
|
461
|
-
const
|
|
829
|
+
const requests = buildShellCapabilityRequests(analysis);
|
|
830
|
+
const enrichedAuth = {
|
|
462
831
|
...auth,
|
|
463
832
|
sensitivePaths: auth?.sensitivePaths ?? analysis.sensitivePaths,
|
|
464
|
-
}
|
|
465
|
-
const decision =
|
|
466
|
-
|
|
833
|
+
};
|
|
834
|
+
const { decision } = evaluateCapabilityRequestsPolicy(requests, config, enrichedAuth, analysis.trustedWorkspaceRoots);
|
|
835
|
+
const [request] = requests;
|
|
836
|
+
if (!request) {
|
|
837
|
+
throw new Error('shell analysis produced no capability request');
|
|
838
|
+
}
|
|
839
|
+
return { request, requests, decision };
|
|
467
840
|
}
|
|
468
841
|
export function evaluateFileMutationPolicy(analysis, config, auth) {
|
|
469
842
|
const request = buildFileMutationCapabilityRequest(analysis);
|
|
@@ -2,6 +2,10 @@ export declare const CAPABILITY_REQUEST_VERSION: 1;
|
|
|
2
2
|
export type CapabilityAction = 'fs.read' | 'fs.write' | 'process.exec' | 'network.connect' | 'secret.read' | 'git.ref.write' | 'control_plane.write' | 'indeterminate';
|
|
3
3
|
export type CapabilityEvidenceLevel = 'certain' | 'possible' | 'indeterminate';
|
|
4
4
|
export type CapabilityHookKind = 'shell' | 'tool' | 'subagent';
|
|
5
|
+
export type NetworkMode = 'read' | 'mutate' | 'ambiguous';
|
|
6
|
+
export type NetworkPayload = 'none' | 'present' | 'secret';
|
|
7
|
+
export type ProcessOperation = 'inspect' | 'spawn' | 'signal';
|
|
8
|
+
export type GitRefScope = 'local' | 'remote';
|
|
5
9
|
export interface CapabilityPrincipal {
|
|
6
10
|
adapter?: string;
|
|
7
11
|
repoRoot: string;
|
|
@@ -15,12 +19,20 @@ export type CapabilityResource = {
|
|
|
15
19
|
host: string;
|
|
16
20
|
port?: number;
|
|
17
21
|
protocol?: string;
|
|
22
|
+
mode?: NetworkMode;
|
|
23
|
+
payload?: NetworkPayload;
|
|
18
24
|
} | {
|
|
19
25
|
kind: 'executable';
|
|
20
26
|
command: string;
|
|
27
|
+
operation?: ProcessOperation;
|
|
28
|
+
} | {
|
|
29
|
+
kind: 'package-cache';
|
|
30
|
+
manager: 'npm' | 'pnpm';
|
|
21
31
|
} | {
|
|
22
32
|
kind: 'git-ref';
|
|
23
33
|
ref: string;
|
|
34
|
+
scope?: GitRefScope;
|
|
35
|
+
repoPath?: string;
|
|
24
36
|
} | {
|
|
25
37
|
kind: 'unknown';
|
|
26
38
|
};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { evaluateSegmentShellPolicy, type SegmentShellPolicyInput, } from '../verdict/shell-policy.js';
|
|
2
1
|
export { BOUNDARY_PROFILE_L3_POLICY, checkGatedActionLimits } from './limits.js';
|
|
3
2
|
export { type CapabilityAuthorizationMetadata, policyReasonToLegacyReason, singleCapabilityMetadata, } from './policy-bridge.js';
|
|
4
3
|
export { buildFileMutationCapabilityRequest, buildShellCapabilityRequest, buildSubagentCapabilityRequest, evaluateFileMutationPolicy, evaluateShellPolicy, evaluateSubagentPolicy, type FileMutationCapabilityAnalysis, policyDecisionRequiresAsk, type ShellCapabilityAnalysis, type SubagentCapabilityAnalysis, } from './policy-engine.js';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { evaluateSegmentShellPolicy, } from '../verdict/shell-policy.js';
|
|
2
1
|
export { BOUNDARY_PROFILE_L3_POLICY, checkGatedActionLimits } from './limits.js';
|
|
3
2
|
export { policyReasonToLegacyReason, singleCapabilityMetadata, } from './policy-bridge.js';
|
|
4
3
|
export { buildFileMutationCapabilityRequest, buildShellCapabilityRequest, buildSubagentCapabilityRequest, evaluateFileMutationPolicy, evaluateShellPolicy, evaluateSubagentPolicy, policyDecisionRequiresAsk, } from './policy-engine.js';
|
package/dist/core/config.d.ts
CHANGED
|
@@ -46,6 +46,15 @@ export interface BelayModelAssistConfig {
|
|
|
46
46
|
model?: string;
|
|
47
47
|
timeoutMs?: number;
|
|
48
48
|
}
|
|
49
|
+
export interface BelayFileCheckpointConfig {
|
|
50
|
+
enabled: boolean;
|
|
51
|
+
allowNonGit: boolean;
|
|
52
|
+
maxFiles: number;
|
|
53
|
+
maxSourceBytes: number;
|
|
54
|
+
maxWorkspaceBytes: number;
|
|
55
|
+
prepareTimeoutMs: number;
|
|
56
|
+
copyConcurrency: number;
|
|
57
|
+
}
|
|
49
58
|
export interface BelayTransactionalConfig {
|
|
50
59
|
enabled: boolean;
|
|
51
60
|
minConfidence: number;
|
|
@@ -55,6 +64,14 @@ export interface BelayTransactionalConfig {
|
|
|
55
64
|
gates: {
|
|
56
65
|
shell: boolean;
|
|
57
66
|
};
|
|
67
|
+
fileCheckpoint: BelayFileCheckpointConfig;
|
|
68
|
+
checkpoint?: {
|
|
69
|
+
enabled: boolean;
|
|
70
|
+
appliedRetentionHours: number;
|
|
71
|
+
restoredRetentionHours: number;
|
|
72
|
+
maxCheckpoints: number;
|
|
73
|
+
maxBytes: number;
|
|
74
|
+
};
|
|
58
75
|
}
|
|
59
76
|
export interface BelayPolicyConfig {
|
|
60
77
|
unknownLocalEffect: UnknownLocalEffectPolicy;
|
|
@@ -196,6 +213,9 @@ export type BelayConfig = BelayConfigV4;
|
|
|
196
213
|
export declare const DEFAULT_FENCE_WARN_THRESHOLD = 0.5;
|
|
197
214
|
export declare const DEFAULT_CONFIDENCE_THRESHOLDS: BelayConfidenceThresholds;
|
|
198
215
|
export declare const DEFAULT_MODEL_ASSIST: BelayModelAssistConfig;
|
|
216
|
+
export declare const DEFAULT_FILE_CHECKPOINT: BelayFileCheckpointConfig;
|
|
217
|
+
export declare function normalizeFileCheckpointConfig(raw: Partial<BelayFileCheckpointConfig> | undefined): BelayFileCheckpointConfig;
|
|
218
|
+
export declare const DEFAULT_RECOVERY_CHECKPOINT: NonNullable<BelayTransactionalConfig['checkpoint']>;
|
|
199
219
|
export declare const DEFAULT_TRANSACTIONAL_V3: BelayTransactionalConfig;
|
|
200
220
|
export declare const LEGACY_POLICY_V3: BelayPolicyConfig;
|
|
201
221
|
/** Fresh install defaults: recoverable-first with opaque/unparseable fail-closed. */
|
package/dist/core/config.js
CHANGED
|
@@ -51,6 +51,46 @@ export const DEFAULT_MODEL_ASSIST = {
|
|
|
51
51
|
enabled: false,
|
|
52
52
|
timeoutMs: 3000,
|
|
53
53
|
};
|
|
54
|
+
export const DEFAULT_FILE_CHECKPOINT = {
|
|
55
|
+
enabled: false,
|
|
56
|
+
allowNonGit: false,
|
|
57
|
+
maxFiles: 100_000,
|
|
58
|
+
maxSourceBytes: 2_147_483_648,
|
|
59
|
+
maxWorkspaceBytes: 4_294_967_296,
|
|
60
|
+
prepareTimeoutMs: 30_000,
|
|
61
|
+
copyConcurrency: 8,
|
|
62
|
+
};
|
|
63
|
+
function clampCopyConcurrency(value) {
|
|
64
|
+
return Math.min(32, Math.max(1, Math.floor(value)));
|
|
65
|
+
}
|
|
66
|
+
export function normalizeFileCheckpointConfig(raw) {
|
|
67
|
+
return {
|
|
68
|
+
enabled: raw?.enabled === true,
|
|
69
|
+
allowNonGit: raw?.allowNonGit === true,
|
|
70
|
+
maxFiles: typeof raw?.maxFiles === 'number' && raw.maxFiles > 0
|
|
71
|
+
? Math.floor(raw.maxFiles)
|
|
72
|
+
: DEFAULT_FILE_CHECKPOINT.maxFiles,
|
|
73
|
+
maxSourceBytes: typeof raw?.maxSourceBytes === 'number' && raw.maxSourceBytes > 0
|
|
74
|
+
? Math.floor(raw.maxSourceBytes)
|
|
75
|
+
: DEFAULT_FILE_CHECKPOINT.maxSourceBytes,
|
|
76
|
+
maxWorkspaceBytes: typeof raw?.maxWorkspaceBytes === 'number' && raw.maxWorkspaceBytes > 0
|
|
77
|
+
? Math.floor(raw.maxWorkspaceBytes)
|
|
78
|
+
: DEFAULT_FILE_CHECKPOINT.maxWorkspaceBytes,
|
|
79
|
+
prepareTimeoutMs: typeof raw?.prepareTimeoutMs === 'number' && raw.prepareTimeoutMs > 0
|
|
80
|
+
? Math.floor(raw.prepareTimeoutMs)
|
|
81
|
+
: DEFAULT_FILE_CHECKPOINT.prepareTimeoutMs,
|
|
82
|
+
copyConcurrency: typeof raw?.copyConcurrency === 'number' && raw.copyConcurrency > 0
|
|
83
|
+
? clampCopyConcurrency(raw.copyConcurrency)
|
|
84
|
+
: DEFAULT_FILE_CHECKPOINT.copyConcurrency,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export const DEFAULT_RECOVERY_CHECKPOINT = {
|
|
88
|
+
enabled: false,
|
|
89
|
+
appliedRetentionHours: 7 * 24,
|
|
90
|
+
restoredRetentionHours: 24,
|
|
91
|
+
maxCheckpoints: 20,
|
|
92
|
+
maxBytes: 1024 * 1024 * 1024,
|
|
93
|
+
};
|
|
54
94
|
export const DEFAULT_TRANSACTIONAL_V3 = {
|
|
55
95
|
enabled: false,
|
|
56
96
|
minConfidence: DEFAULT_CONFIDENCE_THRESHOLDS.flag,
|
|
@@ -60,6 +100,8 @@ export const DEFAULT_TRANSACTIONAL_V3 = {
|
|
|
60
100
|
gates: {
|
|
61
101
|
shell: true,
|
|
62
102
|
},
|
|
103
|
+
fileCheckpoint: { ...DEFAULT_FILE_CHECKPOINT },
|
|
104
|
+
checkpoint: { ...DEFAULT_RECOVERY_CHECKPOINT },
|
|
63
105
|
};
|
|
64
106
|
export const LEGACY_POLICY_V3 = {
|
|
65
107
|
unknownLocalEffect: 'allow_flagged',
|
|
@@ -696,6 +738,26 @@ export function normalizeConfig(config) {
|
|
|
696
738
|
gates: {
|
|
697
739
|
shell: v4.policy?.transactional?.gates?.shell !== false,
|
|
698
740
|
},
|
|
741
|
+
fileCheckpoint: normalizeFileCheckpointConfig(v4.policy?.transactional?.fileCheckpoint),
|
|
742
|
+
checkpoint: {
|
|
743
|
+
enabled: v4.policy?.transactional?.checkpoint?.enabled === true,
|
|
744
|
+
appliedRetentionHours: typeof v4.policy?.transactional?.checkpoint?.appliedRetentionHours === 'number' &&
|
|
745
|
+
v4.policy.transactional.checkpoint.appliedRetentionHours > 0
|
|
746
|
+
? v4.policy.transactional.checkpoint.appliedRetentionHours
|
|
747
|
+
: DEFAULT_RECOVERY_CHECKPOINT.appliedRetentionHours,
|
|
748
|
+
restoredRetentionHours: typeof v4.policy?.transactional?.checkpoint?.restoredRetentionHours === 'number' &&
|
|
749
|
+
v4.policy.transactional.checkpoint.restoredRetentionHours > 0
|
|
750
|
+
? v4.policy.transactional.checkpoint.restoredRetentionHours
|
|
751
|
+
: DEFAULT_RECOVERY_CHECKPOINT.restoredRetentionHours,
|
|
752
|
+
maxCheckpoints: typeof v4.policy?.transactional?.checkpoint?.maxCheckpoints === 'number' &&
|
|
753
|
+
v4.policy.transactional.checkpoint.maxCheckpoints > 0
|
|
754
|
+
? Math.floor(v4.policy.transactional.checkpoint.maxCheckpoints)
|
|
755
|
+
: DEFAULT_RECOVERY_CHECKPOINT.maxCheckpoints,
|
|
756
|
+
maxBytes: typeof v4.policy?.transactional?.checkpoint?.maxBytes === 'number' &&
|
|
757
|
+
v4.policy.transactional.checkpoint.maxBytes > 0
|
|
758
|
+
? Math.floor(v4.policy.transactional.checkpoint.maxBytes)
|
|
759
|
+
: DEFAULT_RECOVERY_CHECKPOINT.maxBytes,
|
|
760
|
+
},
|
|
699
761
|
};
|
|
700
762
|
})(),
|
|
701
763
|
},
|
|
@@ -875,8 +937,6 @@ export function scrubOptionsFromConfig(config) {
|
|
|
875
937
|
export function classifierOptionsFromConfig(config) {
|
|
876
938
|
return {
|
|
877
939
|
strictChains: config.classifier.strictChains,
|
|
878
|
-
customExternalCommands: config.overrides.external,
|
|
879
|
-
customAllowCommands: config.overrides.allow,
|
|
880
940
|
sensitivePaths: config.classifier.sensitivePaths,
|
|
881
941
|
unknownLocalEffect: config.policy.unknownLocalEffect,
|
|
882
942
|
unparseableShell: config.policy.unparseableShell,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PolicyDecision } from '../capability/policy-types.js';
|
|
2
|
+
import type { CapabilityRequestV1 } from '../capability/request.js';
|
|
3
|
+
import type { EffectPlan } from './types.js';
|
|
4
|
+
export declare function hashEffectPlan(plan: EffectPlan): string;
|
|
5
|
+
export declare function effectPlanAuditFields(plan: EffectPlan | undefined, policy?: {
|
|
6
|
+
capabilityRequests?: readonly CapabilityRequestV1[];
|
|
7
|
+
decisions?: readonly PolicyDecision[];
|
|
8
|
+
authorizationDecision?: PolicyDecision;
|
|
9
|
+
}): Record<string, unknown>;
|