@guilz-dev/belay 0.10.0 → 0.10.1

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.
@@ -2155,7 +2155,7 @@ function normalizeConfig(config) {
2155
2155
  policy: {
2156
2156
  unknownLocalEffect: v4.policy?.unknownLocalEffect === "deny" ? "deny" : v4.policy?.unknownLocalEffect === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unknownLocalEffect,
2157
2157
  unparseableShell: v4.policy?.unparseableShell === "deny" ? "deny" : v4.policy?.unparseableShell === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unparseableShell,
2158
- codexUnmappedTool: v4.policy?.codexUnmappedTool === "allow" ? "allow" : "deny",
2158
+ codexUnmappedTool: "allow",
2159
2159
  fenceWarnThreshold: typeof v4.policy?.fenceWarnThreshold === "number" && v4.policy.fenceWarnThreshold > 0 && v4.policy.fenceWarnThreshold <= 1 ? v4.policy.fenceWarnThreshold : DEFAULT_FENCE_WARN_THRESHOLD,
2160
2160
  confidenceThresholds: {
2161
2161
  allow: typeof v4.policy?.confidenceThresholds?.allow === "number" ? v4.policy.confidenceThresholds.allow : DEFAULT_CONFIDENCE_THRESHOLDS.allow,
@@ -2457,7 +2457,7 @@ var init_config = __esm({
2457
2457
  DEFAULT_POLICY_V3 = {
2458
2458
  unknownLocalEffect: "allow_flagged",
2459
2459
  unparseableShell: "deny",
2460
- codexUnmappedTool: "deny",
2460
+ codexUnmappedTool: "allow",
2461
2461
  confidenceThresholds: { ...DEFAULT_CONFIDENCE_THRESHOLDS },
2462
2462
  modelAssist: { ...DEFAULT_MODEL_ASSIST },
2463
2463
  transactional: { ...DEFAULT_TRANSACTIONAL_V3 },
@@ -10866,6 +10866,40 @@ function evaluateFileMutationPolicy(analysis, config, auth) {
10866
10866
  );
10867
10867
  return { request, decision };
10868
10868
  }
10869
+ function buildFileReadCapabilityRequest(analysis) {
10870
+ return {
10871
+ version: CAPABILITY_REQUEST_VERSION,
10872
+ principal: {
10873
+ adapter: analysis.adapter,
10874
+ repoRoot: analysis.repoRoot,
10875
+ sessionHash: hashSession(`${analysis.repoRoot}:${analysis.cwd}`)
10876
+ },
10877
+ action: "fs.read",
10878
+ resource: { kind: "path", path: analysis.resolvedPath },
10879
+ context: {
10880
+ cwd: analysis.cwd,
10881
+ inputFingerprint: analysis.inputFingerprint,
10882
+ hookKind: analysis.hookKind,
10883
+ analysisBasis: [`tool:${analysis.toolKind}`, `location:${analysis.locationLabel}`, "read"]
10884
+ },
10885
+ evidence: {
10886
+ level: analysis.locationLabel === "repo_local" ? "certain" : "possible",
10887
+ signals: [...analysis.signals, "effect.fs_read"]
10888
+ }
10889
+ };
10890
+ }
10891
+ function evaluateFileReadPolicy(analysis, config, auth) {
10892
+ const request = buildFileReadCapabilityRequest(analysis);
10893
+ const enriched = enrichAuthWithMaterializedGrants(request, config, {
10894
+ ...auth,
10895
+ sensitivePaths: auth?.sensitivePaths ?? analysis.sensitivePaths
10896
+ });
10897
+ const decision = getDefaultPolicyEngine().evaluate(
10898
+ request,
10899
+ buildAuthorizationContext(config, analysis.trustedWorkspaceRoots, enriched)
10900
+ );
10901
+ return { request, decision };
10902
+ }
10869
10903
 
10870
10904
  // src/core/capability/reasons.ts
10871
10905
  var FS_SCOPE_REASONS = /* @__PURE__ */ new Set(["outside_repo_mutation", "outside_repo_redirect"]);
@@ -17087,17 +17121,41 @@ function verdictToClassifyResult(result, config, options = {}) {
17087
17121
 
17088
17122
  // src/core/classify-tool.ts
17089
17123
  var DEFAULT_SENSITIVE_PATHS = [".env", ".env.*", "**/credentials/**"];
17090
- var FILE_WRITE_TOOL_NAMES = /* @__PURE__ */ new Set(["write"]);
17091
- var FILE_EDIT_TOOL_NAMES = /* @__PURE__ */ new Set([
17092
- "edit",
17093
- "multiedit",
17094
- "multi_edit",
17095
- "patch",
17096
- "strreplace",
17097
- "str_replace"
17098
- ]);
17099
- var FILE_DELETE_TOOL_NAMES = /* @__PURE__ */ new Set(["delete"]);
17100
- var APPLY_PATCH_TOOL_NAMES = /* @__PURE__ */ new Set(["apply_patch", "applypatch"]);
17124
+ function toolInputRecord(payload) {
17125
+ const toolInput = payload.tool_input;
17126
+ if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) {
17127
+ return null;
17128
+ }
17129
+ return toolInput;
17130
+ }
17131
+ function hasMutationFields(input) {
17132
+ return typeof input.contents === "string" || typeof input.old_string === "string" || typeof input.new_string === "string" || typeof input.newContents === "string";
17133
+ }
17134
+ function inferToolPayloadEffect(payload, toolName) {
17135
+ if (extractShellCommand(payload)) {
17136
+ return "shell";
17137
+ }
17138
+ const input = toolInputRecord(payload);
17139
+ if (!input) {
17140
+ return "indeterminate";
17141
+ }
17142
+ if (hasMutationFields(input)) {
17143
+ return "file_mutation";
17144
+ }
17145
+ if (extractPatch(payload)) {
17146
+ return "apply_patch";
17147
+ }
17148
+ if (typeof input.pattern === "string" || typeof input.glob_pattern === "string") {
17149
+ return "search_read";
17150
+ }
17151
+ if (typeof input.file_path === "string") {
17152
+ return "file_read";
17153
+ }
17154
+ if (typeof input.path === "string" || typeof input.target_file === "string") {
17155
+ return normalizedToolName(toolName) === "delete" ? "file_delete" : "indeterminate";
17156
+ }
17157
+ return "indeterminate";
17158
+ }
17101
17159
  function policyAuth(options) {
17102
17160
  if (!options.grants && options.attestation === void 0 && options.egressProxyActive === void 0) {
17103
17161
  return void 0;
@@ -17277,6 +17335,314 @@ function classifyFileMutationWithPolicy(params) {
17277
17335
  boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
17278
17336
  };
17279
17337
  }
17338
+ function classifyFileReadWithPolicy(params) {
17339
+ const fingerprint = toolFingerprint(params.toolName, { path: params.filePath }, params.repoRoot);
17340
+ const { request, decision } = evaluateFileReadPolicy(
17341
+ {
17342
+ hookKind: "tool",
17343
+ toolKind: params.toolKind,
17344
+ filePath: params.filePath,
17345
+ resolvedPath: params.resolvedPath,
17346
+ repoRoot: params.repoRoot,
17347
+ cwd: params.cwd,
17348
+ inputFingerprint: fingerprint,
17349
+ signals: params.signals,
17350
+ locationLabel: params.locationLabel,
17351
+ trustedWorkspaceRoots: params.options.trustedWorkspaceRoots,
17352
+ sensitivePaths: params.options.sensitivePaths ?? params.config.classifier.sensitivePaths
17353
+ },
17354
+ params.config,
17355
+ policyAuth(params.options)
17356
+ );
17357
+ if (policyDecisionRequiresAsk(decision)) {
17358
+ return {
17359
+ verdict: "deny_pending_approval",
17360
+ reason: policyReasonToLegacyReason(decision),
17361
+ summary: params.filePath,
17362
+ fingerprint,
17363
+ assessment: {
17364
+ reversibility: "reversible",
17365
+ external: params.locationLabel === "outside_repo",
17366
+ blastRadius: params.locationLabel === "outside_repo" ? "outside the repository" : "sensitive repository file",
17367
+ confidence: 0.85,
17368
+ signals: [...params.signals, ...decision.signals]
17369
+ },
17370
+ capabilityRequests: [request],
17371
+ authorizationDecision: decision,
17372
+ boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
17373
+ };
17374
+ }
17375
+ return {
17376
+ verdict: "allow",
17377
+ reason: "effect.fs_read",
17378
+ summary: params.filePath,
17379
+ fingerprint,
17380
+ assessment: {
17381
+ reversibility: "reversible",
17382
+ external: false,
17383
+ blastRadius: "tool scope",
17384
+ confidence: 0.88,
17385
+ signals: [...params.signals, ...decision.signals]
17386
+ },
17387
+ capabilityRequests: [request],
17388
+ authorizationDecision: decision,
17389
+ boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
17390
+ };
17391
+ }
17392
+ function classifyFilePathMutation(params) {
17393
+ const {
17394
+ toolName,
17395
+ toolKind,
17396
+ filePath,
17397
+ repoRoot,
17398
+ cwd,
17399
+ config,
17400
+ options,
17401
+ sensitivePaths,
17402
+ protectedRoots,
17403
+ isDelete
17404
+ } = params;
17405
+ const signals = [];
17406
+ const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
17407
+ const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
17408
+ if (hitsProtectedRoot) {
17409
+ signals.push("control_plane_path");
17410
+ return classifyFileMutationWithPolicy({
17411
+ toolName,
17412
+ toolKind,
17413
+ filePath,
17414
+ resolvedPath,
17415
+ repoRoot,
17416
+ cwd,
17417
+ config,
17418
+ options,
17419
+ signals,
17420
+ isDelete,
17421
+ locationLabel: "control_plane"
17422
+ });
17423
+ }
17424
+ const workspaceMatch = resolveWorkspaceRootMatch(
17425
+ repoRoot,
17426
+ options.trustedWorkspaceRoots,
17427
+ resolvedPath
17428
+ );
17429
+ if (workspaceMatch === null) {
17430
+ signals.push("outside_repo_path");
17431
+ return classifyFileMutationWithPolicy({
17432
+ toolName,
17433
+ toolKind,
17434
+ filePath,
17435
+ resolvedPath,
17436
+ repoRoot,
17437
+ cwd,
17438
+ config,
17439
+ options,
17440
+ signals,
17441
+ isDelete,
17442
+ locationLabel: "outside_repo"
17443
+ });
17444
+ }
17445
+ if (workspaceMatch.kind === "trusted") {
17446
+ signals.push("trusted_workspace_root");
17447
+ }
17448
+ const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
17449
+ const workspacePrescan = mutationPrescanRequiresAsk({
17450
+ targets: [filePath],
17451
+ cwd,
17452
+ repoRoot,
17453
+ trustedCwd,
17454
+ trustedWorkspaceRoots: options.trustedWorkspaceRoots,
17455
+ sensitivePaths
17456
+ });
17457
+ if (workspacePrescan) {
17458
+ return classifyFileMutationWithPolicy({
17459
+ toolName,
17460
+ toolKind,
17461
+ filePath,
17462
+ resolvedPath,
17463
+ repoRoot,
17464
+ cwd,
17465
+ config,
17466
+ options,
17467
+ signals,
17468
+ isDelete,
17469
+ locationLabel: "sensitive_path"
17470
+ });
17471
+ }
17472
+ const relativePath = workspaceMatch.relativePath;
17473
+ if (matchesSensitivePath(relativePath, sensitivePaths)) {
17474
+ signals.push("sensitive_path");
17475
+ return classifyFileMutationWithPolicy({
17476
+ toolName,
17477
+ toolKind,
17478
+ filePath,
17479
+ resolvedPath,
17480
+ repoRoot,
17481
+ cwd,
17482
+ config,
17483
+ options,
17484
+ signals,
17485
+ isDelete,
17486
+ locationLabel: "sensitive_path"
17487
+ });
17488
+ }
17489
+ if (isDelete) {
17490
+ if (isGitPath(resolvedPath, repoRoot)) {
17491
+ signals.push("protected_artifact");
17492
+ return classifyFileMutationWithPolicy({
17493
+ toolName,
17494
+ toolKind,
17495
+ filePath,
17496
+ resolvedPath,
17497
+ repoRoot,
17498
+ cwd,
17499
+ config,
17500
+ options,
17501
+ signals,
17502
+ isDelete: true,
17503
+ locationLabel: "sensitive_path"
17504
+ });
17505
+ }
17506
+ signals.push("file_delete");
17507
+ return classifyFileMutationWithPolicy({
17508
+ toolName,
17509
+ toolKind,
17510
+ filePath,
17511
+ resolvedPath,
17512
+ repoRoot,
17513
+ cwd,
17514
+ config,
17515
+ options,
17516
+ signals,
17517
+ isDelete: true,
17518
+ locationLabel: "repo_local"
17519
+ });
17520
+ }
17521
+ signals.push("file_mutation");
17522
+ return classifyFileMutationWithPolicy({
17523
+ toolName,
17524
+ toolKind,
17525
+ filePath,
17526
+ resolvedPath,
17527
+ repoRoot,
17528
+ cwd,
17529
+ config,
17530
+ options,
17531
+ signals,
17532
+ isDelete: false,
17533
+ locationLabel: "repo_local"
17534
+ });
17535
+ }
17536
+ function classifyFilePathRead(params) {
17537
+ const {
17538
+ toolName,
17539
+ toolKind,
17540
+ filePath,
17541
+ repoRoot,
17542
+ cwd,
17543
+ config,
17544
+ options,
17545
+ sensitivePaths,
17546
+ protectedRoots
17547
+ } = params;
17548
+ const signals = ["effect.fs_read"];
17549
+ const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
17550
+ if (protectedRoots.some((root) => pathWithinRoot(root, resolvedPath))) {
17551
+ signals.push("control_plane_path");
17552
+ return classifyFileReadWithPolicy({
17553
+ toolName,
17554
+ toolKind,
17555
+ filePath,
17556
+ resolvedPath,
17557
+ repoRoot,
17558
+ cwd,
17559
+ config,
17560
+ options,
17561
+ signals,
17562
+ locationLabel: "control_plane"
17563
+ });
17564
+ }
17565
+ const workspaceMatch = resolveWorkspaceRootMatch(
17566
+ repoRoot,
17567
+ options.trustedWorkspaceRoots,
17568
+ resolvedPath
17569
+ );
17570
+ if (workspaceMatch === null) {
17571
+ signals.push("outside_repo_path");
17572
+ return classifyFileReadWithPolicy({
17573
+ toolName,
17574
+ toolKind,
17575
+ filePath,
17576
+ resolvedPath,
17577
+ repoRoot,
17578
+ cwd,
17579
+ config,
17580
+ options,
17581
+ signals,
17582
+ locationLabel: "outside_repo"
17583
+ });
17584
+ }
17585
+ const relativePath = workspaceMatch.relativePath;
17586
+ if (matchesSensitivePath(relativePath, sensitivePaths)) {
17587
+ signals.push("sensitive_path");
17588
+ return classifyFileReadWithPolicy({
17589
+ toolName,
17590
+ toolKind,
17591
+ filePath,
17592
+ resolvedPath,
17593
+ repoRoot,
17594
+ cwd,
17595
+ config,
17596
+ options,
17597
+ signals,
17598
+ locationLabel: "sensitive_path"
17599
+ });
17600
+ }
17601
+ return classifyFileReadWithPolicy({
17602
+ toolName,
17603
+ toolKind,
17604
+ filePath,
17605
+ resolvedPath,
17606
+ repoRoot,
17607
+ cwd,
17608
+ config,
17609
+ options,
17610
+ signals,
17611
+ locationLabel: "repo_local"
17612
+ });
17613
+ }
17614
+ function indeterminateToolResult(toolName, payload, repoRoot, options) {
17615
+ const summary = canonicalStringify(scrubPayload(payload.tool_input ?? {}, options));
17616
+ const fingerprint = toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot);
17617
+ if (options.unknownLocalEffect === "deny") {
17618
+ return {
17619
+ verdict: "deny_pending_approval",
17620
+ reason: "indeterminate_tool_effect",
17621
+ summary,
17622
+ fingerprint,
17623
+ assessment: {
17624
+ reversibility: "irreversible",
17625
+ external: false,
17626
+ blastRadius: "unknown tool action",
17627
+ confidence: 0.5,
17628
+ signals: ["indeterminate_tool_effect"]
17629
+ }
17630
+ };
17631
+ }
17632
+ return {
17633
+ verdict: "allow_flagged",
17634
+ reason: "indeterminate_tool_effect",
17635
+ summary,
17636
+ fingerprint,
17637
+ assessment: {
17638
+ reversibility: "recoverable_with_cost",
17639
+ external: false,
17640
+ blastRadius: "tool scope",
17641
+ confidence: 0.5,
17642
+ signals: ["indeterminate_tool_effect"]
17643
+ }
17644
+ };
17645
+ }
17280
17646
  async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17281
17647
  const toolName = String(payload.tool_name ?? "");
17282
17648
  const toolKind = normalizedToolName(toolName);
@@ -17323,9 +17689,10 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17323
17689
  summary: command
17324
17690
  };
17325
17691
  }
17326
- if (FILE_WRITE_TOOL_NAMES.has(toolKind) || FILE_EDIT_TOOL_NAMES.has(toolKind) || FILE_DELETE_TOOL_NAMES.has(toolKind)) {
17327
- const filePath = extractFilePath(payload);
17328
- if (!filePath) {
17692
+ const effect = inferToolPayloadEffect(payload, toolName);
17693
+ if (effect === "file_mutation" || effect === "file_delete") {
17694
+ const filePath2 = extractFilePath(payload);
17695
+ if (!filePath2) {
17329
17696
  if (options.unknownLocalEffect === "deny") {
17330
17697
  return {
17331
17698
  verdict: "deny_pending_approval",
@@ -17355,138 +17722,20 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17355
17722
  }
17356
17723
  };
17357
17724
  }
17358
- const signals = [];
17359
- const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
17360
- const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
17361
- if (hitsProtectedRoot) {
17362
- signals.push("control_plane_path");
17363
- return classifyFileMutationWithPolicy({
17364
- toolName,
17365
- toolKind,
17366
- filePath,
17367
- resolvedPath,
17368
- repoRoot,
17369
- cwd,
17370
- config,
17371
- options,
17372
- signals,
17373
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17374
- locationLabel: "control_plane"
17375
- });
17376
- }
17377
- const workspaceMatch = resolveWorkspaceRootMatch(
17378
- repoRoot,
17379
- options.trustedWorkspaceRoots,
17380
- resolvedPath
17381
- );
17382
- if (workspaceMatch === null) {
17383
- signals.push("outside_repo_path");
17384
- return classifyFileMutationWithPolicy({
17385
- toolName,
17386
- toolKind,
17387
- filePath,
17388
- resolvedPath,
17389
- repoRoot,
17390
- cwd,
17391
- config,
17392
- options,
17393
- signals,
17394
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17395
- locationLabel: "outside_repo"
17396
- });
17397
- }
17398
- if (workspaceMatch.kind === "trusted") {
17399
- signals.push("trusted_workspace_root");
17400
- }
17401
- const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
17402
- const workspacePrescan = mutationPrescanRequiresAsk({
17403
- targets: [filePath],
17404
- cwd,
17405
- repoRoot,
17406
- trustedCwd,
17407
- trustedWorkspaceRoots: options.trustedWorkspaceRoots,
17408
- sensitivePaths
17409
- });
17410
- if (workspacePrescan) {
17411
- return classifyFileMutationWithPolicy({
17412
- toolName,
17413
- toolKind,
17414
- filePath,
17415
- resolvedPath,
17416
- repoRoot,
17417
- cwd,
17418
- config,
17419
- options,
17420
- signals,
17421
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17422
- locationLabel: "sensitive_path"
17423
- });
17424
- }
17425
- const relativePath = workspaceMatch.relativePath;
17426
- if (matchesSensitivePath(relativePath, sensitivePaths)) {
17427
- signals.push("sensitive_path");
17428
- return classifyFileMutationWithPolicy({
17429
- toolName,
17430
- toolKind,
17431
- filePath,
17432
- resolvedPath,
17433
- repoRoot,
17434
- cwd,
17435
- config,
17436
- options,
17437
- signals,
17438
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17439
- locationLabel: "sensitive_path"
17440
- });
17441
- }
17442
- if (FILE_DELETE_TOOL_NAMES.has(toolKind)) {
17443
- if (isGitPath(resolvedPath, repoRoot)) {
17444
- signals.push("protected_artifact");
17445
- return classifyFileMutationWithPolicy({
17446
- toolName,
17447
- toolKind,
17448
- filePath,
17449
- resolvedPath,
17450
- repoRoot,
17451
- cwd,
17452
- config,
17453
- options,
17454
- signals,
17455
- isDelete: true,
17456
- locationLabel: "sensitive_path"
17457
- });
17458
- }
17459
- signals.push("file_delete");
17460
- return classifyFileMutationWithPolicy({
17461
- toolName,
17462
- toolKind,
17463
- filePath,
17464
- resolvedPath,
17465
- repoRoot,
17466
- cwd,
17467
- config,
17468
- options,
17469
- signals,
17470
- isDelete: true,
17471
- locationLabel: "repo_local"
17472
- });
17473
- }
17474
- signals.push("file_mutation");
17475
- return classifyFileMutationWithPolicy({
17725
+ return classifyFilePathMutation({
17476
17726
  toolName,
17477
17727
  toolKind,
17478
- filePath,
17479
- resolvedPath,
17728
+ filePath: filePath2,
17480
17729
  repoRoot,
17481
17730
  cwd,
17482
17731
  config,
17483
17732
  options,
17484
- signals,
17485
- isDelete: false,
17486
- locationLabel: "repo_local"
17733
+ sensitivePaths,
17734
+ protectedRoots,
17735
+ isDelete: effect === "file_delete"
17487
17736
  });
17488
17737
  }
17489
- if (APPLY_PATCH_TOOL_NAMES.has(toolKind)) {
17738
+ if (effect === "apply_patch") {
17490
17739
  const patch = extractPatch(payload);
17491
17740
  const targets = patch ? applyPatchTargets2(patch) : [];
17492
17741
  if (targets.length === 0) {
@@ -17524,7 +17773,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17524
17773
  const result = await classifyToolUse(
17525
17774
  {
17526
17775
  tool_name: target.delete ? "Delete" : "Write",
17527
- tool_input: { path: target.path }
17776
+ tool_input: target.delete ? { path: target.path } : { path: target.path, contents: " " }
17528
17777
  },
17529
17778
  repoRoot,
17530
17779
  cwd,
@@ -17550,19 +17799,54 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17550
17799
  }
17551
17800
  };
17552
17801
  }
17553
- return {
17554
- verdict: "allow",
17555
- reason: "unclassified_tool",
17556
- summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17557
- fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17558
- assessment: {
17559
- reversibility: "reversible",
17560
- external: false,
17561
- blastRadius: "tool scope",
17562
- confidence: 0.5,
17563
- signals: ["unclassified_tool"]
17802
+ if (effect === "file_read") {
17803
+ const filePath2 = extractFilePath(payload);
17804
+ if (!filePath2) {
17805
+ return indeterminateToolResult(toolName, payload, repoRoot, options);
17564
17806
  }
17565
- };
17807
+ return classifyFilePathRead({
17808
+ toolName,
17809
+ toolKind,
17810
+ filePath: filePath2,
17811
+ repoRoot,
17812
+ cwd,
17813
+ config,
17814
+ options,
17815
+ sensitivePaths,
17816
+ protectedRoots
17817
+ });
17818
+ }
17819
+ if (effect === "search_read") {
17820
+ return {
17821
+ verdict: "allow",
17822
+ reason: "effect.search_read",
17823
+ summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17824
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17825
+ assessment: {
17826
+ reversibility: "reversible",
17827
+ external: false,
17828
+ blastRadius: "tool scope",
17829
+ confidence: 0.88,
17830
+ signals: ["effect.search_read"]
17831
+ }
17832
+ };
17833
+ }
17834
+ const filePath = extractFilePath(payload);
17835
+ if (filePath) {
17836
+ return classifyFilePathMutation({
17837
+ toolName,
17838
+ toolKind,
17839
+ filePath,
17840
+ repoRoot,
17841
+ cwd,
17842
+ config,
17843
+ options,
17844
+ sensitivePaths,
17845
+ protectedRoots,
17846
+ isDelete: toolKind === "delete"
17847
+ });
17848
+ }
17849
+ return indeterminateToolResult(toolName, payload, repoRoot, options);
17566
17850
  }
17567
17851
 
17568
17852
  // src/core/gate-engine.ts
@@ -17897,15 +18181,8 @@ function gateEnabledForAction(config, action) {
17897
18181
  if (action.kind === "subagent") {
17898
18182
  return config.gates.subagent;
17899
18183
  }
17900
- const toolName = action.toolName ?? String(action.payload?.tool_name ?? "");
17901
- if (toolName === "Shell") {
17902
- return config.gates.toolShell;
17903
- }
17904
- if (toolName === "Write" || toolName === "StrReplace" || toolName === "Delete") {
17905
- return config.gates.fileMutation;
17906
- }
17907
- if (toolName === "Task") {
17908
- return config.gates.subagent;
18184
+ if (action.kind === "tool") {
18185
+ return config.gates.fileMutation || config.gates.toolShell;
17909
18186
  }
17910
18187
  return true;
17911
18188
  }
@@ -17942,7 +18219,7 @@ function hashDecisionConfig(config) {
17942
18219
  init_fingerprint2();
17943
18220
 
17944
18221
  // src/version.ts
17945
- var PACKAGE_VERSION = "0.10.0";
18222
+ var PACKAGE_VERSION = "0.10.1";
17946
18223
 
17947
18224
  // src/runtime-provenance.ts
17948
18225
  function resolveRuntimeArtifactHash(artifactHash) {
@@ -21243,21 +21520,7 @@ async function evaluateGatedAction(ctx, deps, params) {
21243
21520
  ...authorization,
21244
21521
  egressProxyActive
21245
21522
  };
21246
- let predicted = await classifyGatedActionAsync(action, ctx.config, enrichedClassifierOptions);
21247
- if (action.kind === "tool" && predicted.reason === "unclassified_tool" && (ctx.config.policy.codexUnmappedTool ?? "deny") === "deny") {
21248
- predicted = {
21249
- ...predicted,
21250
- verdict: "deny_pending_approval",
21251
- reason: "unmapped_tool",
21252
- assessment: {
21253
- reversibility: "irreversible",
21254
- external: false,
21255
- blastRadius: "unknown tool action",
21256
- confidence: 0.5,
21257
- signals: ["unmapped_tool"]
21258
- }
21259
- };
21260
- }
21523
+ const predicted = await classifyGatedActionAsync(action, ctx.config, enrichedClassifierOptions);
21261
21524
  if (action.kind === "shell" && action.command && isContainedUnknownExecutionEligible(ctx.config, action, predicted)) {
21262
21525
  const mediated = await mediateContainedUnknownExecution({
21263
21526
  ctx,
@@ -22121,25 +22384,12 @@ async function runToolGateHook(_eventName) {
22121
22384
  return;
22122
22385
  }
22123
22386
  if (!mappedKind) {
22124
- const verdict3 = claudeFallbackToolVerdict({
22125
- toolName,
22126
- payload,
22127
- repoRoot: ctx.repoRoot,
22128
- reason: "unmapped_tool",
22129
- mode: ctx.config.mode,
22130
- userMessage: "belay does not recognize this tool action. Run belay doctor, then retry.",
22131
- agentMessage: "Belay denied this action because the tool could not be normalized."
22132
- });
22133
- await deps.appendAudit(ctx, {
22134
- event: "preToolUse",
22387
+ const verdict3 = await evaluateGatedAction(ctx, deps, {
22135
22388
  kind: "tool",
22136
- verdict: "deny_pending_approval",
22137
- reason: "unmapped_tool",
22138
- mode: ctx.config.mode,
22139
- wouldBlock: true,
22140
- permission: "deny",
22141
- summary: toolName,
22142
- ...effectPlanAuditFields(verdict3.effectPlan)
22389
+ cwd,
22390
+ payload,
22391
+ toolName,
22392
+ sourceEvent: "PreToolUse"
22143
22393
  });
22144
22394
  jsonResponse(gateVerdictToClaudePreToolUseResponse(verdict3));
22145
22395
  return;