@guilz-dev/belay 0.3.0 → 0.4.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/dist/adapters/cursor/runtime-entry.js +1 -0
- package/dist/adapters/shared/gate-runtime.d.ts +11 -14
- package/dist/adapters/shared/gate-runtime.js +277 -19
- package/dist/bundle/claude-runtime.mjs +3138 -699
- package/dist/bundle/codex-runtime.mjs +3162 -711
- package/dist/bundle/cursor-runtime.mjs +3140 -701
- package/dist/cli.js +137 -5
- package/dist/commands/approve.d.ts +1 -0
- package/dist/commands/approve.js +45 -26
- package/dist/commands/config.js +135 -52
- package/dist/commands/doctor.js +11 -1
- package/dist/commands/explain.js +6 -1
- 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 +21 -2
- 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/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 +10 -5
- 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 +47 -2
- package/dist/core/approval.d.ts +24 -3
- package/dist/core/approval.js +47 -4
- 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/index.d.ts +2 -1
- package/dist/core/capability/index.js +1 -0
- package/dist/core/capability/paths.d.ts +2 -2
- package/dist/core/capability/paths.js +9 -9
- 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.js +2 -20
- package/dist/core/classify-tool.js +32 -3
- package/dist/core/config.d.ts +18 -0
- package/dist/core/config.js +49 -0
- package/dist/core/gate-engine.js +6 -6
- 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 +54 -0
- package/dist/core/path-utils.d.ts +11 -0
- package/dist/core/path-utils.js +56 -6
- package/dist/core/reclassify.d.ts +3 -0
- package/dist/core/reclassify.js +44 -14
- 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 +175 -0
- package/dist/core/types.d.ts +15 -0
- package/dist/core/verdict/adapter.js +10 -1
- package/dist/core/verdict/containment.d.ts +4 -3
- package/dist/core/verdict/containment.js +17 -12
- package/dist/core/verdict/judge-audit.d.ts +1 -0
- package/dist/core/verdict/judge-audit.js +32 -1
- package/dist/core/verdict/judge-baseline.d.ts +20 -0
- package/dist/core/verdict/judge-baseline.js +48 -0
- package/dist/core/verdict/judge-broker-service.d.ts +42 -0
- package/dist/core/verdict/judge-broker-service.js +279 -0
- package/dist/core/verdict/judge-cli-fingerprint.d.ts +5 -0
- package/dist/core/verdict/judge-cli-fingerprint.js +61 -0
- package/dist/core/verdict/judge-cli.d.ts +23 -0
- package/dist/core/verdict/judge-cli.js +137 -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 +42 -0
- package/dist/core/verdict/judge-runtime-config.js +92 -0
- package/dist/core/verdict/judge-session-broker.d.ts +48 -0
- package/dist/core/verdict/judge-session-broker.js +195 -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 +40 -0
- package/dist/core/verdict/judge-transport.js +258 -0
- package/dist/core/verdict/judge.d.ts +14 -1
- package/dist/core/verdict/judge.js +6 -5
- package/dist/core/verdict/parser.js +6 -1
- package/dist/core/verdict/types.d.ts +13 -0
- package/dist/core/verdict/verdict.js +7 -4
- 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 +62 -5
- 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 +151 -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 +45 -0
- package/dist/corpus/standing-allow-catalog.generated.d.ts +13 -0
- package/dist/corpus/standing-allow-catalog.generated.js +79 -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 +123 -0
- package/dist/services/sandbox-service.d.ts +1 -0
- package/dist/services/sandbox-service.js +2 -0
- 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,4 +1,4 @@
|
|
|
1
|
-
export type CapabilityApprovalScope = 'once' | 'path';
|
|
1
|
+
export type CapabilityApprovalScope = 'once' | 'path' | 'workspace-root';
|
|
2
2
|
export interface FsScopeAllowlistEntry {
|
|
3
3
|
path: string;
|
|
4
4
|
approvedAt: string;
|
|
@@ -8,3 +8,13 @@ export interface FsScopeAllowlistFile {
|
|
|
8
8
|
version: 1;
|
|
9
9
|
paths: FsScopeAllowlistEntry[];
|
|
10
10
|
}
|
|
11
|
+
export interface TrustedWorkspaceRootEntry {
|
|
12
|
+
path: string;
|
|
13
|
+
approvedAt: string;
|
|
14
|
+
approvalId: string;
|
|
15
|
+
source?: 'approval';
|
|
16
|
+
}
|
|
17
|
+
export interface TrustedWorkspaceRootsFile {
|
|
18
|
+
version: 1;
|
|
19
|
+
roots: TrustedWorkspaceRootEntry[];
|
|
20
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CapabilityApprovalScope } from './capability/types.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type BelayConfigV3 } from './config.js';
|
|
3
3
|
import type { ApprovalStateFile } from './types.js';
|
|
4
4
|
export interface CapabilityApprovalStore {
|
|
5
5
|
loadPending: () => Promise<{
|
|
@@ -13,6 +13,7 @@ export interface CapabilityApprovalStore {
|
|
|
13
13
|
writePending: (filePath: string, state: ApprovalStateFile) => Promise<void>;
|
|
14
14
|
writeApproved: (filePath: string, state: ApprovalStateFile) => Promise<void>;
|
|
15
15
|
allowlistPath: string;
|
|
16
|
+
trustedRootsPath: string;
|
|
16
17
|
}
|
|
17
18
|
export declare function recordCapabilityApproval(params: {
|
|
18
19
|
approvalId: string;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { recordApproval } from './approval-service.js';
|
|
2
2
|
import { addPathToAllowlist, loadFsScopeAllowlist, normalizeAllowlistPath, saveFsScopeAllowlist, } from './capability/allowlist.js';
|
|
3
3
|
import { FS_SCOPE_REASONS } from './capability/reasons.js';
|
|
4
|
+
import { addTrustedWorkspaceRoot, loadTrustedWorkspaceRoots, normalizeTrustedWorkspaceRootPath, saveTrustedWorkspaceRoots, validateTrustedWorkspaceRootCandidate, } from './capability/trusted-workspace-roots.js';
|
|
5
|
+
import { resolveControlPlaneDir } from './config.js';
|
|
4
6
|
export async function recordCapabilityApproval(params) {
|
|
5
7
|
const pending = await params.store.loadPending();
|
|
6
8
|
const match = pending.state.approvals.find((approval) => approval.approvalId === params.approvalId);
|
|
7
9
|
if (params.scope === 'path') {
|
|
8
|
-
if (!match || !FS_SCOPE_REASONS.has(match.reason)) {
|
|
10
|
+
if (!match || match.kind !== 'shell' || !FS_SCOPE_REASONS.has(match.reason)) {
|
|
9
11
|
return {
|
|
10
12
|
ok: false,
|
|
11
13
|
message: 'Path scope approvals apply only to pending outside-repo shell actions (outside_repo_mutation / outside_repo_redirect).',
|
|
@@ -18,6 +20,85 @@ export async function recordCapabilityApproval(params) {
|
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
}
|
|
23
|
+
if (params.scope === 'workspace-root') {
|
|
24
|
+
if (!match || !FS_SCOPE_REASONS.has(match.reason)) {
|
|
25
|
+
return {
|
|
26
|
+
ok: false,
|
|
27
|
+
message: 'workspace-root scope approvals apply only to pending outside-repo actions (outside_repo_mutation / outside_repo_redirect).',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (!params.scopePath?.trim()) {
|
|
31
|
+
return {
|
|
32
|
+
ok: false,
|
|
33
|
+
message: 'approve --scope workspace-root requires --path <absolute-directory-path>.',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (!match.scopeHint || match.scopeHint.scope !== 'workspace-root') {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
message: 'workspace-root approval requires a pending scope hint. Re-run the original blocked action to mint a scoped approval.',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const normalizedPath = normalizeTrustedWorkspaceRootPath(params.scopePath.trim());
|
|
43
|
+
const normalizedHint = normalizeTrustedWorkspaceRootPath(match.scopeHint.path);
|
|
44
|
+
if (normalizedPath !== normalizedHint) {
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
message: `workspace-root approval path must exactly match the suggested path: ${normalizedHint}`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const validation = validateTrustedWorkspaceRootCandidate({
|
|
51
|
+
candidatePath: normalizedPath,
|
|
52
|
+
repoRoot: match.repoRoot,
|
|
53
|
+
controlPlaneDir: params.config.controlPlane.enabled
|
|
54
|
+
? resolveControlPlaneDir(params.config)
|
|
55
|
+
: undefined,
|
|
56
|
+
requireExistingDirectory: true,
|
|
57
|
+
requireNonGit: true,
|
|
58
|
+
});
|
|
59
|
+
if (!validation.ok) {
|
|
60
|
+
if (validation.reason === 'not_directory') {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
message: 'workspace-root approval requires an existing directory path.',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (validation.reason === 'broad_root') {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
message: 'workspace-root approval rejected: broad roots (/, drive root, HOME) are not allowed.',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (validation.reason === 'high_stakes') {
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
message: 'workspace-root approval rejected: high-stakes directories cannot be trusted roots.',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (validation.reason === 'inside_repo') {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
message: 'workspace-root approval rejected: the path is already inside the repository root.',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (validation.reason === 'inside_git_repo') {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
message: `workspace-root approval rejected: ${normalizedPath} is under a git repository.`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (validation.reason === 'control_plane_overlap') {
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
message: 'workspace-root approval rejected: control plane paths cannot be trusted roots.',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
message: 'workspace-root approval rejected by trusted-root policy.',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
21
102
|
const result = await recordApproval({
|
|
22
103
|
approvalId: params.approvalId,
|
|
23
104
|
config: params.config,
|
|
@@ -25,9 +106,26 @@ export async function recordCapabilityApproval(params) {
|
|
|
25
106
|
requireSignedToken: params.requireSignedToken ?? false,
|
|
26
107
|
store: params.store,
|
|
27
108
|
});
|
|
28
|
-
if (!result.ok ||
|
|
109
|
+
if (!result.ok ||
|
|
110
|
+
(params.scope !== 'path' && params.scope !== 'workspace-root') ||
|
|
111
|
+
!params.scopePath?.trim()) {
|
|
29
112
|
return { ok: result.ok, message: result.message };
|
|
30
113
|
}
|
|
114
|
+
if (params.scope === 'workspace-root') {
|
|
115
|
+
const trustedRoots = await loadTrustedWorkspaceRoots(params.store.trustedRootsPath);
|
|
116
|
+
const normalizedPath = normalizeTrustedWorkspaceRootPath(params.scopePath.trim());
|
|
117
|
+
const updated = addTrustedWorkspaceRoot(trustedRoots, {
|
|
118
|
+
path: normalizedPath,
|
|
119
|
+
approvedAt: new Date().toISOString(),
|
|
120
|
+
approvalId: params.approvalId,
|
|
121
|
+
source: 'approval',
|
|
122
|
+
});
|
|
123
|
+
await saveTrustedWorkspaceRoots(params.store.trustedRootsPath, updated);
|
|
124
|
+
return {
|
|
125
|
+
ok: true,
|
|
126
|
+
message: `${result.message} Trusted workspace root ${normalizedPath} added.`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
31
129
|
const allowlist = await loadFsScopeAllowlist(params.store.allowlistPath);
|
|
32
130
|
const normalizedPath = normalizeAllowlistPath(params.scopePath.trim());
|
|
33
131
|
const updated = addPathToAllowlist(allowlist, {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { canonicalStringify, subagentFingerprint } from './fingerprint.js';
|
|
2
|
+
import { subagentFingerprintSource } from './replay-scrub.js';
|
|
2
3
|
import { scrubValue } from './scrub.js';
|
|
3
4
|
const EXTERNAL_TERMS = ['deploy', 'production', 'publish', 'release', 'ship', 'notify', 'email'];
|
|
4
5
|
function extractSubagentText(payload, options) {
|
|
@@ -22,26 +23,7 @@ function extractSubagentText(payload, options) {
|
|
|
22
23
|
return canonicalStringify(scrubValue(payload, options.scrubOptions));
|
|
23
24
|
}
|
|
24
25
|
function fingerprintSource(payload, options) {
|
|
25
|
-
|
|
26
|
-
if (toolInput && typeof toolInput === 'object') {
|
|
27
|
-
const input = toolInput;
|
|
28
|
-
return scrubValue({
|
|
29
|
-
description: input.description ?? '',
|
|
30
|
-
prompt: input.prompt ?? '',
|
|
31
|
-
}, options.scrubOptions);
|
|
32
|
-
}
|
|
33
|
-
const task = payload.task;
|
|
34
|
-
if (typeof task === 'string') {
|
|
35
|
-
return scrubValue({ task }, options.scrubOptions);
|
|
36
|
-
}
|
|
37
|
-
if (task && typeof task === 'object') {
|
|
38
|
-
const taskObj = task;
|
|
39
|
-
return scrubValue({
|
|
40
|
-
description: taskObj.description ?? '',
|
|
41
|
-
prompt: taskObj.prompt ?? '',
|
|
42
|
-
}, options.scrubOptions);
|
|
43
|
-
}
|
|
44
|
-
return scrubValue(payload, options.scrubOptions);
|
|
26
|
+
return subagentFingerprintSource(payload, options.scrubOptions ?? {});
|
|
45
27
|
}
|
|
46
28
|
export function classifySubagent(payload, repoRoot, options = {}) {
|
|
47
29
|
const kind = payload.tool_name === 'Task' ? 'Task' : String(payload.subagent_type ?? 'generalPurpose');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { canonicalStringify, toolFingerprint } from './fingerprint.js';
|
|
3
3
|
import { matchesSensitivePath } from './glob.js';
|
|
4
|
-
import { pathWithinRoot,
|
|
4
|
+
import { pathWithinRoot, resolveWorkspaceRootMatch } from './path-utils.js';
|
|
5
5
|
import { scrubValue } from './scrub.js';
|
|
6
6
|
import { classifyShell, resolveClassifierTrustedCwd } from './verdict/adapter.js';
|
|
7
7
|
import { mutationPrescanRequiresAsk, tier1RequiresAsk } from './verdict/judge.js';
|
|
@@ -87,6 +87,7 @@ async function classifyFileMutationWithTier1(params) {
|
|
|
87
87
|
cwd: params.cwd,
|
|
88
88
|
repoRoot: params.repoRoot,
|
|
89
89
|
trustedCwd,
|
|
90
|
+
trustedWorkspaceRoots: params.options.trustedWorkspaceRoots,
|
|
90
91
|
sensitivePaths: params.options.sensitivePaths ?? params.config.classifier.sensitivePaths,
|
|
91
92
|
});
|
|
92
93
|
if (prescan) {
|
|
@@ -243,8 +244,8 @@ export async function classifyToolUse(payload, repoRoot, cwd, config, options =
|
|
|
243
244
|
},
|
|
244
245
|
};
|
|
245
246
|
}
|
|
246
|
-
const
|
|
247
|
-
if (
|
|
247
|
+
const workspaceMatch = resolveWorkspaceRootMatch(repoRoot, options.trustedWorkspaceRoots, resolvedPath);
|
|
248
|
+
if (workspaceMatch === null) {
|
|
248
249
|
signals.push('outside_repo_path');
|
|
249
250
|
return classifyFileMutationWithTier1({
|
|
250
251
|
toolName,
|
|
@@ -260,6 +261,34 @@ export async function classifyToolUse(payload, repoRoot, cwd, config, options =
|
|
|
260
261
|
locationLabel: 'outside_repo',
|
|
261
262
|
});
|
|
262
263
|
}
|
|
264
|
+
if (workspaceMatch.kind === 'trusted') {
|
|
265
|
+
signals.push('trusted_workspace_root');
|
|
266
|
+
}
|
|
267
|
+
const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
|
|
268
|
+
const workspacePrescan = mutationPrescanRequiresAsk({
|
|
269
|
+
targets: [filePath],
|
|
270
|
+
cwd,
|
|
271
|
+
repoRoot,
|
|
272
|
+
trustedCwd,
|
|
273
|
+
trustedWorkspaceRoots: options.trustedWorkspaceRoots,
|
|
274
|
+
sensitivePaths,
|
|
275
|
+
});
|
|
276
|
+
if (workspacePrescan) {
|
|
277
|
+
return classifyFileMutationWithTier1({
|
|
278
|
+
toolName,
|
|
279
|
+
toolKind,
|
|
280
|
+
filePath,
|
|
281
|
+
resolvedPath,
|
|
282
|
+
repoRoot,
|
|
283
|
+
cwd,
|
|
284
|
+
config,
|
|
285
|
+
options,
|
|
286
|
+
signals,
|
|
287
|
+
isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
|
|
288
|
+
locationLabel: 'sensitive_path',
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
const relativePath = workspaceMatch.relativePath;
|
|
263
292
|
if (matchesSensitivePath(relativePath, sensitivePaths)) {
|
|
264
293
|
signals.push('sensitive_path');
|
|
265
294
|
return classifyFileMutationWithTier1({
|
package/dist/core/config.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ClassifierOptions, ControlPlaneIntegrity, ScrubOptions, UnknownLocalEffectPolicy, UnparseableShellPolicy } from './types.js';
|
|
2
|
+
import { type BelayJudgeRuntimeConfig } from './verdict/judge-runtime-config.js';
|
|
2
3
|
export type BelayMode = 'enforce' | 'audit';
|
|
3
4
|
export type { UnknownLocalEffectPolicy };
|
|
4
5
|
export interface BelayConfigV1 {
|
|
@@ -102,6 +103,7 @@ export interface BelayJudgeConfig {
|
|
|
102
103
|
keepAlive: string | null;
|
|
103
104
|
cloudConsent?: JudgeCloudConsent;
|
|
104
105
|
credential?: JudgeCredentialConfig;
|
|
106
|
+
runtime?: BelayJudgeRuntimeConfig;
|
|
105
107
|
}
|
|
106
108
|
export declare const DEFAULT_JUDGE_LOCAL_OLLAMA: BelayJudgeConfig;
|
|
107
109
|
export declare const DEFAULT_JUDGE_OPENAI_COMPATIBLE_TEMPLATE: BelayJudgeConfig;
|
|
@@ -137,6 +139,17 @@ export interface BelayApprovalSigningConfig {
|
|
|
137
139
|
/** When true, out-of-band approvals must present a signed token. */
|
|
138
140
|
required: boolean;
|
|
139
141
|
}
|
|
142
|
+
export type ApprovalFlow = 'one_step' | 'two_step';
|
|
143
|
+
export interface BelayApprovalAutoReplayScopes {
|
|
144
|
+
shell: boolean;
|
|
145
|
+
tool: boolean;
|
|
146
|
+
subagent: boolean;
|
|
147
|
+
}
|
|
148
|
+
export interface BelayApprovalConfig {
|
|
149
|
+
flow: ApprovalFlow;
|
|
150
|
+
autoReplayScopes: BelayApprovalAutoReplayScopes;
|
|
151
|
+
executionLeaseMs: number;
|
|
152
|
+
}
|
|
140
153
|
export interface BelayEgressConfig {
|
|
141
154
|
enabled: boolean;
|
|
142
155
|
listenHost: string;
|
|
@@ -160,6 +173,7 @@ export interface BelayConfigV4 {
|
|
|
160
173
|
controlPlane: BelayControlPlaneConfig;
|
|
161
174
|
notifications: BelayNotificationsConfig;
|
|
162
175
|
approvalSigning: BelayApprovalSigningConfig;
|
|
176
|
+
approval: BelayApprovalConfig;
|
|
163
177
|
egress: BelayEgressConfig;
|
|
164
178
|
sandbox: BelaySandboxConfig;
|
|
165
179
|
audit: BelayConfigV2['audit'];
|
|
@@ -184,6 +198,7 @@ export declare const DEFAULT_CONTROL_PLANE_V3: BelayControlPlaneConfig;
|
|
|
184
198
|
export declare const DEFAULT_SANDBOX_V3: BelaySandboxConfig;
|
|
185
199
|
export declare const DEFAULT_NOTIFICATIONS_V3: BelayNotificationsConfig;
|
|
186
200
|
export declare const DEFAULT_APPROVAL_SIGNING_V3: BelayApprovalSigningConfig;
|
|
201
|
+
export declare const DEFAULT_APPROVAL_CONFIG: BelayApprovalConfig;
|
|
187
202
|
export declare const DEFAULT_EGRESS_V3: BelayEgressConfig;
|
|
188
203
|
export declare function normalizeEgressListenHost(host: string): string;
|
|
189
204
|
export declare const DEFAULT_CONFIG_V2: BelayConfigV2;
|
|
@@ -217,6 +232,9 @@ type RawConfigInput = Partial<{
|
|
|
217
232
|
controlPlane: Partial<BelayControlPlaneConfig>;
|
|
218
233
|
notifications: Partial<BelayNotificationsConfig>;
|
|
219
234
|
approvalSigning: Partial<BelayApprovalSigningConfig>;
|
|
235
|
+
approval: Partial<BelayApprovalConfig> & {
|
|
236
|
+
autoReplayScopes?: Partial<BelayApprovalAutoReplayScopes>;
|
|
237
|
+
};
|
|
220
238
|
egress: Partial<BelayEgressConfig>;
|
|
221
239
|
sandbox: Partial<BelaySandboxConfig>;
|
|
222
240
|
audit: Partial<BelayConfigV2['audit']>;
|
package/dist/core/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { warnDeprecatedJudgeModelAuto } from './judge-model-policy.js';
|
|
3
3
|
import { getJudgeProviderSpec, inferProviderIdFromConfig, isRemovedProviderId, normalizeLegacyProviderId, warnRemovedProviderId, } from './verdict/judge-catalog.js';
|
|
4
|
+
import { normalizeJudgeRuntimeConfig, } from './verdict/judge-runtime-config.js';
|
|
4
5
|
export const DEFAULT_JUDGE_LOCAL_OLLAMA = {
|
|
5
6
|
provider: 'ollama',
|
|
6
7
|
providerId: 'ollama',
|
|
@@ -95,6 +96,15 @@ export const DEFAULT_NOTIFICATIONS_V3 = {};
|
|
|
95
96
|
export const DEFAULT_APPROVAL_SIGNING_V3 = {
|
|
96
97
|
required: false,
|
|
97
98
|
};
|
|
99
|
+
export const DEFAULT_APPROVAL_CONFIG = {
|
|
100
|
+
flow: 'one_step',
|
|
101
|
+
autoReplayScopes: {
|
|
102
|
+
shell: true,
|
|
103
|
+
tool: false,
|
|
104
|
+
subagent: false,
|
|
105
|
+
},
|
|
106
|
+
executionLeaseMs: 60_000,
|
|
107
|
+
};
|
|
98
108
|
export const DEFAULT_EGRESS_V3 = {
|
|
99
109
|
enabled: false,
|
|
100
110
|
listenHost: '127.0.0.1',
|
|
@@ -148,6 +158,7 @@ export const DEFAULT_CONFIG_V4 = {
|
|
|
148
158
|
controlPlane: { ...DEFAULT_CONTROL_PLANE_V3 },
|
|
149
159
|
notifications: { ...DEFAULT_NOTIFICATIONS_V3 },
|
|
150
160
|
approvalSigning: { ...DEFAULT_APPROVAL_SIGNING_V3 },
|
|
161
|
+
approval: { ...DEFAULT_APPROVAL_CONFIG },
|
|
151
162
|
egress: { ...DEFAULT_EGRESS_V3 },
|
|
152
163
|
sandbox: { ...DEFAULT_SANDBOX_V3 },
|
|
153
164
|
audit: { ...DEFAULT_CONFIG_V2.audit },
|
|
@@ -190,6 +201,7 @@ export function migrateV2ToV3(v2, rawOverrides) {
|
|
|
190
201
|
controlPlane: { ...LEGACY_CONTROL_PLANE_V3 },
|
|
191
202
|
notifications: { ...DEFAULT_NOTIFICATIONS_V3 },
|
|
192
203
|
approvalSigning: { ...DEFAULT_APPROVAL_SIGNING_V3 },
|
|
204
|
+
approval: { ...DEFAULT_APPROVAL_CONFIG },
|
|
193
205
|
egress: { ...DEFAULT_EGRESS_V3 },
|
|
194
206
|
sandbox: { ...DEFAULT_SANDBOX_V3 },
|
|
195
207
|
audit: v2.audit,
|
|
@@ -283,6 +295,7 @@ export function normalizeJudgeConfig(judge) {
|
|
|
283
295
|
: null,
|
|
284
296
|
...(judge.cloudConsent ? { cloudConsent: judge.cloudConsent } : {}),
|
|
285
297
|
...(judge.credential ? { credential: judge.credential } : {}),
|
|
298
|
+
runtime: normalizeJudgeRuntimeConfig(judge.runtime),
|
|
286
299
|
};
|
|
287
300
|
}
|
|
288
301
|
let providerId = rawProviderId && normalizeLegacyProviderId(rawProviderId)
|
|
@@ -333,6 +346,7 @@ export function normalizeJudgeConfig(judge) {
|
|
|
333
346
|
...(judge.credential.ref ? { ref: judge.credential.ref } : {}),
|
|
334
347
|
};
|
|
335
348
|
}
|
|
349
|
+
normalized.runtime = normalizeJudgeRuntimeConfig(judge.runtime);
|
|
336
350
|
return normalized;
|
|
337
351
|
}
|
|
338
352
|
export function rejectTeamLayerJudgeSecrets(judge, source) {
|
|
@@ -395,6 +409,14 @@ function mergeV3FromRaw(base, raw) {
|
|
|
395
409
|
...base.approvalSigning,
|
|
396
410
|
...(raw.approvalSigning ?? {}),
|
|
397
411
|
},
|
|
412
|
+
approval: {
|
|
413
|
+
...base.approval,
|
|
414
|
+
...(raw.approval ?? {}),
|
|
415
|
+
autoReplayScopes: {
|
|
416
|
+
...base.approval.autoReplayScopes,
|
|
417
|
+
...(raw.approval?.autoReplayScopes ?? {}),
|
|
418
|
+
},
|
|
419
|
+
},
|
|
398
420
|
egress: {
|
|
399
421
|
...base.egress,
|
|
400
422
|
...(raw.egress ?? {}),
|
|
@@ -446,6 +468,14 @@ function normalizeV3Raw(raw) {
|
|
|
446
468
|
approvalSigning: {
|
|
447
469
|
required: raw.approvalSigning?.required === true,
|
|
448
470
|
},
|
|
471
|
+
approval: {
|
|
472
|
+
...DEFAULT_APPROVAL_CONFIG,
|
|
473
|
+
...(raw.approval ?? {}),
|
|
474
|
+
autoReplayScopes: {
|
|
475
|
+
...DEFAULT_APPROVAL_CONFIG.autoReplayScopes,
|
|
476
|
+
...(raw.approval?.autoReplayScopes ?? {}),
|
|
477
|
+
},
|
|
478
|
+
},
|
|
449
479
|
egress: {
|
|
450
480
|
...DEFAULT_EGRESS_V3,
|
|
451
481
|
...(raw.egress ?? {}),
|
|
@@ -676,6 +706,17 @@ export function normalizeConfig(config) {
|
|
|
676
706
|
approvalSigning: {
|
|
677
707
|
required: v4.approvalSigning?.required === true,
|
|
678
708
|
},
|
|
709
|
+
approval: {
|
|
710
|
+
flow: v4.approval?.flow === 'two_step' ? 'two_step' : DEFAULT_APPROVAL_CONFIG.flow,
|
|
711
|
+
autoReplayScopes: {
|
|
712
|
+
shell: v4.approval?.autoReplayScopes?.shell !== false,
|
|
713
|
+
tool: v4.approval?.autoReplayScopes?.tool === true,
|
|
714
|
+
subagent: v4.approval?.autoReplayScopes?.subagent === true,
|
|
715
|
+
},
|
|
716
|
+
executionLeaseMs: typeof v4.approval?.executionLeaseMs === 'number' && v4.approval.executionLeaseMs > 0
|
|
717
|
+
? v4.approval.executionLeaseMs
|
|
718
|
+
: DEFAULT_APPROVAL_CONFIG.executionLeaseMs,
|
|
719
|
+
},
|
|
679
720
|
egress: {
|
|
680
721
|
enabled: v4.egress?.enabled === true,
|
|
681
722
|
listenHost: normalizeEgressListenHost(typeof v4.egress?.listenHost === 'string' && v4.egress.listenHost.trim()
|
|
@@ -752,6 +793,14 @@ export function mergeConfig(existing, defaults = DEFAULT_CONFIG_V4) {
|
|
|
752
793
|
...defaults.approvalSigning,
|
|
753
794
|
...migrated.approvalSigning,
|
|
754
795
|
},
|
|
796
|
+
approval: {
|
|
797
|
+
...defaults.approval,
|
|
798
|
+
...migrated.approval,
|
|
799
|
+
autoReplayScopes: {
|
|
800
|
+
...defaults.approval.autoReplayScopes,
|
|
801
|
+
...migrated.approval.autoReplayScopes,
|
|
802
|
+
},
|
|
803
|
+
},
|
|
755
804
|
egress: {
|
|
756
805
|
...defaults.egress,
|
|
757
806
|
...migrated.egress,
|
package/dist/core/gate-engine.js
CHANGED
|
@@ -167,28 +167,28 @@ function applyFsScopePeripheralPolicy(outsideRepoPaths, result, options) {
|
|
|
167
167
|
return result;
|
|
168
168
|
}
|
|
169
169
|
function applySandboxOutsideBoundary(command, action, result, options) {
|
|
170
|
-
const outsideRepoPaths = collectOutsideRepoPaths(command, action.cwd, action.repoRoot);
|
|
170
|
+
const outsideRepoPaths = collectOutsideRepoPaths(command, action.cwd, action.repoRoot, options.trustedWorkspaceRoots);
|
|
171
171
|
return applySandboxFsScopeBoundary(outsideRepoPaths, result, options, {
|
|
172
172
|
redirect: command.includes('>'),
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
175
|
function applyShellPeripheralPolicy(command, action, result, options) {
|
|
176
|
-
const outsideRepoPaths = collectOutsideRepoPaths(command, action.cwd, action.repoRoot);
|
|
176
|
+
const outsideRepoPaths = collectOutsideRepoPaths(command, action.cwd, action.repoRoot, options.trustedWorkspaceRoots);
|
|
177
177
|
return applyFsScopePeripheralPolicy(outsideRepoPaths, result, options);
|
|
178
178
|
}
|
|
179
|
-
function outsideRepoPathsForToolAction(action) {
|
|
179
|
+
function outsideRepoPathsForToolAction(action, options) {
|
|
180
180
|
const payload = action.payload ?? {};
|
|
181
|
-
const paths = new Set(collectOutsideRepoPathsFromToolPayload(payload, action.cwd, action.repoRoot));
|
|
181
|
+
const paths = new Set(collectOutsideRepoPathsFromToolPayload(payload, action.cwd, action.repoRoot, options.trustedWorkspaceRoots));
|
|
182
182
|
const command = shellCommandFromPayload(payload);
|
|
183
183
|
if (command) {
|
|
184
|
-
for (const resolved of collectOutsideRepoPaths(command, action.cwd, action.repoRoot)) {
|
|
184
|
+
for (const resolved of collectOutsideRepoPaths(command, action.cwd, action.repoRoot, options.trustedWorkspaceRoots)) {
|
|
185
185
|
paths.add(resolved);
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
return [...paths];
|
|
189
189
|
}
|
|
190
190
|
function applyToolSandboxPolicies(action, result, options) {
|
|
191
|
-
const outsideRepoPaths = outsideRepoPathsForToolAction(action);
|
|
191
|
+
const outsideRepoPaths = outsideRepoPathsForToolAction(action, options);
|
|
192
192
|
const command = shellCommandFromPayload(action.payload ?? {});
|
|
193
193
|
let next = applySandboxFsScopeBoundary(outsideRepoPaths, result, options, {
|
|
194
194
|
redirect: command.includes('>'),
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { CorpusCase } from '../corpus/types.js';
|
|
2
|
+
import type { AuditRecord } from './audit-types.js';
|
|
3
|
+
export declare const HARVEST_REPORT_SCHEMA_VERSION = 1;
|
|
4
|
+
export type HarvestCandidateSource = 'deny_then_approve' | 'repeated_ask' | 'read_style_signal' | 'overrides_allow';
|
|
5
|
+
export type HarvestReviewOutcome = 'provably-benign' | 'accepted-benign' | 'reject';
|
|
6
|
+
export interface HarvestCandidate {
|
|
7
|
+
kind: 'shell';
|
|
8
|
+
command: string;
|
|
9
|
+
fingerprint: string;
|
|
10
|
+
reason: string;
|
|
11
|
+
sources: HarvestCandidateSource[];
|
|
12
|
+
askCount: number;
|
|
13
|
+
approvedAfterDeny: boolean;
|
|
14
|
+
}
|
|
15
|
+
export type AvailabilitySignal = 'missing_trusted_cwd' | 'judge_timeout' | 'judge_fallback';
|
|
16
|
+
export interface AvailabilityQueueItem {
|
|
17
|
+
kind: 'shell';
|
|
18
|
+
command: string;
|
|
19
|
+
fingerprint: string;
|
|
20
|
+
reason: string;
|
|
21
|
+
availabilitySignal: AvailabilitySignal;
|
|
22
|
+
judgeFallbackReason?: string;
|
|
23
|
+
askCount: number;
|
|
24
|
+
}
|
|
25
|
+
export interface HarvestReport {
|
|
26
|
+
schemaVersion: typeof HARVEST_REPORT_SCHEMA_VERSION;
|
|
27
|
+
/** Initial harvest scope — shell audit traces only. */
|
|
28
|
+
scope: 'shell';
|
|
29
|
+
candidates: HarvestCandidate[];
|
|
30
|
+
availabilityQueue: AvailabilityQueueItem[];
|
|
31
|
+
}
|
|
32
|
+
export declare function extractAvailabilityQueue(records: AuditRecord[]): AvailabilityQueueItem[];
|
|
33
|
+
/** Shell gate rows plus approval events — matches what `belay harvest list` needs. */
|
|
34
|
+
export declare function filterRecordsForHarvest(records: AuditRecord[], options?: {
|
|
35
|
+
since?: string;
|
|
36
|
+
until?: string;
|
|
37
|
+
}): AuditRecord[];
|
|
38
|
+
export declare function extractHarvestCandidates(records: AuditRecord[], options?: {
|
|
39
|
+
allowPatterns?: string[];
|
|
40
|
+
}): HarvestCandidate[];
|
|
41
|
+
export declare function buildHarvestReport(records: AuditRecord[], options?: {
|
|
42
|
+
allowPatterns?: string[];
|
|
43
|
+
}): HarvestReport;
|
|
44
|
+
export declare function applyHarvestReview(cases: CorpusCase[], params: {
|
|
45
|
+
command: string;
|
|
46
|
+
outcome: HarvestReviewOutcome;
|
|
47
|
+
reason?: string;
|
|
48
|
+
}): {
|
|
49
|
+
cases: CorpusCase[];
|
|
50
|
+
applied: boolean;
|
|
51
|
+
ok: boolean;
|
|
52
|
+
message: string;
|
|
53
|
+
};
|