@guilz-dev/belay 0.1.1 → 0.3.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 +55 -15
- package/dist/adapters/shared/gate-runtime.js +25 -9
- package/dist/bundle/claude-runtime.mjs +1786 -379
- package/dist/bundle/codex-runtime.mjs +1785 -379
- package/dist/bundle/cursor-runtime.mjs +1786 -379
- package/dist/cli.js +201 -11
- package/dist/commands/approve.js +11 -0
- package/dist/commands/classify-for-report.js +3 -3
- package/dist/commands/config.d.ts +70 -0
- package/dist/commands/config.js +494 -0
- package/dist/commands/doctor.js +3 -2
- package/dist/commands/explain.js +14 -14
- package/dist/commands/health-snapshot.d.ts +6 -0
- package/dist/commands/health-snapshot.js +34 -0
- package/dist/commands/judge.d.ts +90 -0
- package/dist/commands/judge.js +285 -0
- package/dist/commands/recover.js +2 -2
- package/dist/commands/status.js +2 -2
- package/dist/commands/stdin-key.d.ts +1 -0
- package/dist/commands/stdin-key.js +8 -0
- package/dist/conformance/guarantee-table.js +12 -0
- package/dist/conformance/types.d.ts +2 -0
- package/dist/core/approval.d.ts +3 -0
- package/dist/core/approval.js +18 -3
- package/dist/core/audit-io.d.ts +2 -0
- package/dist/core/audit-io.js +14 -0
- package/dist/core/audit-query.js +5 -1
- package/dist/core/capability/index.d.ts +2 -2
- package/dist/core/capability/index.js +2 -2
- package/dist/core/capability/paths.d.ts +1 -0
- package/dist/core/capability/paths.js +74 -6
- package/dist/core/capability/reasons.d.ts +3 -0
- package/dist/core/capability/reasons.js +8 -0
- package/dist/core/classify-tool.js +97 -27
- package/dist/core/config-layers.js +2 -1
- package/dist/core/config.d.ts +23 -3
- package/dist/core/config.js +112 -13
- package/dist/core/credential-store.d.ts +11 -0
- package/dist/core/credential-store.js +60 -0
- package/dist/core/gate-contract.d.ts +1 -1
- package/dist/core/gate-contract.js +1 -1
- package/dist/core/gate-engine.js +105 -14
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +2 -2
- package/dist/core/integrity.d.ts +2 -0
- package/dist/core/integrity.js +13 -0
- package/dist/core/judge-api-key.d.ts +19 -0
- package/dist/core/judge-api-key.js +74 -0
- package/dist/core/judge-cloud-consent.d.ts +13 -0
- package/dist/core/judge-cloud-consent.js +38 -0
- package/dist/core/judge-config.d.ts +45 -4
- package/dist/core/judge-config.js +266 -44
- package/dist/core/judge-doctor.d.ts +6 -1
- package/dist/core/judge-doctor.js +148 -97
- package/dist/core/judge-model-discovery.d.ts +24 -0
- package/dist/core/judge-model-discovery.js +168 -0
- package/dist/core/judge-model-policy.d.ts +5 -0
- package/dist/core/judge-model-policy.js +21 -0
- package/dist/core/judge-runtime-detection.d.ts +9 -0
- package/dist/core/judge-runtime-detection.js +68 -0
- package/dist/core/transactional/diff-evaluator.js +1 -19
- package/dist/core/types.d.ts +7 -3
- package/dist/core/{v2 → verdict}/adapter.d.ts +1 -0
- package/dist/core/{v2 → verdict}/adapter.js +16 -4
- package/dist/core/{v2 → verdict}/containment.d.ts +5 -0
- package/dist/core/{v2 → verdict}/containment.js +32 -2
- package/dist/core/{v2 → verdict}/egress-classify.js +3 -0
- package/dist/core/verdict/judge-catalog.d.ts +40 -0
- package/dist/core/verdict/judge-catalog.js +148 -0
- package/dist/core/verdict/judge-cli.d.ts +23 -0
- package/dist/core/verdict/judge-cli.js +280 -0
- package/dist/core/{v2 → verdict}/judge-factory.d.ts +15 -4
- package/dist/core/verdict/judge-factory.js +178 -0
- package/dist/core/{v2 → verdict}/judge.d.ts +20 -1
- package/dist/core/{v2 → verdict}/judge.js +85 -18
- package/dist/core/{v2 → verdict}/launcher-resolve.js +72 -1
- package/dist/core/verdict/persistent-paths.d.ts +8 -0
- package/dist/core/verdict/persistent-paths.js +52 -0
- package/dist/core/{v2 → verdict}/types.d.ts +6 -2
- package/dist/core/{v2 → verdict}/verdict.js +176 -46
- package/dist/corpus/evaluate.js +2 -2
- package/dist/installer.js +66 -14
- package/dist/types.d.ts +8 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
- package/skills/belay/SKILL.md +8 -7
- package/dist/commands/init-wizard.d.ts +0 -16
- package/dist/commands/init-wizard.js +0 -50
- package/dist/core/v2/judge-factory.js +0 -75
- /package/dist/core/{v2 → verdict}/egress-classify.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/fingerprint.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/fingerprint.js +0 -0
- /package/dist/core/{v2 → verdict}/index.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/index.js +0 -0
- /package/dist/core/{v2 → verdict}/judge-audit.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/judge-audit.js +0 -0
- /package/dist/core/{v2 → verdict}/judge-outbound.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/judge-outbound.js +0 -0
- /package/dist/core/{v2 → verdict}/launcher-resolve.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/overrides.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/overrides.js +0 -0
- /package/dist/core/{v2 → verdict}/parser.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/parser.js +0 -0
- /package/dist/core/{v2 → verdict}/types.js +0 -0
- /package/dist/core/{v2 → verdict}/verdict.d.ts +0 -0
|
@@ -3,7 +3,9 @@ import { canonicalStringify, toolFingerprint } from './fingerprint.js';
|
|
|
3
3
|
import { matchesSensitivePath } from './glob.js';
|
|
4
4
|
import { pathWithinRoot, relativeWithinRepo } from './path-utils.js';
|
|
5
5
|
import { scrubValue } from './scrub.js';
|
|
6
|
-
import { classifyShell } from './
|
|
6
|
+
import { classifyShell, resolveClassifierTrustedCwd } from './verdict/adapter.js';
|
|
7
|
+
import { mutationPrescanRequiresAsk, tier1RequiresAsk } from './verdict/judge.js';
|
|
8
|
+
import { createJudgeFromConfig } from './verdict/judge-factory.js';
|
|
7
9
|
const DEFAULT_SENSITIVE_PATHS = ['.env', '.env.*', '**/credentials/**'];
|
|
8
10
|
const FILE_WRITE_TOOL_NAMES = new Set(['write']);
|
|
9
11
|
const FILE_EDIT_TOOL_NAMES = new Set([
|
|
@@ -74,6 +76,74 @@ function applyPatchTargets(patch) {
|
|
|
74
76
|
function normalizedToolName(toolName) {
|
|
75
77
|
return toolName.trim().toLowerCase();
|
|
76
78
|
}
|
|
79
|
+
function resolveFileTier1Judge(config, options, repoRoot) {
|
|
80
|
+
return options.tier1Judge ?? createJudgeFromConfig(config, { repoRoot });
|
|
81
|
+
}
|
|
82
|
+
async function classifyFileMutationWithTier1(params) {
|
|
83
|
+
const judge = resolveFileTier1Judge(params.config, params.options, params.repoRoot);
|
|
84
|
+
const trustedCwd = resolveClassifierTrustedCwd(params.cwd, params.options);
|
|
85
|
+
const prescan = mutationPrescanRequiresAsk({
|
|
86
|
+
targets: [params.filePath],
|
|
87
|
+
cwd: params.cwd,
|
|
88
|
+
repoRoot: params.repoRoot,
|
|
89
|
+
trustedCwd,
|
|
90
|
+
sensitivePaths: params.options.sensitivePaths ?? params.config.classifier.sensitivePaths,
|
|
91
|
+
});
|
|
92
|
+
if (prescan) {
|
|
93
|
+
return {
|
|
94
|
+
verdict: 'deny_pending_approval',
|
|
95
|
+
reason: 'tier1_catastrophic',
|
|
96
|
+
summary: params.filePath,
|
|
97
|
+
fingerprint: toolFingerprint(params.toolName, { path: params.filePath }, params.repoRoot),
|
|
98
|
+
assessment: {
|
|
99
|
+
reversibility: 'irreversible',
|
|
100
|
+
external: params.locationLabel === 'outside_repo',
|
|
101
|
+
blastRadius: params.locationLabel === 'outside_repo'
|
|
102
|
+
? 'outside the repository'
|
|
103
|
+
: 'sensitive repository file',
|
|
104
|
+
confidence: 0.95,
|
|
105
|
+
signals: [...params.signals, 'tier1_catastrophic', prescan.reason],
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const tier1Text = `file_mutation: ${params.toolKind} path=${params.resolvedPath} location=${params.locationLabel}`;
|
|
110
|
+
const tier1 = await judge.evaluate({
|
|
111
|
+
text: tier1Text,
|
|
112
|
+
context: { cwd: params.cwd, repoRoot: params.repoRoot },
|
|
113
|
+
});
|
|
114
|
+
const fingerprint = toolFingerprint(params.toolName, { path: params.filePath }, params.repoRoot);
|
|
115
|
+
if (tier1RequiresAsk(tier1)) {
|
|
116
|
+
return {
|
|
117
|
+
verdict: 'deny_pending_approval',
|
|
118
|
+
reason: 'tier1_catastrophic',
|
|
119
|
+
summary: params.filePath,
|
|
120
|
+
fingerprint,
|
|
121
|
+
assessment: {
|
|
122
|
+
reversibility: 'irreversible',
|
|
123
|
+
external: params.locationLabel === 'outside_repo',
|
|
124
|
+
blastRadius: params.locationLabel === 'outside_repo'
|
|
125
|
+
? 'outside the repository'
|
|
126
|
+
: 'sensitive repository file',
|
|
127
|
+
confidence: 0.82,
|
|
128
|
+
signals: [...params.signals, 'tier1_catastrophic', tier1.reason],
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const reason = params.isDelete ? 'file_delete' : 'file_mutation';
|
|
133
|
+
return {
|
|
134
|
+
verdict: 'allow_flagged',
|
|
135
|
+
reason,
|
|
136
|
+
summary: params.filePath,
|
|
137
|
+
fingerprint,
|
|
138
|
+
assessment: {
|
|
139
|
+
reversibility: 'recoverable_with_cost',
|
|
140
|
+
external: params.locationLabel === 'outside_repo',
|
|
141
|
+
blastRadius: params.locationLabel === 'outside_repo' ? 'outside the repository' : 'this repository',
|
|
142
|
+
confidence: 0.72,
|
|
143
|
+
signals: [...params.signals, 'tier1_restorable', tier1.reason],
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
77
147
|
export async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
|
|
78
148
|
const toolName = String(payload.tool_name ?? '');
|
|
79
149
|
const toolKind = normalizedToolName(toolName);
|
|
@@ -176,35 +246,35 @@ export async function classifyToolUse(payload, repoRoot, cwd, config, options =
|
|
|
176
246
|
const relativePath = relativeWithinRepo(repoRoot, resolvedPath);
|
|
177
247
|
if (relativePath === null) {
|
|
178
248
|
signals.push('outside_repo_path');
|
|
179
|
-
return {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
};
|
|
249
|
+
return classifyFileMutationWithTier1({
|
|
250
|
+
toolName,
|
|
251
|
+
toolKind,
|
|
252
|
+
filePath,
|
|
253
|
+
resolvedPath,
|
|
254
|
+
repoRoot,
|
|
255
|
+
cwd,
|
|
256
|
+
config,
|
|
257
|
+
options,
|
|
258
|
+
signals,
|
|
259
|
+
isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
|
|
260
|
+
locationLabel: 'outside_repo',
|
|
261
|
+
});
|
|
192
262
|
}
|
|
193
263
|
if (matchesSensitivePath(relativePath, sensitivePaths)) {
|
|
194
264
|
signals.push('sensitive_path');
|
|
195
|
-
return {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
};
|
|
265
|
+
return classifyFileMutationWithTier1({
|
|
266
|
+
toolName,
|
|
267
|
+
toolKind,
|
|
268
|
+
filePath,
|
|
269
|
+
resolvedPath,
|
|
270
|
+
repoRoot,
|
|
271
|
+
cwd,
|
|
272
|
+
config,
|
|
273
|
+
options,
|
|
274
|
+
signals,
|
|
275
|
+
isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
|
|
276
|
+
locationLabel: 'sensitive_path',
|
|
277
|
+
});
|
|
208
278
|
}
|
|
209
279
|
if (FILE_DELETE_TOOL_NAMES.has(toolKind)) {
|
|
210
280
|
signals.push('file_delete');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { applyConfigPreset } from '../presets.js';
|
|
3
|
-
import { DEFAULT_CONFIG_V3, mergeConfig } from './config.js';
|
|
3
|
+
import { DEFAULT_CONFIG_V3, mergeConfig, rejectTeamLayerJudgeSecrets } from './config.js';
|
|
4
4
|
export function teamConfigPath(homedir = () => process.env.HOME ?? process.env.USERPROFILE ?? '') {
|
|
5
5
|
const xdg = process.env.XDG_CONFIG_HOME?.trim();
|
|
6
6
|
const base = xdg || path.join(homedir(), '.config');
|
|
@@ -40,6 +40,7 @@ export function resolveLayeredConfig(params) {
|
|
|
40
40
|
const teamRaw = teamFile.preset
|
|
41
41
|
? applyConfigPreset(teamFile.preset, teamFile.config ?? {})
|
|
42
42
|
: (teamFile.config ?? params.teamConfig);
|
|
43
|
+
rejectTeamLayerJudgeSecrets(teamRaw.judge, 'team');
|
|
43
44
|
config = mergeConfigLayer(config, asV3Layer(teamRaw));
|
|
44
45
|
provenance.push({
|
|
45
46
|
path: params.teamConfigPath ?? teamConfigPath(),
|
package/dist/core/config.d.ts
CHANGED
|
@@ -76,13 +76,32 @@ export interface BelayRedactionConfig {
|
|
|
76
76
|
maskKeyValueSecrets: boolean;
|
|
77
77
|
maskHighEntropyStrings: boolean;
|
|
78
78
|
}
|
|
79
|
-
export type JudgeProvider = 'ollama' | 'openai-compatible';
|
|
79
|
+
export type JudgeProvider = 'ollama' | 'openai-compatible' | 'anthropic';
|
|
80
|
+
export type JudgeProviderId = 'ollama' | 'codex' | 'claude' | 'cursor';
|
|
81
|
+
/** Read-only legacy ids; preserved on load with warning until `belay config` migrates. */
|
|
82
|
+
export type DeprecatedJudgeProviderId = 'openrouter' | 'custom';
|
|
83
|
+
export type JudgeCredentialMode = 'project' | 'apiKey';
|
|
84
|
+
export type JudgeCredentialRef = `store:judge` | `env:${string}`;
|
|
85
|
+
export interface JudgeCredentialConfig {
|
|
86
|
+
mode: JudgeCredentialMode;
|
|
87
|
+
ref?: JudgeCredentialRef;
|
|
88
|
+
}
|
|
89
|
+
export interface JudgeCloudConsent {
|
|
90
|
+
accepted: boolean;
|
|
91
|
+
at: string;
|
|
92
|
+
providerId: JudgeProviderId;
|
|
93
|
+
endpoint: string;
|
|
94
|
+
by: string;
|
|
95
|
+
}
|
|
80
96
|
export interface BelayJudgeConfig {
|
|
81
97
|
provider: JudgeProvider;
|
|
98
|
+
providerId?: JudgeProviderId | DeprecatedJudgeProviderId;
|
|
82
99
|
model: string;
|
|
83
100
|
timeoutMs: number;
|
|
84
101
|
endpoint: string | null;
|
|
85
102
|
keepAlive: string | null;
|
|
103
|
+
cloudConsent?: JudgeCloudConsent;
|
|
104
|
+
credential?: JudgeCredentialConfig;
|
|
86
105
|
}
|
|
87
106
|
export declare const DEFAULT_JUDGE_LOCAL_OLLAMA: BelayJudgeConfig;
|
|
88
107
|
export declare const DEFAULT_JUDGE_OPENAI_COMPATIBLE_TEMPLATE: BelayJudgeConfig;
|
|
@@ -155,7 +174,7 @@ export declare const DEFAULT_CONFIDENCE_THRESHOLDS: BelayConfidenceThresholds;
|
|
|
155
174
|
export declare const DEFAULT_MODEL_ASSIST: BelayModelAssistConfig;
|
|
156
175
|
export declare const DEFAULT_TRANSACTIONAL_V3: BelayTransactionalConfig;
|
|
157
176
|
export declare const LEGACY_POLICY_V3: BelayPolicyConfig;
|
|
158
|
-
/** Fresh
|
|
177
|
+
/** Fresh install defaults: recoverable-first with opaque/unparseable fail-closed. */
|
|
159
178
|
export declare const DEFAULT_POLICY_V3: BelayPolicyConfig;
|
|
160
179
|
export declare const DEFAULT_OVERRIDES_V3: BelayOverridesConfig;
|
|
161
180
|
export declare const DEFAULT_REDACTION_V3: BelayRedactionConfig;
|
|
@@ -180,8 +199,9 @@ export declare function isConfigV1(value: unknown): value is BelayConfigV1;
|
|
|
180
199
|
export declare function isConfigV2(value: unknown): value is BelayConfigV2;
|
|
181
200
|
export declare function isConfigV3(value: unknown): value is BelayConfigV4;
|
|
182
201
|
export declare function isConfigV4(value: unknown): value is BelayConfigV4;
|
|
183
|
-
export declare function normalizeJudgeProvider(provider: string | undefined): 'ollama' | 'openai-compatible';
|
|
202
|
+
export declare function normalizeJudgeProvider(provider: string | undefined): 'ollama' | 'openai-compatible' | 'anthropic';
|
|
184
203
|
export declare function normalizeJudgeConfig(judge: BelayJudgeConfig): BelayJudgeConfig;
|
|
204
|
+
export declare function rejectTeamLayerJudgeSecrets(judge: Partial<BelayJudgeConfig> | undefined, source: 'team' | 'repo'): void;
|
|
185
205
|
export declare function migrateV3ToV4(v3: BelayConfigV4, raw?: RawConfigInput): BelayConfigV4;
|
|
186
206
|
type RawConfigInput = Partial<{
|
|
187
207
|
version: number;
|
package/dist/core/config.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { warnDeprecatedJudgeModelAuto } from './judge-model-policy.js';
|
|
3
|
+
import { getJudgeProviderSpec, inferProviderIdFromConfig, isRemovedProviderId, normalizeLegacyProviderId, warnRemovedProviderId, } from './verdict/judge-catalog.js';
|
|
2
4
|
export const DEFAULT_JUDGE_LOCAL_OLLAMA = {
|
|
3
5
|
provider: 'ollama',
|
|
6
|
+
providerId: 'ollama',
|
|
4
7
|
model: 'gemma4:e2b',
|
|
5
8
|
endpoint: 'http://localhost:11434',
|
|
6
9
|
timeoutMs: 25000,
|
|
@@ -8,7 +11,8 @@ export const DEFAULT_JUDGE_LOCAL_OLLAMA = {
|
|
|
8
11
|
};
|
|
9
12
|
export const DEFAULT_JUDGE_OPENAI_COMPATIBLE_TEMPLATE = {
|
|
10
13
|
provider: 'openai-compatible',
|
|
11
|
-
|
|
14
|
+
providerId: 'codex',
|
|
15
|
+
model: 'gpt-5.3-codex-high',
|
|
12
16
|
timeoutMs: 8000,
|
|
13
17
|
endpoint: null,
|
|
14
18
|
keepAlive: null,
|
|
@@ -45,9 +49,9 @@ export const LEGACY_POLICY_V3 = {
|
|
|
45
49
|
transactional: { ...DEFAULT_TRANSACTIONAL_V3 },
|
|
46
50
|
fenceWarnThreshold: DEFAULT_FENCE_WARN_THRESHOLD,
|
|
47
51
|
};
|
|
48
|
-
/** Fresh
|
|
52
|
+
/** Fresh install defaults: recoverable-first with opaque/unparseable fail-closed. */
|
|
49
53
|
export const DEFAULT_POLICY_V3 = {
|
|
50
|
-
unknownLocalEffect: '
|
|
54
|
+
unknownLocalEffect: 'allow_flagged',
|
|
51
55
|
unparseableShell: 'deny',
|
|
52
56
|
codexUnmappedTool: 'deny',
|
|
53
57
|
confidenceThresholds: { ...DEFAULT_CONFIDENCE_THRESHOLDS },
|
|
@@ -209,44 +213,139 @@ export function isConfigV4(value) {
|
|
|
209
213
|
return typeof value === 'object' && value !== null && value.version === 4;
|
|
210
214
|
}
|
|
211
215
|
export function normalizeJudgeProvider(provider) {
|
|
216
|
+
if (provider === 'anthropic') {
|
|
217
|
+
return 'anthropic';
|
|
218
|
+
}
|
|
212
219
|
if (provider === 'openai-compatible' || provider === 'cursor') {
|
|
213
220
|
return 'openai-compatible';
|
|
214
221
|
}
|
|
215
222
|
return 'ollama';
|
|
216
223
|
}
|
|
224
|
+
function defaultJudgeTemplateForProvider(provider) {
|
|
225
|
+
if (provider === 'ollama') {
|
|
226
|
+
return DEFAULT_JUDGE_LOCAL_OLLAMA;
|
|
227
|
+
}
|
|
228
|
+
if (provider === 'anthropic') {
|
|
229
|
+
return {
|
|
230
|
+
provider: 'anthropic',
|
|
231
|
+
providerId: 'claude',
|
|
232
|
+
model: 'claude-sonnet-4-6',
|
|
233
|
+
timeoutMs: 8000,
|
|
234
|
+
endpoint: null,
|
|
235
|
+
keepAlive: null,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return DEFAULT_JUDGE_OPENAI_COMPATIBLE_TEMPLATE;
|
|
239
|
+
}
|
|
217
240
|
function synthesizeJudgeFromRaw(raw) {
|
|
218
241
|
const judge = raw.judge;
|
|
219
242
|
if (judge?.provider) {
|
|
220
|
-
const
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
243
|
+
const rawProvider = String(judge.provider);
|
|
244
|
+
const provider = normalizeJudgeProvider(rawProvider);
|
|
245
|
+
const base = defaultJudgeTemplateForProvider(provider);
|
|
246
|
+
const providerId = judge.providerId && normalizeLegacyProviderId(judge.providerId)
|
|
247
|
+
? normalizeLegacyProviderId(judge.providerId)
|
|
248
|
+
: rawProvider === 'cursor'
|
|
249
|
+
? 'cursor'
|
|
250
|
+
: undefined;
|
|
224
251
|
return normalizeJudgeConfig({
|
|
225
252
|
...base,
|
|
226
253
|
...judge,
|
|
227
254
|
provider,
|
|
255
|
+
...(providerId ? { providerId } : {}),
|
|
228
256
|
});
|
|
229
257
|
}
|
|
230
258
|
return { ...DEFAULT_JUDGE_LOCAL_OLLAMA };
|
|
231
259
|
}
|
|
232
260
|
export function normalizeJudgeConfig(judge) {
|
|
233
261
|
const provider = normalizeJudgeProvider(judge.provider);
|
|
234
|
-
const base = provider
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
262
|
+
const base = defaultJudgeTemplateForProvider(provider);
|
|
263
|
+
const rawProviderId = judge.providerId ? String(judge.providerId) : undefined;
|
|
264
|
+
if (rawProviderId && isRemovedProviderId(rawProviderId)) {
|
|
265
|
+
warnRemovedProviderId(rawProviderId);
|
|
266
|
+
let model = typeof judge.model === 'string' && judge.model.trim() ? judge.model.trim() : base.model;
|
|
267
|
+
if (model === 'auto') {
|
|
268
|
+
warnDeprecatedJudgeModelAuto();
|
|
269
|
+
model = base.model;
|
|
270
|
+
}
|
|
271
|
+
const timeoutMs = typeof judge.timeoutMs === 'number' && judge.timeoutMs > 0 ? judge.timeoutMs : base.timeoutMs;
|
|
272
|
+
const endpoint = typeof judge.endpoint === 'string' && judge.endpoint.trim() ? judge.endpoint.trim() : null;
|
|
273
|
+
return {
|
|
274
|
+
provider,
|
|
275
|
+
providerId: rawProviderId,
|
|
276
|
+
model,
|
|
277
|
+
timeoutMs,
|
|
278
|
+
endpoint,
|
|
279
|
+
keepAlive: provider === 'ollama' && typeof judge.keepAlive === 'string' && judge.keepAlive.trim()
|
|
280
|
+
? judge.keepAlive.trim()
|
|
281
|
+
: provider === 'ollama'
|
|
282
|
+
? DEFAULT_JUDGE_LOCAL_OLLAMA.keepAlive
|
|
283
|
+
: null,
|
|
284
|
+
...(judge.cloudConsent ? { cloudConsent: judge.cloudConsent } : {}),
|
|
285
|
+
...(judge.credential ? { credential: judge.credential } : {}),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
let providerId = rawProviderId && normalizeLegacyProviderId(rawProviderId)
|
|
289
|
+
? normalizeLegacyProviderId(rawProviderId)
|
|
290
|
+
: inferProviderIdFromConfig({ ...judge, provider });
|
|
291
|
+
const spec = getJudgeProviderSpec(providerId);
|
|
292
|
+
if (spec && spec.driver !== provider) {
|
|
293
|
+
providerId = inferProviderIdFromConfig({ ...judge, provider });
|
|
294
|
+
}
|
|
295
|
+
const catalogSpec = getJudgeProviderSpec(providerId);
|
|
296
|
+
let model = typeof judge.model === 'string' && judge.model.trim() ? judge.model.trim() : base.model;
|
|
297
|
+
if (model === 'auto' && catalogSpec?.defaultModel) {
|
|
298
|
+
warnDeprecatedJudgeModelAuto();
|
|
299
|
+
model = catalogSpec.defaultModel;
|
|
300
|
+
}
|
|
301
|
+
if (!model && catalogSpec?.defaultModel) {
|
|
302
|
+
model = catalogSpec.defaultModel;
|
|
303
|
+
}
|
|
238
304
|
const timeoutMs = typeof judge.timeoutMs === 'number' && judge.timeoutMs > 0 ? judge.timeoutMs : base.timeoutMs;
|
|
239
|
-
|
|
305
|
+
let endpoint = typeof judge.endpoint === 'string' && judge.endpoint.trim() ? judge.endpoint.trim() : null;
|
|
306
|
+
if (!endpoint && catalogSpec?.defaultEndpoint) {
|
|
307
|
+
endpoint = catalogSpec.defaultEndpoint;
|
|
308
|
+
}
|
|
309
|
+
const normalized = {
|
|
240
310
|
provider,
|
|
311
|
+
providerId,
|
|
241
312
|
model,
|
|
242
313
|
timeoutMs,
|
|
243
|
-
endpoint
|
|
314
|
+
endpoint,
|
|
244
315
|
keepAlive: provider === 'ollama' && typeof judge.keepAlive === 'string' && judge.keepAlive.trim()
|
|
245
316
|
? judge.keepAlive.trim()
|
|
246
317
|
: provider === 'ollama'
|
|
247
318
|
? DEFAULT_JUDGE_LOCAL_OLLAMA.keepAlive
|
|
248
319
|
: null,
|
|
249
320
|
};
|
|
321
|
+
if (judge.cloudConsent?.accepted) {
|
|
322
|
+
normalized.cloudConsent = {
|
|
323
|
+
accepted: true,
|
|
324
|
+
at: judge.cloudConsent.at,
|
|
325
|
+
providerId: judge.cloudConsent.providerId ?? providerId,
|
|
326
|
+
endpoint: judge.cloudConsent.endpoint,
|
|
327
|
+
by: judge.cloudConsent.by,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
if (judge.credential?.mode === 'project' || judge.credential?.mode === 'apiKey') {
|
|
331
|
+
normalized.credential = {
|
|
332
|
+
mode: judge.credential.mode,
|
|
333
|
+
...(judge.credential.ref ? { ref: judge.credential.ref } : {}),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
return normalized;
|
|
337
|
+
}
|
|
338
|
+
export function rejectTeamLayerJudgeSecrets(judge, source) {
|
|
339
|
+
if (source !== 'team' || !judge) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (judge.credential?.mode === 'apiKey') {
|
|
343
|
+
throw new Error('team config cannot set judge.credential.mode to apiKey.');
|
|
344
|
+
}
|
|
345
|
+
const raw = judge;
|
|
346
|
+
if (raw.credential && 'key' in raw.credential && raw.credential.key) {
|
|
347
|
+
throw new Error('team config cannot contain inline judge credential keys.');
|
|
348
|
+
}
|
|
250
349
|
}
|
|
251
350
|
export function migrateV3ToV4(v3, raw) {
|
|
252
351
|
return normalizeConfig({
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface JudgeCredentialStoreFile {
|
|
2
|
+
version: 1;
|
|
3
|
+
judge?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function credentialStorePath(stateDir: string): string;
|
|
6
|
+
export declare function writeJudgeCredentialStore(stateDir: string, apiKey: string): Promise<string>;
|
|
7
|
+
export declare function clearJudgeCredentialStore(stateDir: string): Promise<void>;
|
|
8
|
+
export declare function readJudgeCredentialStore(stateDir: string): Promise<string | null>;
|
|
9
|
+
export declare function judgeCredentialStoreExists(stateDir: string): Promise<boolean>;
|
|
10
|
+
export type CredentialRef = `store:judge` | `env:${string}`;
|
|
11
|
+
export declare function parseCredentialRef(ref: string): CredentialRef | null;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const STORE_FILENAME = 'credentials.json';
|
|
4
|
+
export function credentialStorePath(stateDir) {
|
|
5
|
+
return path.join(stateDir, STORE_FILENAME);
|
|
6
|
+
}
|
|
7
|
+
export async function writeJudgeCredentialStore(stateDir, apiKey) {
|
|
8
|
+
await mkdir(stateDir, { recursive: true, mode: 0o700 });
|
|
9
|
+
const filePath = credentialStorePath(stateDir);
|
|
10
|
+
const payload = {
|
|
11
|
+
version: 1,
|
|
12
|
+
judge: apiKey.trim(),
|
|
13
|
+
};
|
|
14
|
+
await writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
15
|
+
encoding: 'utf8',
|
|
16
|
+
mode: 0o600,
|
|
17
|
+
});
|
|
18
|
+
await chmod(filePath, 0o600);
|
|
19
|
+
return filePath;
|
|
20
|
+
}
|
|
21
|
+
export async function clearJudgeCredentialStore(stateDir) {
|
|
22
|
+
try {
|
|
23
|
+
await unlink(credentialStorePath(stateDir));
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error.code !== 'ENOENT') {
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function readJudgeCredentialStore(stateDir) {
|
|
32
|
+
const filePath = credentialStorePath(stateDir);
|
|
33
|
+
try {
|
|
34
|
+
const raw = await readFile(filePath, 'utf8');
|
|
35
|
+
const parsed = JSON.parse(raw);
|
|
36
|
+
const key = parsed.judge?.trim();
|
|
37
|
+
return key || null;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function judgeCredentialStoreExists(stateDir) {
|
|
44
|
+
try {
|
|
45
|
+
await readFile(credentialStorePath(stateDir), 'utf8');
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function parseCredentialRef(ref) {
|
|
53
|
+
if (ref === 'store:judge') {
|
|
54
|
+
return ref;
|
|
55
|
+
}
|
|
56
|
+
if (ref.startsWith('env:') && ref.length > 4) {
|
|
57
|
+
return ref;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
@@ -28,7 +28,7 @@ export interface GateVerdict extends GatePermissionResponse {
|
|
|
28
28
|
approvalId?: string;
|
|
29
29
|
wouldBlock: boolean;
|
|
30
30
|
mode: 'enforce' | 'audit';
|
|
31
|
-
|
|
31
|
+
axes?: ClassifyResult['axes'];
|
|
32
32
|
}
|
|
33
33
|
export declare function isGatedAction(value: unknown): value is GatedAction;
|
|
34
34
|
export declare function classifyResultToGateVerdict(params: {
|
package/dist/core/gate-engine.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { allPathsAllowlisted } from './capability/allowlist.js';
|
|
2
|
-
import { collectOutsideRepoPaths } from './capability/paths.js';
|
|
2
|
+
import { collectOutsideRepoPaths, collectOutsideRepoPathsFromToolPayload, } from './capability/paths.js';
|
|
3
3
|
import { classifySubagent } from './classify-subagent.js';
|
|
4
4
|
import { classifyToolUse } from './classify-tool.js';
|
|
5
5
|
import { classifierOptionsFromConfig } from './config.js';
|
|
6
6
|
import { GATE_CONTRACT_VERSION } from './gate-contract.js';
|
|
7
7
|
import { mergeAgentAssessment } from './judgment.js';
|
|
8
|
-
import { classifyShell } from './
|
|
8
|
+
import { classifyShell } from './verdict/adapter.js';
|
|
9
9
|
export class GateNormalizationError extends Error {
|
|
10
10
|
reason = 'normalization_failed';
|
|
11
11
|
constructor(message) {
|
|
@@ -92,17 +92,63 @@ export function normalizeGatedAction(params) {
|
|
|
92
92
|
agentAssessment,
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
|
-
function
|
|
96
|
-
if (options.brokerFsScope
|
|
97
|
-
result
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
95
|
+
function applySandboxFsScopeBoundary(outsideRepoPaths, result, options, hints = {}) {
|
|
96
|
+
if (!options.brokerFsScope) {
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
if (outsideRepoPaths.length === 0) {
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
if (options.fsScopeAllowlist && allPathsAllowlisted(outsideRepoPaths, options.fsScopeAllowlist)) {
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
if (result.verdict === 'deny_pending_approval') {
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
const redirect = hints.redirect === true ||
|
|
109
|
+
result.reason === 'read_only' ||
|
|
110
|
+
result.assessment.signals.includes('read_only') ||
|
|
111
|
+
result.reason === 'outside_repo_redirect';
|
|
112
|
+
return {
|
|
113
|
+
...result,
|
|
114
|
+
verdict: 'deny_pending_approval',
|
|
115
|
+
reason: redirect ? 'outside_repo_redirect' : 'outside_repo_mutation',
|
|
116
|
+
assessment: {
|
|
117
|
+
...result.assessment,
|
|
118
|
+
external: true,
|
|
119
|
+
reversibility: 'irreversible',
|
|
120
|
+
signals: [...result.assessment.signals, 'sandbox_boundary_expected'],
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function applyFsScopePeripheralPolicy(outsideRepoPaths, result, options) {
|
|
125
|
+
if (!options.brokerFsScope) {
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
if (outsideRepoPaths.length === 0) {
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
if (options.fsScopeAllowlist && allPathsAllowlisted(outsideRepoPaths, options.fsScopeAllowlist)) {
|
|
132
|
+
if (result.verdict === 'deny_pending_approval' &&
|
|
133
|
+
(result.reason === 'outside_repo_mutation' ||
|
|
134
|
+
result.reason === 'outside_repo_redirect' ||
|
|
135
|
+
result.reason === 'repo_outside_mutation' ||
|
|
136
|
+
result.axes?.location === 'repo_outside')) {
|
|
137
|
+
return {
|
|
138
|
+
...result,
|
|
139
|
+
verdict: 'allow_flagged',
|
|
140
|
+
reason: 'capability_fs_hint',
|
|
141
|
+
assessment: {
|
|
142
|
+
...result.assessment,
|
|
143
|
+
signals: [
|
|
144
|
+
...result.assessment.signals,
|
|
145
|
+
'capability_fs_hint',
|
|
146
|
+
'sandbox_boundary_expected',
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (result.verdict === 'allow' || result.verdict === 'allow_flagged') {
|
|
106
152
|
return {
|
|
107
153
|
...result,
|
|
108
154
|
verdict: 'allow_flagged',
|
|
@@ -120,6 +166,36 @@ function applyShellPeripheralPolicy(command, action, result, options) {
|
|
|
120
166
|
}
|
|
121
167
|
return result;
|
|
122
168
|
}
|
|
169
|
+
function applySandboxOutsideBoundary(command, action, result, options) {
|
|
170
|
+
const outsideRepoPaths = collectOutsideRepoPaths(command, action.cwd, action.repoRoot);
|
|
171
|
+
return applySandboxFsScopeBoundary(outsideRepoPaths, result, options, {
|
|
172
|
+
redirect: command.includes('>'),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function applyShellPeripheralPolicy(command, action, result, options) {
|
|
176
|
+
const outsideRepoPaths = collectOutsideRepoPaths(command, action.cwd, action.repoRoot);
|
|
177
|
+
return applyFsScopePeripheralPolicy(outsideRepoPaths, result, options);
|
|
178
|
+
}
|
|
179
|
+
function outsideRepoPathsForToolAction(action) {
|
|
180
|
+
const payload = action.payload ?? {};
|
|
181
|
+
const paths = new Set(collectOutsideRepoPathsFromToolPayload(payload, action.cwd, action.repoRoot));
|
|
182
|
+
const command = shellCommandFromPayload(payload);
|
|
183
|
+
if (command) {
|
|
184
|
+
for (const resolved of collectOutsideRepoPaths(command, action.cwd, action.repoRoot)) {
|
|
185
|
+
paths.add(resolved);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return [...paths];
|
|
189
|
+
}
|
|
190
|
+
function applyToolSandboxPolicies(action, result, options) {
|
|
191
|
+
const outsideRepoPaths = outsideRepoPathsForToolAction(action);
|
|
192
|
+
const command = shellCommandFromPayload(action.payload ?? {});
|
|
193
|
+
let next = applySandboxFsScopeBoundary(outsideRepoPaths, result, options, {
|
|
194
|
+
redirect: command.includes('>'),
|
|
195
|
+
});
|
|
196
|
+
next = applyFsScopePeripheralPolicy(outsideRepoPaths, next, options);
|
|
197
|
+
return next;
|
|
198
|
+
}
|
|
123
199
|
export async function classifyGatedAction(action, config, extraOptions = {}) {
|
|
124
200
|
const options = { ...classifierOptionsFromConfig(config), ...extraOptions };
|
|
125
201
|
if (action.kind === 'shell') {
|
|
@@ -128,6 +204,7 @@ export async function classifyGatedAction(action, config, extraOptions = {}) {
|
|
|
128
204
|
throw new GateNormalizationError('Shell gated action requires a command.');
|
|
129
205
|
}
|
|
130
206
|
let result = await classifyShell(command, action.cwd, action.repoRoot, config, options);
|
|
207
|
+
result = applySandboxOutsideBoundary(command, action, result, options);
|
|
131
208
|
result = applyShellPeripheralPolicy(command, action, result, options);
|
|
132
209
|
if (!action.agentAssessment) {
|
|
133
210
|
return result;
|
|
@@ -146,7 +223,21 @@ export async function classifyGatedAction(action, config, extraOptions = {}) {
|
|
|
146
223
|
if (action.kind === 'subagent') {
|
|
147
224
|
return classifySubagent(action.payload ?? {}, action.repoRoot, options);
|
|
148
225
|
}
|
|
149
|
-
|
|
226
|
+
let result = await classifyToolUse(action.payload ?? {}, action.repoRoot, action.cwd, config, options);
|
|
227
|
+
result = applyToolSandboxPolicies(action, result, options);
|
|
228
|
+
if (!action.agentAssessment) {
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
const merged = mergeAgentAssessment(result.assessment, action.agentAssessment);
|
|
232
|
+
if (!merged.mismatch) {
|
|
233
|
+
return { ...result, assessment: merged.assessment };
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
...result,
|
|
237
|
+
verdict: 'deny_pending_approval',
|
|
238
|
+
reason: 'agent_assessment_mismatch',
|
|
239
|
+
assessment: merged.assessment,
|
|
240
|
+
};
|
|
150
241
|
}
|
|
151
242
|
export async function classifyGatedActionAsync(action, config, extraOptions = {}) {
|
|
152
243
|
return classifyGatedAction(action, config, extraOptions);
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { approvalCommandMatch, buildRetryInstruction, compactApprovals, createApprovalRecord, isExpired, mergeApprovalStates, nowIso, } from './approval.js';
|
|
1
|
+
export { APPROVAL_EXECUTION_LEASE_MS, approvalCommandMatch, buildRetryInstruction, compactApprovals, createApprovalRecord, isExecutionLeaseExpired, isExpired, mergeApprovalStates, nowIso, } from './approval.js';
|
|
2
2
|
export type { AuditMetricsReport } from './audit-metrics.js';
|
|
3
3
|
export { computeAuditMetrics, parseAuditNdjson } from './audit-metrics.js';
|
|
4
4
|
export { classifySubagent } from './classify-subagent.js';
|
|
@@ -16,4 +16,4 @@ export { findCommandSubstitutions, MAX_SUBSTITUTION_DEPTH } from './shell-substi
|
|
|
16
16
|
export type { TransactionalDiffEvaluation, TransactionalExecutionResult, } from './transactional/index.js';
|
|
17
17
|
export { isTransactionalEligible, runTransactionalExecution } from './transactional/index.js';
|
|
18
18
|
export type { ApprovalRecord, ApprovalStateFile, Assessment, ClassifierOptions, ClassifyResult, HookVerdict, Reversibility, ScrubOptions, UnknownLocalEffectPolicy, } from './types.js';
|
|
19
|
-
export { buildVerdictContext, classifyShell, verdict, verdictToClassifyResult, } from './
|
|
19
|
+
export { buildVerdictContext, classifyShell, verdict, verdictToClassifyResult, } from './verdict/index.js';
|