@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
|
@@ -245,6 +245,13 @@ export function resolveLauncherRecipe(params) {
|
|
|
245
245
|
if (tokens[0] === 'make' && tokens[1] && !tokens[1].startsWith('-')) {
|
|
246
246
|
return resolveMakeRecipe(params.cwd, params.repoRoot, tokens[1]);
|
|
247
247
|
}
|
|
248
|
+
if (tokens[0] === 'pnpm' && tokens[1] === 'exec' && tokens[2]) {
|
|
249
|
+
return {
|
|
250
|
+
recipes: [tokens.slice(2).join(' ')],
|
|
251
|
+
opaque: false,
|
|
252
|
+
reason: 'pnpm_exec',
|
|
253
|
+
};
|
|
254
|
+
}
|
|
248
255
|
if (tokens[0] === 'pnpm' && tokens[1] && PNPM_EXEC_LIKE_HEADS.has(tokens[1])) {
|
|
249
256
|
return {
|
|
250
257
|
recipes: [tokens.slice(1).join(' ')],
|
|
@@ -254,7 +261,16 @@ export function resolveLauncherRecipe(params) {
|
|
|
254
261
|
}
|
|
255
262
|
return null;
|
|
256
263
|
}
|
|
264
|
+
const READ_ONLY_LAUNCHER_SUFFIXES = new Set(['--version', '-v', '--help', '-h']);
|
|
265
|
+
function isReadOnlyLauncherInvocation(tokens) {
|
|
266
|
+
const head = tokens[1];
|
|
267
|
+
return Boolean(head && READ_ONLY_LAUNCHER_SUFFIXES.has(head));
|
|
268
|
+
}
|
|
269
|
+
export { isReadOnlyLauncherInvocation };
|
|
257
270
|
export function isRoutineLauncher(tokens) {
|
|
271
|
+
if ((tokens[0] === 'pnpm' || tokens[0] === 'npm') && isReadOnlyLauncherInvocation(tokens)) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
258
274
|
return ((tokens[0] === 'npm' && (tokens[1] === 'run' || tokens[1] === 'test')) ||
|
|
259
275
|
tokens[0] === 'pnpm' ||
|
|
260
276
|
tokens[0] === 'make');
|
|
@@ -160,7 +160,12 @@ export function splitTopLevelSegments(command) {
|
|
|
160
160
|
}
|
|
161
161
|
};
|
|
162
162
|
for (const token of tokens) {
|
|
163
|
-
if (token === '&&' ||
|
|
163
|
+
if (token === '&&' ||
|
|
164
|
+
token === '||' ||
|
|
165
|
+
token === ';' ||
|
|
166
|
+
token === '|' ||
|
|
167
|
+
token === '&' ||
|
|
168
|
+
token === '|&') {
|
|
164
169
|
flush();
|
|
165
170
|
continue;
|
|
166
171
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Tier1Verdict } from './types.js';
|
|
2
|
+
export declare function prescanInterpreterCode(code: string): Tier1Verdict | null;
|
|
3
|
+
export interface MutationPrescanParams {
|
|
4
|
+
targets: string[];
|
|
5
|
+
cwd: string;
|
|
6
|
+
repoRoot: string;
|
|
7
|
+
trustedCwd: boolean;
|
|
8
|
+
trustedWorkspaceRoots?: string[];
|
|
9
|
+
sensitivePaths: string[];
|
|
10
|
+
}
|
|
11
|
+
/** ADR-002 M3: structural prescan for sensitive / persistent mutation targets (shell redirects, etc.). */
|
|
12
|
+
export declare function prescanMutationTargets(params: MutationPrescanParams): Tier1Verdict | null;
|
|
13
|
+
/** Returns prescan verdict when structural M3 rules require ask (before async judge shadow). */
|
|
14
|
+
export declare function mutationPrescanRequiresAsk(params: MutationPrescanParams): Tier1Verdict | null;
|
|
15
|
+
export declare function tier1RequiresAsk(verdict: Tier1Verdict): boolean;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { matchesSensitivePath } from '../glob.js';
|
|
2
|
+
import { resolveMutationTarget, resolveWorkspaceRootMatch } from '../path-utils.js';
|
|
3
|
+
import { resolveTrustedPath } from './containment.js';
|
|
4
|
+
import { isOutsideRepoSecretCredentialPath, isPersistentAgentPath } from './persistent-paths.js';
|
|
5
|
+
const SECRET_PATTERNS = [/\.env\b/i, /\.pem\b/i, /id_rsa\b/i, /credentials/i, /secrets?\b/i];
|
|
6
|
+
const DESTRUCTIVE_VERBS = /\b(rm|rmtree|unlink|delete|truncate|shred|destroy|drop)\b/i;
|
|
7
|
+
const GIT_PATTERNS = /\.git\b/i;
|
|
8
|
+
export function prescanInterpreterCode(code) {
|
|
9
|
+
const normalized = code.replaceAll('\\', '/');
|
|
10
|
+
const hitsSecret = SECRET_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
11
|
+
const hitsGit = GIT_PATTERNS.test(normalized);
|
|
12
|
+
const hitsDestructive = DESTRUCTIVE_VERBS.test(normalized);
|
|
13
|
+
if ((hitsSecret || hitsGit) && hitsDestructive) {
|
|
14
|
+
return {
|
|
15
|
+
local_recoverable: true,
|
|
16
|
+
destroys_outside_repo: false,
|
|
17
|
+
destroys_history_or_secrets: true,
|
|
18
|
+
reason: 'prescan_destructive_secret',
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
/** ADR-002 M3: structural prescan for sensitive / persistent mutation targets (shell redirects, etc.). */
|
|
24
|
+
export function prescanMutationTargets(params) {
|
|
25
|
+
for (const target of params.targets) {
|
|
26
|
+
const resolved = resolveTrustedPath(target, params.cwd, params.trustedCwd) ??
|
|
27
|
+
resolveMutationTarget(target, params.cwd);
|
|
28
|
+
if (!resolved) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const workspaceMatch = resolveWorkspaceRootMatch(params.repoRoot, params.trustedWorkspaceRoots, resolved);
|
|
32
|
+
if (workspaceMatch !== null &&
|
|
33
|
+
matchesSensitivePath(workspaceMatch.relativePath.replaceAll('\\', '/'), params.sensitivePaths)) {
|
|
34
|
+
return {
|
|
35
|
+
local_recoverable: false,
|
|
36
|
+
destroys_outside_repo: false,
|
|
37
|
+
destroys_history_or_secrets: true,
|
|
38
|
+
reason: 'sensitive_path_mutation',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if ((workspaceMatch === null || workspaceMatch.kind === 'trusted') &&
|
|
42
|
+
isOutsideRepoSecretCredentialPath(resolved)) {
|
|
43
|
+
return {
|
|
44
|
+
local_recoverable: false,
|
|
45
|
+
destroys_outside_repo: false,
|
|
46
|
+
destroys_history_or_secrets: true,
|
|
47
|
+
reason: 'outside_repo_secret_credential_path',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (isPersistentAgentPath(resolved)) {
|
|
51
|
+
return {
|
|
52
|
+
local_recoverable: false,
|
|
53
|
+
destroys_outside_repo: false,
|
|
54
|
+
destroys_history_or_secrets: true,
|
|
55
|
+
reason: 'persistent_agent_path',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
/** Returns prescan verdict when structural M3 rules require ask (before async judge shadow). */
|
|
62
|
+
export function mutationPrescanRequiresAsk(params) {
|
|
63
|
+
const prescan = prescanMutationTargets(params);
|
|
64
|
+
return prescan && tier1RequiresAsk(prescan) ? prescan : null;
|
|
65
|
+
}
|
|
66
|
+
export function tier1RequiresAsk(verdict) {
|
|
67
|
+
return !verdict.local_recoverable || verdict.destroys_history_or_secrets;
|
|
68
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type ShellCapabilityAnalysis } from '../capability/policy-engine.js';
|
|
2
|
+
import type { PolicyDecision } from '../capability/policy-types.js';
|
|
3
|
+
import type { CapabilityRequestV1 } from '../capability/request.js';
|
|
4
|
+
import type { VerdictContext, VerdictEffect, VerdictLocation, VerdictOpacity } from './types.js';
|
|
5
|
+
export interface SegmentShellPolicyInput {
|
|
6
|
+
command: string;
|
|
7
|
+
segmentHead: string;
|
|
8
|
+
effect: VerdictEffect;
|
|
9
|
+
location: VerdictLocation;
|
|
10
|
+
opacity: VerdictOpacity;
|
|
11
|
+
egressClass?: ShellCapabilityAnalysis['egressClass'];
|
|
12
|
+
pathArgs: string[];
|
|
13
|
+
resolvedPathTargets?: string[];
|
|
14
|
+
signals: string[];
|
|
15
|
+
context: VerdictContext;
|
|
16
|
+
}
|
|
17
|
+
export declare function evaluateSegmentShellPolicy(input: SegmentShellPolicyInput): {
|
|
18
|
+
request: CapabilityRequestV1;
|
|
19
|
+
decision: PolicyDecision;
|
|
20
|
+
};
|
|
21
|
+
/** Attach capability metadata without influencing the verdict (audit / shadow only). */
|
|
22
|
+
export declare function shellPolicyMetadata(input: SegmentShellPolicyInput): {
|
|
23
|
+
capabilityRequests: CapabilityRequestV1[];
|
|
24
|
+
authorizationDecision: PolicyDecision;
|
|
25
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { evaluateShellPolicy } from '../capability/policy-engine.js';
|
|
2
|
+
import { cwdRelative } from './containment.js';
|
|
3
|
+
import { verdictFingerprint } from './fingerprint.js';
|
|
4
|
+
import { redactCommand } from './parser.js';
|
|
5
|
+
function toShellCapabilityAnalysis(input) {
|
|
6
|
+
const relative = cwdRelative(input.context.repoRoot, input.context.cwd);
|
|
7
|
+
return {
|
|
8
|
+
command: input.command,
|
|
9
|
+
hookKind: 'shell',
|
|
10
|
+
segmentHead: input.segmentHead,
|
|
11
|
+
effect: input.effect,
|
|
12
|
+
location: input.location,
|
|
13
|
+
opacity: input.opacity,
|
|
14
|
+
egressClass: input.egressClass,
|
|
15
|
+
pathArgs: input.pathArgs,
|
|
16
|
+
resolvedPathTargets: input.resolvedPathTargets,
|
|
17
|
+
signals: input.signals,
|
|
18
|
+
cwd: input.context.cwd,
|
|
19
|
+
repoRoot: input.context.repoRoot,
|
|
20
|
+
inputFingerprint: verdictFingerprint(relative, redactCommand(input.command)),
|
|
21
|
+
trustedWorkspaceRoots: input.context.trustedWorkspaceRoots,
|
|
22
|
+
sensitivePaths: input.context.sensitivePaths,
|
|
23
|
+
protectedArtifactRoots: input.context.protectedArtifactRoots,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function evaluateSegmentShellPolicy(input) {
|
|
27
|
+
return evaluateShellPolicy(toShellCapabilityAnalysis(input), input.context.config, {
|
|
28
|
+
grants: input.context.grants,
|
|
29
|
+
attestation: input.context.attestation,
|
|
30
|
+
egressProxyActive: input.context.egressProxyActive,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** Attach capability metadata without influencing the verdict (audit / shadow only). */
|
|
34
|
+
export function shellPolicyMetadata(input) {
|
|
35
|
+
const result = evaluateSegmentShellPolicy(input);
|
|
36
|
+
return {
|
|
37
|
+
capabilityRequests: [result.request],
|
|
38
|
+
authorizationDecision: result.decision,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ParsedSegment } from './parser.js';
|
|
2
|
+
import type { VerdictEffect } from './types.js';
|
|
3
|
+
export interface ShellCommandSemantics {
|
|
4
|
+
effect: VerdictEffect;
|
|
5
|
+
pathTargets: string[];
|
|
6
|
+
scopeTargets?: string[];
|
|
7
|
+
signals: string[];
|
|
8
|
+
egressClass?: 'read' | 'destructive' | 'ambiguous';
|
|
9
|
+
requiresAsk?: {
|
|
10
|
+
reason: string;
|
|
11
|
+
signals: string[];
|
|
12
|
+
};
|
|
13
|
+
effectiveCwd?: string;
|
|
14
|
+
gitWorkTree?: string;
|
|
15
|
+
normalizedKey?: string;
|
|
16
|
+
isReadOnly?: boolean;
|
|
17
|
+
}
|
|
18
|
+
declare const READ_ONLY_KEYS: Set<string>;
|
|
19
|
+
declare const PURE_READ_ONLY_KEYS: Set<string>;
|
|
20
|
+
declare const LOCAL_MUTATION_KEYS: Set<string>;
|
|
21
|
+
export declare const LOCAL_ROUTINE_HEADS: Set<string>;
|
|
22
|
+
export declare function isPureReadOnlySemantics(segment: ParsedSegment, semantics: ShellCommandSemantics): boolean;
|
|
23
|
+
export declare function analyzeShellCommandSemantics(tokens: string[], segment: ParsedSegment, cwd: string): ShellCommandSemantics;
|
|
24
|
+
export { LOCAL_MUTATION_KEYS, PURE_READ_ONLY_KEYS, READ_ONLY_KEYS };
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { extractRedirectTargets } from '../shell-tokenizer.js';
|
|
3
|
+
import { classifyGitCommand } from './git-classifier.js';
|
|
4
|
+
const READ_ONLY_KEYS = new Set([
|
|
5
|
+
'cat',
|
|
6
|
+
'cd',
|
|
7
|
+
'echo',
|
|
8
|
+
'head',
|
|
9
|
+
'ls',
|
|
10
|
+
'pwd',
|
|
11
|
+
'rg',
|
|
12
|
+
'sort',
|
|
13
|
+
'tail',
|
|
14
|
+
'wc',
|
|
15
|
+
'which',
|
|
16
|
+
'find',
|
|
17
|
+
'grep',
|
|
18
|
+
'jq',
|
|
19
|
+
'stat',
|
|
20
|
+
'file',
|
|
21
|
+
'du',
|
|
22
|
+
'readlink',
|
|
23
|
+
'realpath',
|
|
24
|
+
'basename',
|
|
25
|
+
'dirname',
|
|
26
|
+
'uname',
|
|
27
|
+
'whoami',
|
|
28
|
+
'id',
|
|
29
|
+
'printenv',
|
|
30
|
+
'test',
|
|
31
|
+
'printf',
|
|
32
|
+
'cut',
|
|
33
|
+
'tr',
|
|
34
|
+
'uniq',
|
|
35
|
+
'cmp',
|
|
36
|
+
'comm',
|
|
37
|
+
'shasum',
|
|
38
|
+
'md5sum',
|
|
39
|
+
]);
|
|
40
|
+
const PURE_READ_ONLY_KEYS = new Set([
|
|
41
|
+
'echo',
|
|
42
|
+
'pwd',
|
|
43
|
+
'which',
|
|
44
|
+
'grep',
|
|
45
|
+
'jq',
|
|
46
|
+
'stat',
|
|
47
|
+
'file',
|
|
48
|
+
'du',
|
|
49
|
+
'readlink',
|
|
50
|
+
'realpath',
|
|
51
|
+
'basename',
|
|
52
|
+
'dirname',
|
|
53
|
+
'uname',
|
|
54
|
+
'whoami',
|
|
55
|
+
'id',
|
|
56
|
+
'printenv',
|
|
57
|
+
'test',
|
|
58
|
+
'printf',
|
|
59
|
+
'cut',
|
|
60
|
+
'tr',
|
|
61
|
+
'uniq',
|
|
62
|
+
'cmp',
|
|
63
|
+
'comm',
|
|
64
|
+
'shasum',
|
|
65
|
+
'md5sum',
|
|
66
|
+
]);
|
|
67
|
+
const LOCAL_MUTATION_KEYS = new Set([
|
|
68
|
+
'chmod',
|
|
69
|
+
'cp',
|
|
70
|
+
'mkdir',
|
|
71
|
+
'mv',
|
|
72
|
+
'rm',
|
|
73
|
+
'sed',
|
|
74
|
+
'tee',
|
|
75
|
+
'touch',
|
|
76
|
+
'truncate',
|
|
77
|
+
]);
|
|
78
|
+
export const LOCAL_ROUTINE_HEADS = new Set([
|
|
79
|
+
'tsc',
|
|
80
|
+
'vitest',
|
|
81
|
+
'vite',
|
|
82
|
+
'webpack',
|
|
83
|
+
'esbuild',
|
|
84
|
+
'rollup',
|
|
85
|
+
'jest',
|
|
86
|
+
'mocha',
|
|
87
|
+
'cargo',
|
|
88
|
+
'go',
|
|
89
|
+
'make',
|
|
90
|
+
'cmake',
|
|
91
|
+
'biome',
|
|
92
|
+
'eslint',
|
|
93
|
+
]);
|
|
94
|
+
function looksLikePathToken(token) {
|
|
95
|
+
if (!token || token.startsWith('-')) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return (token.includes('/') || token.includes('\\') || token.startsWith('.') || token.includes('...'));
|
|
99
|
+
}
|
|
100
|
+
function filterRoutinePathTargets(head, targets) {
|
|
101
|
+
if (!LOCAL_ROUTINE_HEADS.has(head)) {
|
|
102
|
+
return targets;
|
|
103
|
+
}
|
|
104
|
+
return targets.filter((target) => looksLikePathToken(target));
|
|
105
|
+
}
|
|
106
|
+
function extractFilePathOperands(tokens) {
|
|
107
|
+
const redirects = extractRedirectTargets(tokens);
|
|
108
|
+
const args = [...redirects];
|
|
109
|
+
for (let index = 1; index < tokens.length; index += 1) {
|
|
110
|
+
const token = tokens[index];
|
|
111
|
+
if (!token || token.startsWith('-')) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (redirects.includes(token)) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
args.push(token);
|
|
118
|
+
}
|
|
119
|
+
return args;
|
|
120
|
+
}
|
|
121
|
+
function isRsyncRemoteOperand(token) {
|
|
122
|
+
if (/^rsync:\/\/.+/.test(token)) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
if (/^(?:[^@\s/:]+@)?(?:\[[^\]]+\]|[^:/\s]+)::/.test(token)) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
if (!/^[A-Za-z]:[\\/]/.test(token) &&
|
|
129
|
+
/^(?:[^@\s/:]+@)?(?:\[[^\]]+\]|[^:/\s]+):[^:].*/.test(token)) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
if (/^\/\/.+\//.test(token)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
function classifyRsync(tokens) {
|
|
138
|
+
const args = tokens.slice(1);
|
|
139
|
+
const pathTargets = args.filter((token) => token && !token.startsWith('-'));
|
|
140
|
+
const signals = ['rsync'];
|
|
141
|
+
const destructive = args.some((token) => token.startsWith('--delete') ||
|
|
142
|
+
token === '--del' ||
|
|
143
|
+
token === '--remove-source-files' ||
|
|
144
|
+
token.startsWith('--remove-source-files='));
|
|
145
|
+
if (destructive) {
|
|
146
|
+
return {
|
|
147
|
+
effect: 'local_mutation',
|
|
148
|
+
pathTargets,
|
|
149
|
+
signals: [...signals, 'rsync.destructive'],
|
|
150
|
+
requiresAsk: {
|
|
151
|
+
reason: 'rsync_destructive',
|
|
152
|
+
signals: ['rsync.destructive', 'rsync.delete'],
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const remote = pathTargets.some((token) => isRsyncRemoteOperand(token));
|
|
157
|
+
if (remote) {
|
|
158
|
+
return {
|
|
159
|
+
effect: 'remote_mutation',
|
|
160
|
+
pathTargets,
|
|
161
|
+
signals: [...signals, 'rsync.remote'],
|
|
162
|
+
egressClass: 'ambiguous',
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
effect: 'local_mutation',
|
|
167
|
+
pathTargets,
|
|
168
|
+
signals: [...signals, 'rsync.local'],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
export function isPureReadOnlySemantics(segment, semantics) {
|
|
172
|
+
return (semantics.isReadOnly === true ||
|
|
173
|
+
PURE_READ_ONLY_KEYS.has(segment.head) ||
|
|
174
|
+
PURE_READ_ONLY_KEYS.has(segment.key) ||
|
|
175
|
+
PURE_READ_ONLY_KEYS.has(semantics.normalizedKey ?? ''));
|
|
176
|
+
}
|
|
177
|
+
export function analyzeShellCommandSemantics(tokens, segment, cwd) {
|
|
178
|
+
const head = segment.head;
|
|
179
|
+
const basename = path.basename(tokens[0] ?? head);
|
|
180
|
+
if (basename === 'git') {
|
|
181
|
+
const gitSemantics = classifyGitCommand(tokens, cwd);
|
|
182
|
+
if (gitSemantics) {
|
|
183
|
+
return {
|
|
184
|
+
effect: gitSemantics.effect,
|
|
185
|
+
pathTargets: gitSemantics.pathTargets,
|
|
186
|
+
scopeTargets: gitSemantics.scopeTargets,
|
|
187
|
+
signals: gitSemantics.signals,
|
|
188
|
+
egressClass: gitSemantics.egressClass,
|
|
189
|
+
requiresAsk: gitSemantics.requiresAsk,
|
|
190
|
+
effectiveCwd: gitSemantics.effectiveCwd,
|
|
191
|
+
gitWorkTree: gitSemantics.gitWorkTree,
|
|
192
|
+
normalizedKey: gitSemantics.normalizedKey,
|
|
193
|
+
isReadOnly: gitSemantics.isReadOnly,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (head === 'rsync') {
|
|
198
|
+
return classifyRsync(tokens);
|
|
199
|
+
}
|
|
200
|
+
let effect = 'unknown';
|
|
201
|
+
let pathTargets = extractFilePathOperands(tokens);
|
|
202
|
+
pathTargets = filterRoutinePathTargets(head, pathTargets);
|
|
203
|
+
const signals = [];
|
|
204
|
+
if (READ_ONLY_KEYS.has(segment.key) || READ_ONLY_KEYS.has(head)) {
|
|
205
|
+
effect = 'read_only';
|
|
206
|
+
}
|
|
207
|
+
else if (LOCAL_MUTATION_KEYS.has(segment.key) || LOCAL_MUTATION_KEYS.has(head)) {
|
|
208
|
+
effect = 'local_mutation';
|
|
209
|
+
}
|
|
210
|
+
else if (LOCAL_ROUTINE_HEADS.has(head)) {
|
|
211
|
+
effect = 'local_mutation';
|
|
212
|
+
signals.push('routine_local');
|
|
213
|
+
}
|
|
214
|
+
if (extractRedirectTargets(tokens).length > 0 && effect === 'read_only') {
|
|
215
|
+
effect = 'local_mutation';
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
effect,
|
|
219
|
+
pathTargets,
|
|
220
|
+
signals,
|
|
221
|
+
normalizedKey: segment.key,
|
|
222
|
+
isReadOnly: effect === 'read_only',
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
export { LOCAL_MUTATION_KEYS, PURE_READ_ONLY_KEYS, READ_ONLY_KEYS };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { PolicyDecision } from '../capability/policy-types.js';
|
|
2
|
+
import type { CapabilityRequestV1 } from '../capability/request.js';
|
|
3
|
+
import type { BelayConfigV4 } from '../config.js';
|
|
1
4
|
export type VerdictPermission = 'allow' | 'ask';
|
|
2
5
|
export type VerdictLocation = 'repo_local' | 'repo_outside' | 'external' | 'mixed' | 'unknown';
|
|
3
6
|
export type VerdictOpacity = 'transparent' | 'recursive' | 'opaque' | 'unparseable';
|
|
@@ -14,6 +17,8 @@ export interface VerdictResult {
|
|
|
14
17
|
fingerprint: string;
|
|
15
18
|
signals: string[];
|
|
16
19
|
judgeTrace?: JudgeTrace;
|
|
20
|
+
capabilityRequests?: CapabilityRequestV1[];
|
|
21
|
+
authorizationDecision?: PolicyDecision;
|
|
17
22
|
}
|
|
18
23
|
export interface Tier1Verdict {
|
|
19
24
|
/** true = undoable via git/fs snapshot or trivial local revert (ADR-002 local-recoverable). */
|
|
@@ -33,6 +38,7 @@ export interface Tier1EvaluateInput {
|
|
|
33
38
|
};
|
|
34
39
|
innerCode?: string;
|
|
35
40
|
}
|
|
41
|
+
/** @deprecated Sync gate path no longer invokes Tier1 judge. Retained for shadow tooling. */
|
|
36
42
|
export interface Tier1Judge {
|
|
37
43
|
evaluate(input: Tier1EvaluateInput): Promise<Tier1Verdict>;
|
|
38
44
|
}
|
|
@@ -41,23 +47,40 @@ export interface JudgeTrace {
|
|
|
41
47
|
modelRequested: string;
|
|
42
48
|
modelResolved: string;
|
|
43
49
|
latencyMs: number;
|
|
50
|
+
transport?: 'http' | 'ollama-http' | 'codex-cli' | 'cursor-cli' | 'claude-cli' | 'codex-cli-session' | 'cursor-cli-session' | 'claude-cli-session' | 'cursor-acp' | 'unavailable';
|
|
44
51
|
outboundRedacted?: boolean;
|
|
45
52
|
fallbackReason?: string;
|
|
53
|
+
judgeSessionUsed?: boolean;
|
|
54
|
+
judgeSessionReused?: boolean;
|
|
55
|
+
judgeSessionRefHash?: string;
|
|
56
|
+
judgeSessionResetReason?: string;
|
|
57
|
+
judgeFallbackReason?: string;
|
|
58
|
+
judgeConnectMs?: number;
|
|
59
|
+
judgeEvalMs?: number;
|
|
60
|
+
judgeParseMs?: number;
|
|
61
|
+
judgeShadowCompared?: boolean;
|
|
62
|
+
judgeShadowMismatch?: boolean;
|
|
63
|
+
judgeShadowMismatchRateWindow?: number;
|
|
64
|
+
judgeKillSwitchTriggered?: boolean;
|
|
46
65
|
}
|
|
47
66
|
export type VerdictMode = 'enforce' | 'audit';
|
|
48
67
|
export interface VerdictContext {
|
|
49
68
|
cwd: string;
|
|
50
69
|
repoRoot: string;
|
|
70
|
+
config: BelayConfigV4;
|
|
51
71
|
trustedCwd: boolean;
|
|
72
|
+
trustedWorkspaceRoots?: string[];
|
|
52
73
|
sensitivePaths: string[];
|
|
53
74
|
protectedArtifactRoots?: string[];
|
|
54
75
|
customAllowCommands?: string[];
|
|
55
76
|
customExternalCommands?: string[];
|
|
56
|
-
judge: Tier1Judge;
|
|
57
77
|
mode: VerdictMode;
|
|
58
78
|
unknownLocalEffect: 'allow_flagged' | 'deny';
|
|
59
79
|
unparseableShell: 'allow_flagged' | 'deny';
|
|
60
80
|
maxRecursionDepth?: number;
|
|
81
|
+
grants?: import('../capability/grant.js').CapabilityGrantV1[];
|
|
82
|
+
attestation?: import('../capability/attestation.js').BoundaryAttestation | null;
|
|
83
|
+
egressProxyActive?: boolean;
|
|
61
84
|
}
|
|
62
85
|
export interface InternalSegmentVerdict {
|
|
63
86
|
permission: VerdictPermission;
|
|
@@ -68,4 +91,6 @@ export interface InternalSegmentVerdict {
|
|
|
68
91
|
reason: string;
|
|
69
92
|
signals: string[];
|
|
70
93
|
judgeTrace?: JudgeTrace;
|
|
94
|
+
capabilityRequests?: CapabilityRequestV1[];
|
|
95
|
+
authorizationDecision?: PolicyDecision;
|
|
71
96
|
}
|