@wrongstack/plugins 0.306.3 → 0.307.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/index.js CHANGED
@@ -9343,7 +9343,7 @@ var DEFAULT_ARTIFACT_PATTERNS = Object.freeze([
9343
9343
  "*.env.*"
9344
9344
  ]);
9345
9345
  function toForwardSlashes(p) {
9346
- return sep3 === "/" ? p : p.split(sep3).join("/");
9346
+ return p.split(sep3).join("/").replaceAll("\\", "/");
9347
9347
  }
9348
9348
  function globToSource(glob) {
9349
9349
  return glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
@@ -13544,6 +13544,19 @@ var plugin38 = {
13544
13544
  }
13545
13545
  }
13546
13546
  state35.eventUnsubscribers = [];
13547
+ if (api.tools?.unregister) {
13548
+ try {
13549
+ api.tools.unregister("notify_send");
13550
+ api.tools.unregister("notify_hub_status");
13551
+ } catch {
13552
+ }
13553
+ }
13554
+ if (api.notifier?.unregisterChannel) {
13555
+ try {
13556
+ api.notifier.unregisterChannel("webhook");
13557
+ } catch {
13558
+ }
13559
+ }
13547
13560
  const channelCounters = state35.channel?.counters();
13548
13561
  state35.channel = null;
13549
13562
  state35.lastDelivery = null;
@@ -13565,40 +13578,7 @@ var plugin38 = {
13565
13578
  };
13566
13579
  var notify_hub_default = plugin38;
13567
13580
 
13568
- // src/path-guard/index.ts
13569
- function createState() {
13570
- return { invocations: 0, blocks: 0, warns: 0, lastBlock: null, hookUnregister: null };
13571
- }
13572
- var states = /* @__PURE__ */ new WeakMap();
13573
- var latestState = createState();
13574
- var DEFAULT_PROTECT = [
13575
- "pnpm-lock.yaml",
13576
- "package-lock.json",
13577
- "yarn.lock",
13578
- "bun.lockb",
13579
- "Cargo.lock",
13580
- "poetry.lock",
13581
- ".env",
13582
- ".env.*",
13583
- ".git/**",
13584
- "**/migrations/**"
13585
- ];
13586
- var DEFAULTS33 = {
13587
- enabled: true,
13588
- mode: "block",
13589
- protect: DEFAULT_PROTECT,
13590
- allow: []
13591
- };
13592
- function readConfig34(raw) {
13593
- if (!raw || typeof raw !== "object") return { ...DEFAULTS33, protect: [...DEFAULT_PROTECT] };
13594
- const r = raw;
13595
- return {
13596
- enabled: r["enabled"] !== false,
13597
- mode: r["mode"] === "warn" ? "warn" : "block",
13598
- protect: Array.isArray(r["protect"]) ? r["protect"].filter((p) => typeof p === "string" && p.length > 0) : [...DEFAULT_PROTECT],
13599
- allow: Array.isArray(r["allow"]) ? r["allow"].filter((p) => typeof p === "string" && p.length > 0) : []
13600
- };
13601
- }
13581
+ // src/path-guard/glob.ts
13602
13582
  function compilePathGlob(pattern) {
13603
13583
  const normalized = pattern.replace(/\\/g, "/");
13604
13584
  let source = "";
@@ -13764,244 +13744,8 @@ function targetFullyAllowed(target, allowTexts, allowRes) {
13764
13744
  return allowNormalized.endsWith("/**") && allowRe?.test(`${normalized}/.path-guard-probe`);
13765
13745
  });
13766
13746
  }
13767
- var WRITE_CAPABILITIES = /* @__PURE__ */ new Set(["fs.write", "fs.write.outside-project"]);
13768
- var DISK_MUTATING_CAPABILITIES = /* @__PURE__ */ new Set(["package.install"]);
13769
- var SHELL_CAPABILITIES = /* @__PURE__ */ new Set(["shell.arbitrary", "shell.restricted", "shell.exec"]);
13770
- var LEGACY_SHELL_TOOLS = /* @__PURE__ */ new Set(["bash", "shell", "exec"]);
13771
- var LEGACY_WRITE_TOOLS = /* @__PURE__ */ new Set([
13772
- "write",
13773
- "edit",
13774
- "patch",
13775
- "scaffold",
13776
- "format",
13777
- "replace",
13778
- "design",
13779
- "install"
13780
- ]);
13781
- function writesToDisk(input) {
13782
- const capabilities = input.toolCapabilities;
13783
- const capabilitiesUndeclared = !capabilities || capabilities.length === 0;
13784
- const hasWriteCap = capabilities?.some((capability) => WRITE_CAPABILITIES.has(capability)) ?? false;
13785
- const hasDiskMutatingCap = capabilities?.some((capability) => DISK_MUTATING_CAPABILITIES.has(capability)) ?? false;
13786
- const hasCommandString = typeof input.toolInput?.["command"] === "string";
13787
- if (hasCommandString && executesShell(input) && !hasWriteCap && !hasDiskMutatingCap && !(capabilitiesUndeclared && LEGACY_WRITE_TOOLS.has(input.toolName ?? "")))
13788
- return false;
13789
- if (!capabilitiesUndeclared) {
13790
- if (hasWriteCap || hasDiskMutatingCap) return true;
13791
- if (capabilities.includes("mcp.proxy")) {
13792
- const segmentedName = (input.toolName ?? "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9]+/gi, "_").toLowerCase();
13793
- const filesystemWriter = /(?:^|_)filesystem(?:_|$)/.test(segmentedName) && /(?:^|_)(?:add|append|copy|create|delete|edit|mkdir|move|remove|rename|write)(?:_|$)/.test(
13794
- segmentedName
13795
- );
13796
- const toolInput = input.toolInput ?? {};
13797
- const hasKnownPath = [...PATH_FIELDS, ...PATH_LIST_FIELDS].some(
13798
- (field) => toolInput[field] !== void 0
13799
- );
13800
- if (filesystemWriter && hasKnownPath) return true;
13801
- }
13802
- return false;
13803
- }
13804
- if (input.toolMutating !== void 0) return input.toolMutating;
13805
- return LEGACY_WRITE_TOOLS.has(input.toolName ?? "");
13806
- }
13807
- function executesShell(input) {
13808
- const toolName = input.toolName ?? "";
13809
- if (LEGACY_SHELL_TOOLS.has(toolName)) return true;
13810
- if (toolName === "git") return false;
13811
- const segmentedName = toolName.replace(/([a-z0-9])([A-Z])/g, "$1_$2");
13812
- const isCommandStringRunner = /(?:^|[_.-])(?:command|exec|execute|shell|terminal)(?:$|[_.-])/i.test(segmentedName);
13813
- const hasShellCapability = input.toolCapabilities?.some((capability) => SHELL_CAPABILITIES.has(capability)) ?? false;
13814
- const hasCommandString = typeof input.toolInput?.["command"] === "string";
13815
- return isCommandStringRunner && hasShellCapability || hasCommandString && input.toolMutating === true;
13816
- }
13817
- var PATH_FIELDS = [
13818
- "path",
13819
- "file_path",
13820
- "filePath",
13821
- "target",
13822
- "target_path",
13823
- "targetPath",
13824
- "destination",
13825
- "dest",
13826
- "output_path",
13827
- "outputPath",
13828
- "out",
13829
- "file"
13830
- ];
13831
- var PATH_LIST_FIELDS = ["files", "paths"];
13832
- var SOURCE_PATH_FIELDS = [
13833
- "source",
13834
- "from",
13835
- "src",
13836
- "input_path",
13837
- "inputPath",
13838
- "old_path",
13839
- "oldPath"
13840
- ];
13841
- var MOVE_TOOL_NAME = /(?:^|[_.-])(move|rename)(?:$|[_.-])/i;
13842
- var DIRECTORY_DELETE_TOOL_NAME = /(?:^|[_.-])(?:rmdir|(?:delete|remove)[_.-](?:dir|directory))(?:$|[_.-])/i;
13843
- function segmentedToolName(toolName) {
13844
- return toolName.replace(/([a-z0-9])([A-Z])/g, "$1_$2");
13845
- }
13846
- function isMoveToolName(toolName) {
13847
- return MOVE_TOOL_NAME.test(segmentedToolName(toolName));
13848
- }
13849
- function isDirectoryDeleteToolName(toolName) {
13850
- return DIRECTORY_DELETE_TOOL_NAME.test(segmentedToolName(toolName));
13851
- }
13852
- function cleanPatchPath(raw) {
13853
- const trimmed = raw.trim();
13854
- if (!trimmed || trimmed === "/dev/null") return null;
13855
- const quoted = /^"([^"]+)"(?:\s|$)/.exec(trimmed)?.[1];
13856
- const withoutTimestamp = quoted ?? trimmed.replace(/(?:\t|\s+)\d{4}-\d{2}-\d{2}(?:\s.*)?$/, "").trim();
13857
- if (!withoutTimestamp || withoutTimestamp === "/dev/null") return null;
13858
- return withoutTimestamp.replace(/^[ab]\//, "");
13859
- }
13860
- function pathsFromPatch(patch) {
13861
- const paths = [];
13862
- const lines = patch.split(/\r?\n/);
13863
- for (let index = 0; index + 2 < lines.length; index += 1) {
13864
- const oldHeader = lines[index];
13865
- const newHeader = lines[index + 1];
13866
- const hunkHeader = lines[index + 2];
13867
- if (!oldHeader?.startsWith("--- ") || !newHeader?.startsWith("+++ ")) continue;
13868
- if (!hunkHeader?.startsWith("@@ ")) continue;
13869
- const oldPath = cleanPatchPath(oldHeader.slice(4));
13870
- const newPath = cleanPatchPath(newHeader.slice(4));
13871
- if (oldPath) paths.push(oldPath);
13872
- if (newPath) paths.push(newPath);
13873
- }
13874
- return paths;
13875
- }
13876
- function appendTarget(targets, value, kind, base) {
13877
- if (typeof value !== "string" || value.length === 0) return;
13878
- targets.push({
13879
- path: resolveTargetPath(value, base),
13880
- kind
13881
- });
13882
- }
13883
- function appendTargetList(targets, value, directoryCapable, base) {
13884
- const appendPath = (path) => {
13885
- targets.push({
13886
- path: resolveTargetPath(path, base),
13887
- kind: isUnresolvedPathScope(path) || isRootPathScope(path) || directoryCapable && isDirectoryAmbiguousPath(path) ? "scope" : "file"
13888
- });
13889
- };
13890
- if (typeof value === "string") {
13891
- const paths = value.split(",").map((item) => item.trim()).filter((path) => path.length > 0);
13892
- for (const path of paths) appendPath(path);
13893
- return;
13894
- }
13895
- if (!Array.isArray(value)) return;
13896
- for (const item of value) {
13897
- if (typeof item !== "string") continue;
13898
- const path = item.trim();
13899
- if (path.length > 0) appendPath(path);
13900
- }
13901
- }
13902
- function isReadOnlyInvocation(toolName, toolInput) {
13903
- if (toolName === "git") {
13904
- const command = toolInput["command"];
13905
- if (command === "status" || command === "log" || command === "diff" || command === "fetch" || command === "commit" || command === "branch" || command === "push") {
13906
- return true;
13907
- }
13908
- return command === "worktree" && toolInput["worktreeAction"] === "list";
13909
- }
13910
- if (toolName === "design") {
13911
- const action = toolInput["action"] ?? "list";
13912
- return action === "list" || action === "foundations" || action === "verify";
13913
- }
13914
- if (toolName === "format") return toolInput["check"] === true;
13915
- if (toolName === "patch" || toolName === "scaffold") return toolInput["dry_run"] === true;
13916
- if (toolName === "replace") return toolInput["dry_run"] !== false;
13917
- return false;
13918
- }
13919
- function pathsFromToolInput(toolInput, toolName, invocationCwd) {
13920
- const targets = [];
13921
- const effectiveCwd = effectiveToolCwd(toolInput["cwd"], invocationCwd);
13922
- const directoryDelete = isDirectoryDeleteToolName(toolName);
13923
- for (const field of PATH_FIELDS) {
13924
- const value = toolInput[field];
13925
- appendTarget(
13926
- targets,
13927
- value,
13928
- directoryDelete ? "deletion-scope" : typeof value === "string" && isUnresolvedPathScope(value) ? "scope" : "file",
13929
- effectiveCwd
13930
- );
13931
- }
13932
- const directoryCapableLists = toolName === "format" || toolName === "replace";
13933
- for (const field of PATH_LIST_FIELDS) {
13934
- appendTargetList(
13935
- targets,
13936
- toolInput[field],
13937
- directoryCapableLists && field === "files",
13938
- effectiveCwd
13939
- );
13940
- }
13941
- if (toolName === "install") {
13942
- appendTarget(targets, effectiveCwd ?? invocationCwd ?? ".", "scope");
13943
- }
13944
- if (toolName === "git") {
13945
- const command = toolInput["command"];
13946
- if (command === "worktree" && (toolInput["worktreeAction"] === "add" || toolInput["worktreeAction"] === "remove")) {
13947
- appendTarget(targets, toolInput["worktreePath"] ?? toolInput["worktree_path"], "file");
13948
- }
13949
- }
13950
- if (toolName === "scaffold") {
13951
- const cwdValue = toolInput["cwd"];
13952
- const cwd = typeof cwdValue === "string" && cwdValue.length > 0 ? cwdValue : invocationCwd ?? ".";
13953
- const base = normalizePath2(cwd).replace(/\/$/, "");
13954
- const name = typeof toolInput["name"] === "string" ? toolInput["name"] : "";
13955
- const template = toolInput["template"];
13956
- const filesByTemplate = {
13957
- "npm-package": ["package.json", "tsconfig.json", "src/index.ts", "src/index.test.ts"],
13958
- "cli-tool": ["package.json", "src/index.ts"],
13959
- "react-component": name ? [`${name}.tsx`, `${name}.test.tsx`] : []
13960
- };
13961
- for (const file of typeof template === "string" ? filesByTemplate[template] ?? [] : []) {
13962
- targets.push({ path: `${base}/${file}`, kind: "file" });
13963
- }
13964
- targets.push({ path: base, kind: "scope" });
13965
- }
13966
- if (isMoveToolName(toolName)) {
13967
- for (const field of SOURCE_PATH_FIELDS) {
13968
- appendTarget(targets, toolInput[field], "deletion-scope", effectiveCwd);
13969
- }
13970
- }
13971
- const patch = toolInput["patch"];
13972
- if (typeof patch === "string") {
13973
- const patchTargets = pathsFromPatch(patch);
13974
- const directory = toolInput["directory"];
13975
- const baseValue = typeof directory === "string" && directory.length > 0 ? directory : invocationCwd;
13976
- if (baseValue) {
13977
- const base = normalizePath2(baseValue).replace(/\/$/, "");
13978
- targets.push(
13979
- ...patchTargets.map((target) => ({ path: `${base}/${target}`, kind: "file" }))
13980
- );
13981
- } else {
13982
- targets.push(...patchTargets.map((path) => ({ path, kind: "file" })));
13983
- }
13984
- }
13985
- if (targets.length === 0) {
13986
- const cwd = effectiveCwd ?? ".";
13987
- targets.push({ path: normalizePath2(cwd).replace(/\/$/, "") || ".", kind: "scope" });
13988
- }
13989
- const seen = /* @__PURE__ */ new Set();
13990
- return targets.map((target) => ({
13991
- ...target,
13992
- path: relativeToInvocationCwd(target.path, invocationCwd)
13993
- })).filter((target) => {
13994
- const key = `${target.kind}:${target.path}`;
13995
- if (seen.has(key)) return false;
13996
- seen.add(key);
13997
- return true;
13998
- });
13999
- }
14000
- function operationLabel(toolName) {
14001
- if (toolName === "write") return "write";
14002
- if (toolName === "edit") return "edit";
14003
- return `write via "${toolName}"`;
14004
- }
13747
+
13748
+ // src/path-guard/shell-targets.ts
14005
13749
  var VALUE_TAKING_GIT_OPTIONS = /* @__PURE__ */ new Set([
14006
13750
  "-C",
14007
13751
  "-c",
@@ -14165,21 +13909,6 @@ function unwrapEnvSplitStringAtBoundary(command) {
14165
13909
  }
14166
13910
  return command;
14167
13911
  }
14168
- function stripTransparentLaunchers(command) {
14169
- let stripped = command;
14170
- let previous;
14171
- do {
14172
- previous = stripped;
14173
- let beforeUnwrap;
14174
- do {
14175
- beforeUnwrap = stripped;
14176
- stripped = unwrapEnvSplitStringAtBoundary(stripped);
14177
- } while (stripped !== beforeUnwrap);
14178
- stripped = stripLauncherAtBoundary(stripped, "env", ENV_VALUE_TAKING);
14179
- stripped = stripLauncherAtBoundary(stripped, "sudo", SUDO_VALUE_TAKING);
14180
- } while (stripped !== previous);
14181
- return stripped;
14182
- }
14183
13912
  var SUDO_VALUE_TAKING = /* @__PURE__ */ new Set([
14184
13913
  "-u",
14185
13914
  "-g",
@@ -14221,6 +13950,21 @@ var ENV_FLAG_OPTIONS = /* @__PURE__ */ new Set([
14221
13950
  "--help",
14222
13951
  "--version"
14223
13952
  ]);
13953
+ function stripTransparentLaunchers(command) {
13954
+ let stripped = command;
13955
+ let previous;
13956
+ do {
13957
+ previous = stripped;
13958
+ let beforeUnwrap;
13959
+ do {
13960
+ beforeUnwrap = stripped;
13961
+ stripped = unwrapEnvSplitStringAtBoundary(stripped);
13962
+ } while (stripped !== beforeUnwrap);
13963
+ stripped = stripLauncherAtBoundary(stripped, "env", ENV_VALUE_TAKING);
13964
+ stripped = stripLauncherAtBoundary(stripped, "sudo", SUDO_VALUE_TAKING);
13965
+ } while (stripped !== previous);
13966
+ return stripped;
13967
+ }
14224
13968
  function firstUnquotedShellSeparator(raw, start, end) {
14225
13969
  let quote = null;
14226
13970
  for (let index = start; index < end; index += 1) {
@@ -14357,9 +14101,7 @@ function executableCommandSubstitutions(command) {
14357
14101
  }
14358
14102
  const commandSubstitution = char === "$" && command[index + 1] === "(";
14359
14103
  const processSubstitution = (char === ">" || char === "<") && command[index + 1] === "(";
14360
- if (!commandSubstitution && !processSubstitution || // Arithmetic expansion evaluates an expression; it does not execute its
14361
- // contents as a shell command substitution.
14362
- commandSubstitution && command[index + 2] === "(")
14104
+ if (!commandSubstitution && !processSubstitution || commandSubstitution && command[index + 2] === "(")
14363
14105
  continue;
14364
14106
  let depth = 1;
14365
14107
  let innerQuote = null;
@@ -14742,70 +14484,345 @@ function destructiveTargetsAtDepth(command, depth) {
14742
14484
  targets.push(gitTarget("."));
14743
14485
  }
14744
14486
  }
14745
- const findDelete = /(?:^|[;&|\r\n]\s*|\$\(\s*|\(\s*|`\s*)find\b([^;&|)`]*(?:\s-delete(?:\s|$)|\s-exec(?:dir)?\s+(?:[^\s;&|]+[\\/])?(?:rm|rmdir)\b)[^;&|)`]*)/gi;
14746
- let f = findDelete.exec(normalizedCommand);
14747
- while (f !== null) {
14748
- const tokens = shellTokens(f[1] ?? "");
14749
- const expressionIndex = tokens.findIndex(
14750
- (token) => token.startsWith("-") || token === "!" || token === "("
14487
+ const findDelete = /(?:^|[;&|\r\n]\s*|\$\(\s*|\(\s*|`\s*)find\b([^;&|)`]*(?:\s-delete(?:\s|$)|\s-exec(?:dir)?\s+(?:[^\s;&|]+[\\/])?(?:rm|rmdir)\b)[^;&|)`]*)/gi;
14488
+ let f = findDelete.exec(normalizedCommand);
14489
+ while (f !== null) {
14490
+ const tokens = shellTokens(f[1] ?? "");
14491
+ const expressionIndex = tokens.findIndex(
14492
+ (token) => token.startsWith("-") || token === "!" || token === "("
14493
+ );
14494
+ const roots = (expressionIndex === -1 ? tokens : tokens.slice(0, expressionIndex)).filter(
14495
+ (token) => token.length > 0
14496
+ );
14497
+ targets.push(...roots.length > 0 ? roots : ["."]);
14498
+ f = findDelete.exec(normalizedCommand);
14499
+ }
14500
+ const shellWrapper = /\b(?:ba|z|k)?sh\s+-c\s+(['"])(.*?)\1/gi;
14501
+ let w = shellWrapper.exec(normalizedCommand);
14502
+ while (w !== null) {
14503
+ if (w[2] && !tokenIsQuoted(w, w[0].split(/\s/)[0] ?? "")) {
14504
+ targets.push(...destructiveTargetsAtDepth(w[2], depth + 1));
14505
+ }
14506
+ w = shellWrapper.exec(normalizedCommand);
14507
+ }
14508
+ let quote = null;
14509
+ for (let index = 0; index < normalizedCommand.length; index += 1) {
14510
+ const char = normalizedCommand[index];
14511
+ if (char === "\n") {
14512
+ quote = null;
14513
+ continue;
14514
+ }
14515
+ if (isQuoteBoundary(normalizedCommand, index, quote)) {
14516
+ quote = quote === char ? null : char === "'" ? "'" : '"';
14517
+ continue;
14518
+ }
14519
+ if (quote !== null || char !== ">") continue;
14520
+ const redirectsBothStreams = normalizedCommand[index + 1] === "&";
14521
+ const overridesNoclobber = normalizedCommand[index + 1] === "|";
14522
+ if (redirectsBothStreams || overridesNoclobber || normalizedCommand[index + 1] === ">") {
14523
+ index += 1;
14524
+ }
14525
+ while (normalizedCommand[index + 1] === " " || normalizedCommand[index + 1] === " ") {
14526
+ index += 1;
14527
+ }
14528
+ const targetQuote = normalizedCommand[index + 1];
14529
+ let end = index + 1;
14530
+ let target;
14531
+ if (targetQuote === "'" || targetQuote === '"') {
14532
+ end = normalizedCommand.indexOf(targetQuote, index + 2);
14533
+ if (end === -1) continue;
14534
+ target = normalizedCommand.slice(index + 2, end);
14535
+ } else {
14536
+ while (end < normalizedCommand.length && !/[\s;&|>()]/.test(normalizedCommand[end] ?? "")) {
14537
+ end += 1;
14538
+ }
14539
+ target = normalizedCommand.slice(index + 1, end);
14540
+ }
14541
+ if (target && !(redirectsBothStreams && (/^\d+$/.test(target) || target === "-"))) {
14542
+ targets.push(target);
14543
+ }
14544
+ index = end;
14545
+ }
14546
+ return [
14547
+ ...new Set(
14548
+ targets.map((target) => target.replace(/^['"]|['"]$/g, "")).filter((target) => target !== "/dev/null" && target.toLowerCase() !== "nul")
14549
+ )
14550
+ ];
14551
+ }
14552
+
14553
+ // src/path-guard/tool-targets.ts
14554
+ var WRITE_CAPABILITIES = /* @__PURE__ */ new Set(["fs.write", "fs.write.outside-project"]);
14555
+ var DISK_MUTATING_CAPABILITIES = /* @__PURE__ */ new Set(["package.install"]);
14556
+ var SHELL_CAPABILITIES = /* @__PURE__ */ new Set(["shell.arbitrary", "shell.restricted", "shell.exec"]);
14557
+ var LEGACY_SHELL_TOOLS = /* @__PURE__ */ new Set(["bash", "shell", "exec"]);
14558
+ var LEGACY_WRITE_TOOLS = /* @__PURE__ */ new Set([
14559
+ "write",
14560
+ "edit",
14561
+ "patch",
14562
+ "scaffold",
14563
+ "format",
14564
+ "replace",
14565
+ "design",
14566
+ "install"
14567
+ ]);
14568
+ var PATH_FIELDS = [
14569
+ "path",
14570
+ "file_path",
14571
+ "filePath",
14572
+ "target",
14573
+ "target_path",
14574
+ "targetPath",
14575
+ "destination",
14576
+ "dest",
14577
+ "output_path",
14578
+ "outputPath",
14579
+ "out",
14580
+ "file"
14581
+ ];
14582
+ var PATH_LIST_FIELDS = ["files", "paths"];
14583
+ var SOURCE_PATH_FIELDS = [
14584
+ "source",
14585
+ "from",
14586
+ "src",
14587
+ "input_path",
14588
+ "inputPath",
14589
+ "old_path",
14590
+ "oldPath"
14591
+ ];
14592
+ var MOVE_TOOL_NAME = /(?:^|[_.-])(move|rename)(?:$|[_.-])/i;
14593
+ var DIRECTORY_DELETE_TOOL_NAME = /(?:^|[_.-])(?:rmdir|(?:delete|remove)[_.-](?:dir|directory))(?:$|[_.-])/i;
14594
+ function segmentedToolName(toolName) {
14595
+ return toolName.replace(/([a-z0-9])([A-Z])/g, "$1_$2");
14596
+ }
14597
+ function isMoveToolName(toolName) {
14598
+ return MOVE_TOOL_NAME.test(segmentedToolName(toolName));
14599
+ }
14600
+ function isDirectoryDeleteToolName(toolName) {
14601
+ return DIRECTORY_DELETE_TOOL_NAME.test(segmentedToolName(toolName));
14602
+ }
14603
+ function cleanPatchPath(raw) {
14604
+ const trimmed = raw.trim();
14605
+ if (!trimmed || trimmed === "/dev/null") return null;
14606
+ const quoted = /^"([^"]+)"(?:\s|$)/.exec(trimmed)?.[1];
14607
+ const withoutTimestamp = quoted ?? trimmed.replace(/(?:\t|\s+)\d{4}-\d{2}-\d{2}(?:\s.*)?$/, "").trim();
14608
+ if (!withoutTimestamp || withoutTimestamp === "/dev/null") return null;
14609
+ return withoutTimestamp.replace(/^[ab]\//, "");
14610
+ }
14611
+ function pathsFromPatch(patch) {
14612
+ const paths = [];
14613
+ const lines = patch.split(/\r?\n/);
14614
+ for (let index = 0; index + 2 < lines.length; index += 1) {
14615
+ const oldHeader = lines[index];
14616
+ const newHeader = lines[index + 1];
14617
+ const hunkHeader = lines[index + 2];
14618
+ if (!oldHeader?.startsWith("--- ") || !newHeader?.startsWith("+++ ")) continue;
14619
+ if (!hunkHeader?.startsWith("@@ ")) continue;
14620
+ const oldPath = cleanPatchPath(oldHeader.slice(4));
14621
+ const newPath = cleanPatchPath(newHeader.slice(4));
14622
+ if (oldPath) paths.push(oldPath);
14623
+ if (newPath) paths.push(newPath);
14624
+ }
14625
+ return paths;
14626
+ }
14627
+ function appendTarget(targets, value, kind, base) {
14628
+ if (typeof value !== "string" || value.length === 0) return;
14629
+ targets.push({
14630
+ path: resolveTargetPath(value, base),
14631
+ kind
14632
+ });
14633
+ }
14634
+ function appendTargetList(targets, value, directoryCapable, base) {
14635
+ const appendPath = (path) => {
14636
+ targets.push({
14637
+ path: resolveTargetPath(path, base),
14638
+ kind: isUnresolvedPathScope(path) || isRootPathScope(path) || directoryCapable && isDirectoryAmbiguousPath(path) ? "scope" : "file"
14639
+ });
14640
+ };
14641
+ if (typeof value === "string") {
14642
+ const paths = value.split(",").map((item) => item.trim()).filter((path) => path.length > 0);
14643
+ for (const path of paths) appendPath(path);
14644
+ return;
14645
+ }
14646
+ if (!Array.isArray(value)) return;
14647
+ for (const item of value) {
14648
+ if (typeof item !== "string") continue;
14649
+ const path = item.trim();
14650
+ if (path.length > 0) appendPath(path);
14651
+ }
14652
+ }
14653
+ function isReadOnlyInvocation(toolName, toolInput) {
14654
+ if (toolName === "git") {
14655
+ const command = toolInput["command"];
14656
+ if (command === "status" || command === "log" || command === "diff" || command === "fetch" || command === "commit" || command === "branch" || command === "push") {
14657
+ return true;
14658
+ }
14659
+ return command === "worktree" && toolInput["worktreeAction"] === "list";
14660
+ }
14661
+ if (toolName === "design") {
14662
+ const action = toolInput["action"] ?? "list";
14663
+ return action === "list" || action === "foundations" || action === "verify";
14664
+ }
14665
+ if (toolName === "format") return toolInput["check"] === true;
14666
+ if (toolName === "patch" || toolName === "scaffold") return toolInput["dry_run"] === true;
14667
+ if (toolName === "replace") return toolInput["dry_run"] !== false;
14668
+ return false;
14669
+ }
14670
+ function writesToDisk(input) {
14671
+ const capabilities = input.toolCapabilities;
14672
+ const capabilitiesUndeclared = !capabilities || capabilities.length === 0;
14673
+ const hasWriteCap = capabilities?.some((capability) => WRITE_CAPABILITIES.has(capability)) ?? false;
14674
+ const hasDiskMutatingCap = capabilities?.some((capability) => DISK_MUTATING_CAPABILITIES.has(capability)) ?? false;
14675
+ const hasCommandString = typeof input.toolInput?.["command"] === "string";
14676
+ if (hasCommandString && executesShell(input) && !hasWriteCap && !hasDiskMutatingCap && !(capabilitiesUndeclared && LEGACY_WRITE_TOOLS.has(input.toolName ?? "")))
14677
+ return false;
14678
+ if (!capabilitiesUndeclared) {
14679
+ if (hasWriteCap || hasDiskMutatingCap) return true;
14680
+ if (capabilities.includes("mcp.proxy")) {
14681
+ const segmentedName = (input.toolName ?? "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9]+/gi, "_").toLowerCase();
14682
+ const filesystemWriter = /(?:^|_)filesystem(?:_|$)/.test(segmentedName) && /(?:^|_)(?:add|append|copy|create|delete|edit|mkdir|move|remove|rename|write)(?:_|$)/.test(
14683
+ segmentedName
14684
+ );
14685
+ const toolInput = input.toolInput ?? {};
14686
+ const hasKnownPath = [...PATH_FIELDS, ...PATH_LIST_FIELDS].some(
14687
+ (field) => toolInput[field] !== void 0
14688
+ );
14689
+ if (filesystemWriter && hasKnownPath) return true;
14690
+ }
14691
+ return false;
14692
+ }
14693
+ if (input.toolMutating !== void 0) return input.toolMutating;
14694
+ return LEGACY_WRITE_TOOLS.has(input.toolName ?? "");
14695
+ }
14696
+ function executesShell(input) {
14697
+ const toolName = input.toolName ?? "";
14698
+ if (LEGACY_SHELL_TOOLS.has(toolName)) return true;
14699
+ if (toolName === "git") return false;
14700
+ const segmentedName = toolName.replace(/([a-z0-9])([A-Z])/g, "$1_$2");
14701
+ const isCommandStringRunner = /(?:^|[_.-])(?:command|exec|execute|shell|terminal)(?:$|[_.-])/i.test(segmentedName);
14702
+ const hasShellCapability = input.toolCapabilities?.some((capability) => SHELL_CAPABILITIES.has(capability)) ?? false;
14703
+ const hasCommandString = typeof input.toolInput?.["command"] === "string";
14704
+ return isCommandStringRunner && hasShellCapability || hasCommandString && input.toolMutating === true;
14705
+ }
14706
+ function pathsFromToolInput(toolInput, toolName, invocationCwd) {
14707
+ const targets = [];
14708
+ const effectiveCwd = effectiveToolCwd(toolInput["cwd"], invocationCwd);
14709
+ const directoryDelete = isDirectoryDeleteToolName(toolName);
14710
+ for (const field of PATH_FIELDS) {
14711
+ const value = toolInput[field];
14712
+ appendTarget(
14713
+ targets,
14714
+ value,
14715
+ directoryDelete ? "deletion-scope" : typeof value === "string" && isUnresolvedPathScope(value) ? "scope" : "file",
14716
+ effectiveCwd
14751
14717
  );
14752
- const roots = (expressionIndex === -1 ? tokens : tokens.slice(0, expressionIndex)).filter(
14753
- (token) => token.length > 0
14718
+ }
14719
+ const directoryCapableLists = toolName === "format" || toolName === "replace";
14720
+ for (const field of PATH_LIST_FIELDS) {
14721
+ appendTargetList(
14722
+ targets,
14723
+ toolInput[field],
14724
+ directoryCapableLists && field === "files",
14725
+ effectiveCwd
14754
14726
  );
14755
- targets.push(...roots.length > 0 ? roots : ["."]);
14756
- f = findDelete.exec(normalizedCommand);
14757
14727
  }
14758
- const shellWrapper = /\b(?:ba|z|k)?sh\s+-c\s+(['"])(.*?)\1/gi;
14759
- let w = shellWrapper.exec(normalizedCommand);
14760
- while (w !== null) {
14761
- if (w[2] && !tokenIsQuoted(w, w[0].split(/\s/)[0] ?? "")) {
14762
- targets.push(...destructiveTargetsAtDepth(w[2], depth + 1));
14763
- }
14764
- w = shellWrapper.exec(normalizedCommand);
14728
+ if (toolName === "install") {
14729
+ appendTarget(targets, effectiveCwd ?? invocationCwd ?? ".", "scope");
14765
14730
  }
14766
- let quote = null;
14767
- for (let index = 0; index < normalizedCommand.length; index += 1) {
14768
- const char = normalizedCommand[index];
14769
- if (char === "\n") {
14770
- quote = null;
14771
- continue;
14772
- }
14773
- if (isQuoteBoundary(normalizedCommand, index, quote)) {
14774
- quote = quote === char ? null : char === "'" ? "'" : '"';
14775
- continue;
14731
+ if (toolName === "git") {
14732
+ const command = toolInput["command"];
14733
+ if (command === "worktree" && (toolInput["worktreeAction"] === "add" || toolInput["worktreeAction"] === "remove")) {
14734
+ appendTarget(targets, toolInput["worktreePath"] ?? toolInput["worktree_path"], "file");
14776
14735
  }
14777
- if (quote !== null || char !== ">") continue;
14778
- const redirectsBothStreams = normalizedCommand[index + 1] === "&";
14779
- const overridesNoclobber = normalizedCommand[index + 1] === "|";
14780
- if (redirectsBothStreams || overridesNoclobber || normalizedCommand[index + 1] === ">") {
14781
- index += 1;
14736
+ }
14737
+ if (toolName === "scaffold") {
14738
+ const cwdValue = toolInput["cwd"];
14739
+ const cwd = typeof cwdValue === "string" && cwdValue.length > 0 ? cwdValue : invocationCwd ?? ".";
14740
+ const base = normalizePath2(cwd).replace(/\/$/, "");
14741
+ const name = typeof toolInput["name"] === "string" ? toolInput["name"] : "";
14742
+ const template = toolInput["template"];
14743
+ const filesByTemplate = {
14744
+ "npm-package": ["package.json", "tsconfig.json", "src/index.ts", "src/index.test.ts"],
14745
+ "cli-tool": ["package.json", "src/index.ts"],
14746
+ "react-component": name ? [`${name}.tsx`, `${name}.test.tsx`] : []
14747
+ };
14748
+ for (const file of typeof template === "string" ? filesByTemplate[template] ?? [] : []) {
14749
+ targets.push({ path: `${base}/${file}`, kind: "file" });
14782
14750
  }
14783
- while (normalizedCommand[index + 1] === " " || normalizedCommand[index + 1] === " ") {
14784
- index += 1;
14751
+ targets.push({ path: base, kind: "scope" });
14752
+ }
14753
+ if (isMoveToolName(toolName)) {
14754
+ for (const field of SOURCE_PATH_FIELDS) {
14755
+ appendTarget(targets, toolInput[field], "deletion-scope", effectiveCwd);
14785
14756
  }
14786
- const targetQuote = normalizedCommand[index + 1];
14787
- let end = index + 1;
14788
- let target;
14789
- if (targetQuote === "'" || targetQuote === '"') {
14790
- end = normalizedCommand.indexOf(targetQuote, index + 2);
14791
- if (end === -1) continue;
14792
- target = normalizedCommand.slice(index + 2, end);
14757
+ }
14758
+ const patch = toolInput["patch"];
14759
+ if (typeof patch === "string") {
14760
+ const patchTargets = pathsFromPatch(patch);
14761
+ const directory = toolInput["directory"];
14762
+ const baseValue = typeof directory === "string" && directory.length > 0 ? directory : invocationCwd;
14763
+ if (baseValue) {
14764
+ const base = normalizePath2(baseValue).replace(/\/$/, "");
14765
+ targets.push(
14766
+ ...patchTargets.map((target) => ({ path: `${base}/${target}`, kind: "file" }))
14767
+ );
14793
14768
  } else {
14794
- while (end < normalizedCommand.length && !/[\s;&|>()]/.test(normalizedCommand[end] ?? "")) {
14795
- end += 1;
14796
- }
14797
- target = normalizedCommand.slice(index + 1, end);
14798
- }
14799
- if (target && !(redirectsBothStreams && (/^\d+$/.test(target) || target === "-"))) {
14800
- targets.push(target);
14769
+ targets.push(...patchTargets.map((path) => ({ path, kind: "file" })));
14801
14770
  }
14802
- index = end;
14803
14771
  }
14804
- return [
14805
- ...new Set(
14806
- targets.map((target) => target.replace(/^['"]|['"]$/g, "")).filter((target) => target !== "/dev/null" && target.toLowerCase() !== "nul")
14807
- )
14808
- ];
14772
+ if (targets.length === 0) {
14773
+ const cwd = effectiveCwd ?? ".";
14774
+ targets.push({ path: normalizePath2(cwd).replace(/\/$/, "") || ".", kind: "scope" });
14775
+ }
14776
+ const seen = /* @__PURE__ */ new Set();
14777
+ return targets.map((target) => ({
14778
+ ...target,
14779
+ path: relativeToInvocationCwd(target.path, invocationCwd)
14780
+ })).filter((target) => {
14781
+ const key = `${target.kind}:${target.path}`;
14782
+ if (seen.has(key)) return false;
14783
+ seen.add(key);
14784
+ return true;
14785
+ });
14786
+ }
14787
+ function operationLabel(toolName) {
14788
+ if (toolName === "write") return "write";
14789
+ if (toolName === "edit") return "edit";
14790
+ return `write via "${toolName}"`;
14791
+ }
14792
+
14793
+ // src/path-guard/index.ts
14794
+ function createState() {
14795
+ return { invocations: 0, blocks: 0, warns: 0, lastBlock: null, hookUnregister: null };
14796
+ }
14797
+ var states = /* @__PURE__ */ new WeakMap();
14798
+ var latestState = createState();
14799
+ var DEFAULT_PROTECT = [
14800
+ "pnpm-lock.yaml",
14801
+ "package-lock.json",
14802
+ "yarn.lock",
14803
+ "bun.lockb",
14804
+ "Cargo.lock",
14805
+ "poetry.lock",
14806
+ ".env",
14807
+ ".env.*",
14808
+ ".git/**",
14809
+ "**/migrations/**"
14810
+ ];
14811
+ var DEFAULTS33 = {
14812
+ enabled: true,
14813
+ mode: "block",
14814
+ protect: DEFAULT_PROTECT,
14815
+ allow: []
14816
+ };
14817
+ function readConfig34(raw) {
14818
+ if (!raw || typeof raw !== "object") return { ...DEFAULTS33, protect: [...DEFAULT_PROTECT] };
14819
+ const r = raw;
14820
+ return {
14821
+ enabled: r["enabled"] !== false,
14822
+ mode: r["mode"] === "warn" ? "warn" : "block",
14823
+ protect: Array.isArray(r["protect"]) ? r["protect"].filter((p) => typeof p === "string" && p.length > 0) : [...DEFAULT_PROTECT],
14824
+ allow: Array.isArray(r["allow"]) ? r["allow"].filter((p) => typeof p === "string" && p.length > 0) : []
14825
+ };
14809
14826
  }
14810
14827
  var plugin39 = {
14811
14828
  name: "path-guard",
@@ -16033,7 +16050,29 @@ var CREDENTIAL_PATTERNS = [
16033
16050
  regex: /postgres(?:ql)?:\/\/(?:[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+|[^&\s?"'`#]{1,2048}\?(?:(?!(?:p|%70)(?:a|%61)(?:s|%73)(?:s|%73)(?:w|%77)(?:o|%6[fF])(?:r|%72)(?:d|%64)=)[^&\s#"'`]{1,256}&){0,32}(?:p|%70)(?:a|%61)(?:s|%73)(?:s|%73)(?:w|%77)(?:o|%6[fF])(?:r|%72)(?:d|%64)=[^&\s#"'`]{1,4096})/g
16034
16051
  },
16035
16052
  { type: "mysql_uri", regex: /mysql:\/\/[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+/g },
16036
- { type: "redis_uri", regex: /redis:\/\/[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+/g }
16053
+ { type: "redis_uri", regex: /redis:\/\/[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+/g },
16054
+ {
16055
+ // Credentials serialised as JSON, keyed rather than prefixed. Every other
16056
+ // entry in this table recognises a credential by its SHAPE (`ghp_`, `sk-`,
16057
+ // `eyJ`), which means a key with no distinctive prefix — Azure, a
16058
+ // self-hosted gateway, an Anthropic/Codex OAuth token — was invisible to
16059
+ // both surfaces. `prompt-firewall` guards the outgoing provider request, so
16060
+ // this is what stops a JSON-shaped tool result carrying such a value to a
16061
+ // third party.
16062
+ //
16063
+ // The key is matched in a LOOKBEHIND, so the reported match is the secret
16064
+ // itself and the pattern keeps zero capturing groups — `secret-scanner`
16065
+ // maps a combined-regex group index back to the pattern that fired, and an
16066
+ // inner group would shift that mapping (see the style note above, enforced
16067
+ // by credential-pattern-parity.test.ts).
16068
+ //
16069
+ // Mirrors `json_credential_key` in
16070
+ // `@wrongstack/core` → `src/security/secret-scrubber.ts`. Keep the two key
16071
+ // lists in step; the core side additionally preserves the key name when it
16072
+ // rewrites, which is why it is written with capture groups instead.
16073
+ type: "json_credential_key",
16074
+ regex: /(?<="[A-Za-z0-9_]{0,64}(?:apiKey|api_key|token|secret|password|authorization|bearer|private_key|access_token|refresh_token|client_secret)"\s{0,8}:\s{0,8}")[^"\\]{8,512}(?=")/gi
16075
+ }
16037
16076
  ];
16038
16077
  function cloneCredentialPatterns() {
16039
16078
  return CREDENTIAL_PATTERNS.map((p) => ({