@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.
@@ -1504,7 +1504,7 @@ function normalizeConfig(config) {
1504
1504
  policy: {
1505
1505
  unknownLocalEffect: v4.policy?.unknownLocalEffect === "deny" ? "deny" : v4.policy?.unknownLocalEffect === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unknownLocalEffect,
1506
1506
  unparseableShell: v4.policy?.unparseableShell === "deny" ? "deny" : v4.policy?.unparseableShell === "allow_flagged" ? "allow_flagged" : DEFAULT_POLICY_V3.unparseableShell,
1507
- codexUnmappedTool: v4.policy?.codexUnmappedTool === "allow" ? "allow" : "deny",
1507
+ codexUnmappedTool: "allow",
1508
1508
  fenceWarnThreshold: typeof v4.policy?.fenceWarnThreshold === "number" && v4.policy.fenceWarnThreshold > 0 && v4.policy.fenceWarnThreshold <= 1 ? v4.policy.fenceWarnThreshold : DEFAULT_FENCE_WARN_THRESHOLD,
1509
1509
  confidenceThresholds: {
1510
1510
  allow: typeof v4.policy?.confidenceThresholds?.allow === "number" ? v4.policy.confidenceThresholds.allow : DEFAULT_CONFIDENCE_THRESHOLDS.allow,
@@ -1806,7 +1806,7 @@ var init_config = __esm({
1806
1806
  DEFAULT_POLICY_V3 = {
1807
1807
  unknownLocalEffect: "allow_flagged",
1808
1808
  unparseableShell: "deny",
1809
- codexUnmappedTool: "deny",
1809
+ codexUnmappedTool: "allow",
1810
1810
  confidenceThresholds: { ...DEFAULT_CONFIDENCE_THRESHOLDS },
1811
1811
  modelAssist: { ...DEFAULT_MODEL_ASSIST },
1812
1812
  transactional: { ...DEFAULT_TRANSACTIONAL_V3 },
@@ -10813,6 +10813,40 @@ function evaluateFileMutationPolicy(analysis, config, auth) {
10813
10813
  );
10814
10814
  return { request, decision };
10815
10815
  }
10816
+ function buildFileReadCapabilityRequest(analysis) {
10817
+ return {
10818
+ version: CAPABILITY_REQUEST_VERSION,
10819
+ principal: {
10820
+ adapter: analysis.adapter,
10821
+ repoRoot: analysis.repoRoot,
10822
+ sessionHash: hashSession(`${analysis.repoRoot}:${analysis.cwd}`)
10823
+ },
10824
+ action: "fs.read",
10825
+ resource: { kind: "path", path: analysis.resolvedPath },
10826
+ context: {
10827
+ cwd: analysis.cwd,
10828
+ inputFingerprint: analysis.inputFingerprint,
10829
+ hookKind: analysis.hookKind,
10830
+ analysisBasis: [`tool:${analysis.toolKind}`, `location:${analysis.locationLabel}`, "read"]
10831
+ },
10832
+ evidence: {
10833
+ level: analysis.locationLabel === "repo_local" ? "certain" : "possible",
10834
+ signals: [...analysis.signals, "effect.fs_read"]
10835
+ }
10836
+ };
10837
+ }
10838
+ function evaluateFileReadPolicy(analysis, config, auth) {
10839
+ const request = buildFileReadCapabilityRequest(analysis);
10840
+ const enriched = enrichAuthWithMaterializedGrants(request, config, {
10841
+ ...auth,
10842
+ sensitivePaths: auth?.sensitivePaths ?? analysis.sensitivePaths
10843
+ });
10844
+ const decision = getDefaultPolicyEngine().evaluate(
10845
+ request,
10846
+ buildAuthorizationContext(config, analysis.trustedWorkspaceRoots, enriched)
10847
+ );
10848
+ return { request, decision };
10849
+ }
10816
10850
 
10817
10851
  // src/core/capability/reasons.ts
10818
10852
  var FS_SCOPE_REASONS = /* @__PURE__ */ new Set(["outside_repo_mutation", "outside_repo_redirect"]);
@@ -17083,17 +17117,41 @@ function verdictToClassifyResult(result, config, options = {}) {
17083
17117
 
17084
17118
  // src/core/classify-tool.ts
17085
17119
  var DEFAULT_SENSITIVE_PATHS = [".env", ".env.*", "**/credentials/**"];
17086
- var FILE_WRITE_TOOL_NAMES = /* @__PURE__ */ new Set(["write"]);
17087
- var FILE_EDIT_TOOL_NAMES = /* @__PURE__ */ new Set([
17088
- "edit",
17089
- "multiedit",
17090
- "multi_edit",
17091
- "patch",
17092
- "strreplace",
17093
- "str_replace"
17094
- ]);
17095
- var FILE_DELETE_TOOL_NAMES = /* @__PURE__ */ new Set(["delete"]);
17096
- var APPLY_PATCH_TOOL_NAMES = /* @__PURE__ */ new Set(["apply_patch", "applypatch"]);
17120
+ function toolInputRecord(payload) {
17121
+ const toolInput = payload.tool_input;
17122
+ if (!toolInput || typeof toolInput !== "object" || Array.isArray(toolInput)) {
17123
+ return null;
17124
+ }
17125
+ return toolInput;
17126
+ }
17127
+ function hasMutationFields(input) {
17128
+ return typeof input.contents === "string" || typeof input.old_string === "string" || typeof input.new_string === "string" || typeof input.newContents === "string";
17129
+ }
17130
+ function inferToolPayloadEffect(payload, toolName) {
17131
+ if (extractShellCommand(payload)) {
17132
+ return "shell";
17133
+ }
17134
+ const input = toolInputRecord(payload);
17135
+ if (!input) {
17136
+ return "indeterminate";
17137
+ }
17138
+ if (hasMutationFields(input)) {
17139
+ return "file_mutation";
17140
+ }
17141
+ if (extractPatch(payload)) {
17142
+ return "apply_patch";
17143
+ }
17144
+ if (typeof input.pattern === "string" || typeof input.glob_pattern === "string") {
17145
+ return "search_read";
17146
+ }
17147
+ if (typeof input.file_path === "string") {
17148
+ return "file_read";
17149
+ }
17150
+ if (typeof input.path === "string" || typeof input.target_file === "string") {
17151
+ return normalizedToolName(toolName) === "delete" ? "file_delete" : "indeterminate";
17152
+ }
17153
+ return "indeterminate";
17154
+ }
17097
17155
  function policyAuth(options) {
17098
17156
  if (!options.grants && options.attestation === void 0 && options.egressProxyActive === void 0) {
17099
17157
  return void 0;
@@ -17273,6 +17331,314 @@ function classifyFileMutationWithPolicy(params) {
17273
17331
  boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
17274
17332
  };
17275
17333
  }
17334
+ function classifyFileReadWithPolicy(params) {
17335
+ const fingerprint = toolFingerprint(params.toolName, { path: params.filePath }, params.repoRoot);
17336
+ const { request, decision } = evaluateFileReadPolicy(
17337
+ {
17338
+ hookKind: "tool",
17339
+ toolKind: params.toolKind,
17340
+ filePath: params.filePath,
17341
+ resolvedPath: params.resolvedPath,
17342
+ repoRoot: params.repoRoot,
17343
+ cwd: params.cwd,
17344
+ inputFingerprint: fingerprint,
17345
+ signals: params.signals,
17346
+ locationLabel: params.locationLabel,
17347
+ trustedWorkspaceRoots: params.options.trustedWorkspaceRoots,
17348
+ sensitivePaths: params.options.sensitivePaths ?? params.config.classifier.sensitivePaths
17349
+ },
17350
+ params.config,
17351
+ policyAuth(params.options)
17352
+ );
17353
+ if (policyDecisionRequiresAsk(decision)) {
17354
+ return {
17355
+ verdict: "deny_pending_approval",
17356
+ reason: policyReasonToLegacyReason(decision),
17357
+ summary: params.filePath,
17358
+ fingerprint,
17359
+ assessment: {
17360
+ reversibility: "reversible",
17361
+ external: params.locationLabel === "outside_repo",
17362
+ blastRadius: params.locationLabel === "outside_repo" ? "outside the repository" : "sensitive repository file",
17363
+ confidence: 0.85,
17364
+ signals: [...params.signals, ...decision.signals]
17365
+ },
17366
+ capabilityRequests: [request],
17367
+ authorizationDecision: decision,
17368
+ boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
17369
+ };
17370
+ }
17371
+ return {
17372
+ verdict: "allow",
17373
+ reason: "effect.fs_read",
17374
+ summary: params.filePath,
17375
+ fingerprint,
17376
+ assessment: {
17377
+ reversibility: "reversible",
17378
+ external: false,
17379
+ blastRadius: "tool scope",
17380
+ confidence: 0.88,
17381
+ signals: [...params.signals, ...decision.signals]
17382
+ },
17383
+ capabilityRequests: [request],
17384
+ authorizationDecision: decision,
17385
+ boundaryProfile: params.options.boundaryProfile ?? BOUNDARY_PROFILE_L3_L4_ONLY
17386
+ };
17387
+ }
17388
+ function classifyFilePathMutation(params) {
17389
+ const {
17390
+ toolName,
17391
+ toolKind,
17392
+ filePath,
17393
+ repoRoot,
17394
+ cwd,
17395
+ config,
17396
+ options,
17397
+ sensitivePaths,
17398
+ protectedRoots,
17399
+ isDelete
17400
+ } = params;
17401
+ const signals = [];
17402
+ const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
17403
+ const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
17404
+ if (hitsProtectedRoot) {
17405
+ signals.push("control_plane_path");
17406
+ return classifyFileMutationWithPolicy({
17407
+ toolName,
17408
+ toolKind,
17409
+ filePath,
17410
+ resolvedPath,
17411
+ repoRoot,
17412
+ cwd,
17413
+ config,
17414
+ options,
17415
+ signals,
17416
+ isDelete,
17417
+ locationLabel: "control_plane"
17418
+ });
17419
+ }
17420
+ const workspaceMatch = resolveWorkspaceRootMatch(
17421
+ repoRoot,
17422
+ options.trustedWorkspaceRoots,
17423
+ resolvedPath
17424
+ );
17425
+ if (workspaceMatch === null) {
17426
+ signals.push("outside_repo_path");
17427
+ return classifyFileMutationWithPolicy({
17428
+ toolName,
17429
+ toolKind,
17430
+ filePath,
17431
+ resolvedPath,
17432
+ repoRoot,
17433
+ cwd,
17434
+ config,
17435
+ options,
17436
+ signals,
17437
+ isDelete,
17438
+ locationLabel: "outside_repo"
17439
+ });
17440
+ }
17441
+ if (workspaceMatch.kind === "trusted") {
17442
+ signals.push("trusted_workspace_root");
17443
+ }
17444
+ const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
17445
+ const workspacePrescan = mutationPrescanRequiresAsk({
17446
+ targets: [filePath],
17447
+ cwd,
17448
+ repoRoot,
17449
+ trustedCwd,
17450
+ trustedWorkspaceRoots: options.trustedWorkspaceRoots,
17451
+ sensitivePaths
17452
+ });
17453
+ if (workspacePrescan) {
17454
+ return classifyFileMutationWithPolicy({
17455
+ toolName,
17456
+ toolKind,
17457
+ filePath,
17458
+ resolvedPath,
17459
+ repoRoot,
17460
+ cwd,
17461
+ config,
17462
+ options,
17463
+ signals,
17464
+ isDelete,
17465
+ locationLabel: "sensitive_path"
17466
+ });
17467
+ }
17468
+ const relativePath = workspaceMatch.relativePath;
17469
+ if (matchesSensitivePath(relativePath, sensitivePaths)) {
17470
+ signals.push("sensitive_path");
17471
+ return classifyFileMutationWithPolicy({
17472
+ toolName,
17473
+ toolKind,
17474
+ filePath,
17475
+ resolvedPath,
17476
+ repoRoot,
17477
+ cwd,
17478
+ config,
17479
+ options,
17480
+ signals,
17481
+ isDelete,
17482
+ locationLabel: "sensitive_path"
17483
+ });
17484
+ }
17485
+ if (isDelete) {
17486
+ if (isGitPath(resolvedPath, repoRoot)) {
17487
+ signals.push("protected_artifact");
17488
+ return classifyFileMutationWithPolicy({
17489
+ toolName,
17490
+ toolKind,
17491
+ filePath,
17492
+ resolvedPath,
17493
+ repoRoot,
17494
+ cwd,
17495
+ config,
17496
+ options,
17497
+ signals,
17498
+ isDelete: true,
17499
+ locationLabel: "sensitive_path"
17500
+ });
17501
+ }
17502
+ signals.push("file_delete");
17503
+ return classifyFileMutationWithPolicy({
17504
+ toolName,
17505
+ toolKind,
17506
+ filePath,
17507
+ resolvedPath,
17508
+ repoRoot,
17509
+ cwd,
17510
+ config,
17511
+ options,
17512
+ signals,
17513
+ isDelete: true,
17514
+ locationLabel: "repo_local"
17515
+ });
17516
+ }
17517
+ signals.push("file_mutation");
17518
+ return classifyFileMutationWithPolicy({
17519
+ toolName,
17520
+ toolKind,
17521
+ filePath,
17522
+ resolvedPath,
17523
+ repoRoot,
17524
+ cwd,
17525
+ config,
17526
+ options,
17527
+ signals,
17528
+ isDelete: false,
17529
+ locationLabel: "repo_local"
17530
+ });
17531
+ }
17532
+ function classifyFilePathRead(params) {
17533
+ const {
17534
+ toolName,
17535
+ toolKind,
17536
+ filePath,
17537
+ repoRoot,
17538
+ cwd,
17539
+ config,
17540
+ options,
17541
+ sensitivePaths,
17542
+ protectedRoots
17543
+ } = params;
17544
+ const signals = ["effect.fs_read"];
17545
+ const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
17546
+ if (protectedRoots.some((root) => pathWithinRoot(root, resolvedPath))) {
17547
+ signals.push("control_plane_path");
17548
+ return classifyFileReadWithPolicy({
17549
+ toolName,
17550
+ toolKind,
17551
+ filePath,
17552
+ resolvedPath,
17553
+ repoRoot,
17554
+ cwd,
17555
+ config,
17556
+ options,
17557
+ signals,
17558
+ locationLabel: "control_plane"
17559
+ });
17560
+ }
17561
+ const workspaceMatch = resolveWorkspaceRootMatch(
17562
+ repoRoot,
17563
+ options.trustedWorkspaceRoots,
17564
+ resolvedPath
17565
+ );
17566
+ if (workspaceMatch === null) {
17567
+ signals.push("outside_repo_path");
17568
+ return classifyFileReadWithPolicy({
17569
+ toolName,
17570
+ toolKind,
17571
+ filePath,
17572
+ resolvedPath,
17573
+ repoRoot,
17574
+ cwd,
17575
+ config,
17576
+ options,
17577
+ signals,
17578
+ locationLabel: "outside_repo"
17579
+ });
17580
+ }
17581
+ const relativePath = workspaceMatch.relativePath;
17582
+ if (matchesSensitivePath(relativePath, sensitivePaths)) {
17583
+ signals.push("sensitive_path");
17584
+ return classifyFileReadWithPolicy({
17585
+ toolName,
17586
+ toolKind,
17587
+ filePath,
17588
+ resolvedPath,
17589
+ repoRoot,
17590
+ cwd,
17591
+ config,
17592
+ options,
17593
+ signals,
17594
+ locationLabel: "sensitive_path"
17595
+ });
17596
+ }
17597
+ return classifyFileReadWithPolicy({
17598
+ toolName,
17599
+ toolKind,
17600
+ filePath,
17601
+ resolvedPath,
17602
+ repoRoot,
17603
+ cwd,
17604
+ config,
17605
+ options,
17606
+ signals,
17607
+ locationLabel: "repo_local"
17608
+ });
17609
+ }
17610
+ function indeterminateToolResult(toolName, payload, repoRoot, options) {
17611
+ const summary = canonicalStringify(scrubPayload(payload.tool_input ?? {}, options));
17612
+ const fingerprint = toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot);
17613
+ if (options.unknownLocalEffect === "deny") {
17614
+ return {
17615
+ verdict: "deny_pending_approval",
17616
+ reason: "indeterminate_tool_effect",
17617
+ summary,
17618
+ fingerprint,
17619
+ assessment: {
17620
+ reversibility: "irreversible",
17621
+ external: false,
17622
+ blastRadius: "unknown tool action",
17623
+ confidence: 0.5,
17624
+ signals: ["indeterminate_tool_effect"]
17625
+ }
17626
+ };
17627
+ }
17628
+ return {
17629
+ verdict: "allow_flagged",
17630
+ reason: "indeterminate_tool_effect",
17631
+ summary,
17632
+ fingerprint,
17633
+ assessment: {
17634
+ reversibility: "recoverable_with_cost",
17635
+ external: false,
17636
+ blastRadius: "tool scope",
17637
+ confidence: 0.5,
17638
+ signals: ["indeterminate_tool_effect"]
17639
+ }
17640
+ };
17641
+ }
17276
17642
  async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17277
17643
  const toolName = String(payload.tool_name ?? "");
17278
17644
  const toolKind = normalizedToolName(toolName);
@@ -17319,9 +17685,10 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17319
17685
  summary: command
17320
17686
  };
17321
17687
  }
17322
- if (FILE_WRITE_TOOL_NAMES.has(toolKind) || FILE_EDIT_TOOL_NAMES.has(toolKind) || FILE_DELETE_TOOL_NAMES.has(toolKind)) {
17323
- const filePath = extractFilePath(payload);
17324
- if (!filePath) {
17688
+ const effect = inferToolPayloadEffect(payload, toolName);
17689
+ if (effect === "file_mutation" || effect === "file_delete") {
17690
+ const filePath2 = extractFilePath(payload);
17691
+ if (!filePath2) {
17325
17692
  if (options.unknownLocalEffect === "deny") {
17326
17693
  return {
17327
17694
  verdict: "deny_pending_approval",
@@ -17351,138 +17718,20 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17351
17718
  }
17352
17719
  };
17353
17720
  }
17354
- const signals = [];
17355
- const resolvedPath = path50.isAbsolute(filePath) ? filePath : path50.resolve(cwd, filePath);
17356
- const hitsProtectedRoot = protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
17357
- if (hitsProtectedRoot) {
17358
- signals.push("control_plane_path");
17359
- return classifyFileMutationWithPolicy({
17360
- toolName,
17361
- toolKind,
17362
- filePath,
17363
- resolvedPath,
17364
- repoRoot,
17365
- cwd,
17366
- config,
17367
- options,
17368
- signals,
17369
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17370
- locationLabel: "control_plane"
17371
- });
17372
- }
17373
- const workspaceMatch = resolveWorkspaceRootMatch(
17374
- repoRoot,
17375
- options.trustedWorkspaceRoots,
17376
- resolvedPath
17377
- );
17378
- if (workspaceMatch === null) {
17379
- signals.push("outside_repo_path");
17380
- return classifyFileMutationWithPolicy({
17381
- toolName,
17382
- toolKind,
17383
- filePath,
17384
- resolvedPath,
17385
- repoRoot,
17386
- cwd,
17387
- config,
17388
- options,
17389
- signals,
17390
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17391
- locationLabel: "outside_repo"
17392
- });
17393
- }
17394
- if (workspaceMatch.kind === "trusted") {
17395
- signals.push("trusted_workspace_root");
17396
- }
17397
- const trustedCwd = resolveClassifierTrustedCwd(cwd, options);
17398
- const workspacePrescan = mutationPrescanRequiresAsk({
17399
- targets: [filePath],
17400
- cwd,
17401
- repoRoot,
17402
- trustedCwd,
17403
- trustedWorkspaceRoots: options.trustedWorkspaceRoots,
17404
- sensitivePaths
17405
- });
17406
- if (workspacePrescan) {
17407
- return classifyFileMutationWithPolicy({
17408
- toolName,
17409
- toolKind,
17410
- filePath,
17411
- resolvedPath,
17412
- repoRoot,
17413
- cwd,
17414
- config,
17415
- options,
17416
- signals,
17417
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17418
- locationLabel: "sensitive_path"
17419
- });
17420
- }
17421
- const relativePath = workspaceMatch.relativePath;
17422
- if (matchesSensitivePath(relativePath, sensitivePaths)) {
17423
- signals.push("sensitive_path");
17424
- return classifyFileMutationWithPolicy({
17425
- toolName,
17426
- toolKind,
17427
- filePath,
17428
- resolvedPath,
17429
- repoRoot,
17430
- cwd,
17431
- config,
17432
- options,
17433
- signals,
17434
- isDelete: FILE_DELETE_TOOL_NAMES.has(toolKind),
17435
- locationLabel: "sensitive_path"
17436
- });
17437
- }
17438
- if (FILE_DELETE_TOOL_NAMES.has(toolKind)) {
17439
- if (isGitPath(resolvedPath, repoRoot)) {
17440
- signals.push("protected_artifact");
17441
- return classifyFileMutationWithPolicy({
17442
- toolName,
17443
- toolKind,
17444
- filePath,
17445
- resolvedPath,
17446
- repoRoot,
17447
- cwd,
17448
- config,
17449
- options,
17450
- signals,
17451
- isDelete: true,
17452
- locationLabel: "sensitive_path"
17453
- });
17454
- }
17455
- signals.push("file_delete");
17456
- return classifyFileMutationWithPolicy({
17457
- toolName,
17458
- toolKind,
17459
- filePath,
17460
- resolvedPath,
17461
- repoRoot,
17462
- cwd,
17463
- config,
17464
- options,
17465
- signals,
17466
- isDelete: true,
17467
- locationLabel: "repo_local"
17468
- });
17469
- }
17470
- signals.push("file_mutation");
17471
- return classifyFileMutationWithPolicy({
17721
+ return classifyFilePathMutation({
17472
17722
  toolName,
17473
17723
  toolKind,
17474
- filePath,
17475
- resolvedPath,
17724
+ filePath: filePath2,
17476
17725
  repoRoot,
17477
17726
  cwd,
17478
17727
  config,
17479
17728
  options,
17480
- signals,
17481
- isDelete: false,
17482
- locationLabel: "repo_local"
17729
+ sensitivePaths,
17730
+ protectedRoots,
17731
+ isDelete: effect === "file_delete"
17483
17732
  });
17484
17733
  }
17485
- if (APPLY_PATCH_TOOL_NAMES.has(toolKind)) {
17734
+ if (effect === "apply_patch") {
17486
17735
  const patch = extractPatch(payload);
17487
17736
  const targets = patch ? applyPatchTargets2(patch) : [];
17488
17737
  if (targets.length === 0) {
@@ -17520,7 +17769,7 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17520
17769
  const result = await classifyToolUse(
17521
17770
  {
17522
17771
  tool_name: target.delete ? "Delete" : "Write",
17523
- tool_input: { path: target.path }
17772
+ tool_input: target.delete ? { path: target.path } : { path: target.path, contents: " " }
17524
17773
  },
17525
17774
  repoRoot,
17526
17775
  cwd,
@@ -17546,19 +17795,54 @@ async function classifyToolUse(payload, repoRoot, cwd, config, options = {}) {
17546
17795
  }
17547
17796
  };
17548
17797
  }
17549
- return {
17550
- verdict: "allow",
17551
- reason: "unclassified_tool",
17552
- summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17553
- fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17554
- assessment: {
17555
- reversibility: "reversible",
17556
- external: false,
17557
- blastRadius: "tool scope",
17558
- confidence: 0.5,
17559
- signals: ["unclassified_tool"]
17798
+ if (effect === "file_read") {
17799
+ const filePath2 = extractFilePath(payload);
17800
+ if (!filePath2) {
17801
+ return indeterminateToolResult(toolName, payload, repoRoot, options);
17560
17802
  }
17561
- };
17803
+ return classifyFilePathRead({
17804
+ toolName,
17805
+ toolKind,
17806
+ filePath: filePath2,
17807
+ repoRoot,
17808
+ cwd,
17809
+ config,
17810
+ options,
17811
+ sensitivePaths,
17812
+ protectedRoots
17813
+ });
17814
+ }
17815
+ if (effect === "search_read") {
17816
+ return {
17817
+ verdict: "allow",
17818
+ reason: "effect.search_read",
17819
+ summary: canonicalStringify(scrubPayload(payload.tool_input ?? {}, options)),
17820
+ fingerprint: toolFingerprint(toolName, fingerprintPayload(payload, options), repoRoot),
17821
+ assessment: {
17822
+ reversibility: "reversible",
17823
+ external: false,
17824
+ blastRadius: "tool scope",
17825
+ confidence: 0.88,
17826
+ signals: ["effect.search_read"]
17827
+ }
17828
+ };
17829
+ }
17830
+ const filePath = extractFilePath(payload);
17831
+ if (filePath) {
17832
+ return classifyFilePathMutation({
17833
+ toolName,
17834
+ toolKind,
17835
+ filePath,
17836
+ repoRoot,
17837
+ cwd,
17838
+ config,
17839
+ options,
17840
+ sensitivePaths,
17841
+ protectedRoots,
17842
+ isDelete: toolKind === "delete"
17843
+ });
17844
+ }
17845
+ return indeterminateToolResult(toolName, payload, repoRoot, options);
17562
17846
  }
17563
17847
 
17564
17848
  // src/core/gate-engine.ts
@@ -17893,15 +18177,8 @@ function gateEnabledForAction(config, action) {
17893
18177
  if (action.kind === "subagent") {
17894
18178
  return config.gates.subagent;
17895
18179
  }
17896
- const toolName = action.toolName ?? String(action.payload?.tool_name ?? "");
17897
- if (toolName === "Shell") {
17898
- return config.gates.toolShell;
17899
- }
17900
- if (toolName === "Write" || toolName === "StrReplace" || toolName === "Delete") {
17901
- return config.gates.fileMutation;
17902
- }
17903
- if (toolName === "Task") {
17904
- return config.gates.subagent;
18180
+ if (action.kind === "tool") {
18181
+ return config.gates.fileMutation || config.gates.toolShell;
17905
18182
  }
17906
18183
  return true;
17907
18184
  }
@@ -17938,7 +18215,7 @@ function hashDecisionConfig(config) {
17938
18215
  init_fingerprint2();
17939
18216
 
17940
18217
  // src/version.ts
17941
- var PACKAGE_VERSION = "0.10.0";
18218
+ var PACKAGE_VERSION = "0.10.1";
17942
18219
 
17943
18220
  // src/runtime-provenance.ts
17944
18221
  function resolveRuntimeArtifactHash(artifactHash) {
@@ -21239,21 +21516,7 @@ async function evaluateGatedAction(ctx, deps, params) {
21239
21516
  ...authorization,
21240
21517
  egressProxyActive
21241
21518
  };
21242
- let predicted = await classifyGatedActionAsync(action, ctx.config, enrichedClassifierOptions);
21243
- if (action.kind === "tool" && predicted.reason === "unclassified_tool" && (ctx.config.policy.codexUnmappedTool ?? "deny") === "deny") {
21244
- predicted = {
21245
- ...predicted,
21246
- verdict: "deny_pending_approval",
21247
- reason: "unmapped_tool",
21248
- assessment: {
21249
- reversibility: "irreversible",
21250
- external: false,
21251
- blastRadius: "unknown tool action",
21252
- confidence: 0.5,
21253
- signals: ["unmapped_tool"]
21254
- }
21255
- };
21256
- }
21519
+ const predicted = await classifyGatedActionAsync(action, ctx.config, enrichedClassifierOptions);
21257
21520
  if (action.kind === "shell" && action.command && isContainedUnknownExecutionEligible(ctx.config, action, predicted)) {
21258
21521
  const mediated = await mediateContainedUnknownExecution({
21259
21522
  ctx,
@@ -21382,40 +21645,6 @@ async function evaluateGatedAction(ctx, deps, params) {
21382
21645
  scopeHintPayload: params.payload
21383
21646
  });
21384
21647
  }
21385
- async function gateUnmappedToolVerdict(ctx, deps, toolName, payload) {
21386
- const scrubOpts = scrubOptionsFromConfig(ctx.config);
21387
- const replayPayload = fingerprintReplayPayload("tool", payload, scrubOpts) ?? {};
21388
- const result = {
21389
- verdict: "deny_pending_approval",
21390
- reason: "unmapped_tool",
21391
- summary: toolName,
21392
- fingerprint: toolFingerprint(toolName, replayPayload, ctx.repoRoot),
21393
- assessment: {
21394
- reversibility: "irreversible",
21395
- external: false,
21396
- blastRadius: "unknown Codex tool action",
21397
- confidence: 0.5,
21398
- signals: ["unmapped_tool"]
21399
- }
21400
- };
21401
- return gateDecisionToVerdict(ctx, deps, "tool", result, {
21402
- approvalInput: {
21403
- input: toolName,
21404
- inputKind: "tool",
21405
- cwd: ctx.repoRoot,
21406
- toolName,
21407
- payload: replayPayload
21408
- },
21409
- replayAction: {
21410
- kind: "tool",
21411
- cwd: ctx.repoRoot,
21412
- toolName,
21413
- payload: replayPayload,
21414
- fingerprint: result.fingerprint,
21415
- repoRoot: ctx.repoRoot
21416
- }
21417
- });
21418
- }
21419
21648
  async function consumeCapabilityGrantIfUsed(ctx, deps, result) {
21420
21649
  if (result.reason !== "capability_grant" && result.authorizationDecision?.matchedRule !== "grant.exact") {
21421
21650
  return true;
@@ -22107,13 +22336,14 @@ async function runToolGateHook(eventName) {
22107
22336
  const ctx = await loadRuntimeContext(cwd);
22108
22337
  const deps = createDefaultGateRuntimeDeps();
22109
22338
  if (!kind) {
22110
- const policy = ctx.config.policy?.codexUnmappedTool ?? "deny";
22111
- if (policy === "allow") {
22112
- await appendObservedAudit(ctx, deps, eventName, payload);
22113
- jsonResponse({});
22114
- return;
22115
- }
22116
- const verdict3 = await gateUnmappedToolVerdict(ctx, deps, toolName, payload);
22339
+ const normalizedPayload2 = normalizeCodexToolPayload("tool", payload);
22340
+ const verdict3 = await evaluateGatedAction(ctx, deps, {
22341
+ kind: "tool",
22342
+ cwd,
22343
+ payload: normalizedPayload2,
22344
+ toolName,
22345
+ sourceEvent: eventName
22346
+ });
22117
22347
  jsonResponse(gateVerdictToCodexPreToolUseResponse(verdict3));
22118
22348
  return;
22119
22349
  }