@wrongstack/plugins 0.306.4 → 0.307.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.
- package/dist/gitignore-guard.js +1 -1
- package/dist/index.js +393 -376
- package/dist/notify-hub.js +13 -0
- package/dist/path-guard/glob.d.ts +32 -0
- package/dist/path-guard/index.d.ts +2 -44
- package/dist/path-guard/shell-targets.d.ts +40 -0
- package/dist/path-guard/tool-targets.d.ts +34 -0
- package/dist/path-guard.js +377 -373
- package/package.json +4 -4
package/dist/path-guard.js
CHANGED
|
@@ -1,37 +1,4 @@
|
|
|
1
|
-
// src/path-guard/
|
|
2
|
-
function createState() {
|
|
3
|
-
return { invocations: 0, blocks: 0, warns: 0, lastBlock: null, hookUnregister: null };
|
|
4
|
-
}
|
|
5
|
-
var states = /* @__PURE__ */ new WeakMap();
|
|
6
|
-
var latestState = createState();
|
|
7
|
-
var DEFAULT_PROTECT = [
|
|
8
|
-
"pnpm-lock.yaml",
|
|
9
|
-
"package-lock.json",
|
|
10
|
-
"yarn.lock",
|
|
11
|
-
"bun.lockb",
|
|
12
|
-
"Cargo.lock",
|
|
13
|
-
"poetry.lock",
|
|
14
|
-
".env",
|
|
15
|
-
".env.*",
|
|
16
|
-
".git/**",
|
|
17
|
-
"**/migrations/**"
|
|
18
|
-
];
|
|
19
|
-
var DEFAULTS = {
|
|
20
|
-
enabled: true,
|
|
21
|
-
mode: "block",
|
|
22
|
-
protect: DEFAULT_PROTECT,
|
|
23
|
-
allow: []
|
|
24
|
-
};
|
|
25
|
-
function readConfig(raw) {
|
|
26
|
-
if (!raw || typeof raw !== "object") return { ...DEFAULTS, protect: [...DEFAULT_PROTECT] };
|
|
27
|
-
const r = raw;
|
|
28
|
-
return {
|
|
29
|
-
enabled: r["enabled"] !== false,
|
|
30
|
-
mode: r["mode"] === "warn" ? "warn" : "block",
|
|
31
|
-
protect: Array.isArray(r["protect"]) ? r["protect"].filter((p) => typeof p === "string" && p.length > 0) : [...DEFAULT_PROTECT],
|
|
32
|
-
allow: Array.isArray(r["allow"]) ? r["allow"].filter((p) => typeof p === "string" && p.length > 0) : []
|
|
33
|
-
};
|
|
34
|
-
}
|
|
1
|
+
// src/path-guard/glob.ts
|
|
35
2
|
function compilePathGlob(pattern) {
|
|
36
3
|
const normalized = pattern.replace(/\\/g, "/");
|
|
37
4
|
let source = "";
|
|
@@ -197,335 +164,99 @@ function targetFullyAllowed(target, allowTexts, allowRes) {
|
|
|
197
164
|
return allowNormalized.endsWith("/**") && allowRe?.test(`${normalized}/.path-guard-probe`);
|
|
198
165
|
});
|
|
199
166
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
var
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
"
|
|
206
|
-
"
|
|
207
|
-
"
|
|
208
|
-
"
|
|
209
|
-
"
|
|
210
|
-
"
|
|
211
|
-
"
|
|
212
|
-
"install"
|
|
167
|
+
|
|
168
|
+
// src/path-guard/shell-targets.ts
|
|
169
|
+
var VALUE_TAKING_GIT_OPTIONS = /* @__PURE__ */ new Set([
|
|
170
|
+
"-C",
|
|
171
|
+
"-c",
|
|
172
|
+
"--git-dir",
|
|
173
|
+
"--work-tree",
|
|
174
|
+
"--namespace",
|
|
175
|
+
"--super-prefix",
|
|
176
|
+
"--exec-path",
|
|
177
|
+
"--config-env",
|
|
178
|
+
"--attr-source"
|
|
213
179
|
]);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
(field) => toolInput[field] !== void 0
|
|
232
|
-
);
|
|
233
|
-
if (filesystemWriter && hasKnownPath) return true;
|
|
180
|
+
var MAX_LAUNCHER_TOKENS = 256;
|
|
181
|
+
var MAX_LAUNCHER_LENGTH = 64 * 1024;
|
|
182
|
+
function boundedShellTokens(raw) {
|
|
183
|
+
const tokens = [];
|
|
184
|
+
let token = "";
|
|
185
|
+
let tokenStart = -1;
|
|
186
|
+
let quote = null;
|
|
187
|
+
const limit = Math.min(raw.length, MAX_LAUNCHER_LENGTH);
|
|
188
|
+
for (let index = 0; index < limit && tokens.length < MAX_LAUNCHER_TOKENS; index += 1) {
|
|
189
|
+
const char = raw[index] ?? "";
|
|
190
|
+
if (tokenStart < 0 && !/\s/.test(char)) tokenStart = index;
|
|
191
|
+
if (char === "\\" && quote !== "'" && index + 1 < limit) {
|
|
192
|
+
const escaped = raw[index + 1] ?? "";
|
|
193
|
+
const shellEscaped = quote === '"' ? /[$\x60"\\\r\n]/.test(escaped) : /[\s'"\\;&|()`]/.test(escaped);
|
|
194
|
+
token += shellEscaped ? escaped : `\\${escaped}`;
|
|
195
|
+
index += 1;
|
|
196
|
+
continue;
|
|
234
197
|
}
|
|
235
|
-
|
|
198
|
+
if (char === "'" || char === '"') {
|
|
199
|
+
if (quote === char) quote = null;
|
|
200
|
+
else if (quote === null) quote = char;
|
|
201
|
+
else token += char;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (quote === null && /\s/.test(char)) {
|
|
205
|
+
if (tokenStart >= 0) tokens.push({ value: token, start: tokenStart, end: index });
|
|
206
|
+
token = "";
|
|
207
|
+
tokenStart = -1;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
token += char;
|
|
236
211
|
}
|
|
237
|
-
if (
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
function executesShell(input) {
|
|
241
|
-
const toolName = input.toolName ?? "";
|
|
242
|
-
if (LEGACY_SHELL_TOOLS.has(toolName)) return true;
|
|
243
|
-
if (toolName === "git") return false;
|
|
244
|
-
const segmentedName = toolName.replace(/([a-z0-9])([A-Z])/g, "$1_$2");
|
|
245
|
-
const isCommandStringRunner = /(?:^|[_.-])(?:command|exec|execute|shell|terminal)(?:$|[_.-])/i.test(segmentedName);
|
|
246
|
-
const hasShellCapability = input.toolCapabilities?.some((capability) => SHELL_CAPABILITIES.has(capability)) ?? false;
|
|
247
|
-
const hasCommandString = typeof input.toolInput?.["command"] === "string";
|
|
248
|
-
return isCommandStringRunner && hasShellCapability || hasCommandString && input.toolMutating === true;
|
|
249
|
-
}
|
|
250
|
-
var PATH_FIELDS = [
|
|
251
|
-
"path",
|
|
252
|
-
"file_path",
|
|
253
|
-
"filePath",
|
|
254
|
-
"target",
|
|
255
|
-
"target_path",
|
|
256
|
-
"targetPath",
|
|
257
|
-
"destination",
|
|
258
|
-
"dest",
|
|
259
|
-
"output_path",
|
|
260
|
-
"outputPath",
|
|
261
|
-
"out",
|
|
262
|
-
"file"
|
|
263
|
-
];
|
|
264
|
-
var PATH_LIST_FIELDS = ["files", "paths"];
|
|
265
|
-
var SOURCE_PATH_FIELDS = [
|
|
266
|
-
"source",
|
|
267
|
-
"from",
|
|
268
|
-
"src",
|
|
269
|
-
"input_path",
|
|
270
|
-
"inputPath",
|
|
271
|
-
"old_path",
|
|
272
|
-
"oldPath"
|
|
273
|
-
];
|
|
274
|
-
var MOVE_TOOL_NAME = /(?:^|[_.-])(move|rename)(?:$|[_.-])/i;
|
|
275
|
-
var DIRECTORY_DELETE_TOOL_NAME = /(?:^|[_.-])(?:rmdir|(?:delete|remove)[_.-](?:dir|directory))(?:$|[_.-])/i;
|
|
276
|
-
function segmentedToolName(toolName) {
|
|
277
|
-
return toolName.replace(/([a-z0-9])([A-Z])/g, "$1_$2");
|
|
278
|
-
}
|
|
279
|
-
function isMoveToolName(toolName) {
|
|
280
|
-
return MOVE_TOOL_NAME.test(segmentedToolName(toolName));
|
|
281
|
-
}
|
|
282
|
-
function isDirectoryDeleteToolName(toolName) {
|
|
283
|
-
return DIRECTORY_DELETE_TOOL_NAME.test(segmentedToolName(toolName));
|
|
284
|
-
}
|
|
285
|
-
function cleanPatchPath(raw) {
|
|
286
|
-
const trimmed = raw.trim();
|
|
287
|
-
if (!trimmed || trimmed === "/dev/null") return null;
|
|
288
|
-
const quoted = /^"([^"]+)"(?:\s|$)/.exec(trimmed)?.[1];
|
|
289
|
-
const withoutTimestamp = quoted ?? trimmed.replace(/(?:\t|\s+)\d{4}-\d{2}-\d{2}(?:\s.*)?$/, "").trim();
|
|
290
|
-
if (!withoutTimestamp || withoutTimestamp === "/dev/null") return null;
|
|
291
|
-
return withoutTimestamp.replace(/^[ab]\//, "");
|
|
292
|
-
}
|
|
293
|
-
function pathsFromPatch(patch) {
|
|
294
|
-
const paths = [];
|
|
295
|
-
const lines = patch.split(/\r?\n/);
|
|
296
|
-
for (let index = 0; index + 2 < lines.length; index += 1) {
|
|
297
|
-
const oldHeader = lines[index];
|
|
298
|
-
const newHeader = lines[index + 1];
|
|
299
|
-
const hunkHeader = lines[index + 2];
|
|
300
|
-
if (!oldHeader?.startsWith("--- ") || !newHeader?.startsWith("+++ ")) continue;
|
|
301
|
-
if (!hunkHeader?.startsWith("@@ ")) continue;
|
|
302
|
-
const oldPath = cleanPatchPath(oldHeader.slice(4));
|
|
303
|
-
const newPath = cleanPatchPath(newHeader.slice(4));
|
|
304
|
-
if (oldPath) paths.push(oldPath);
|
|
305
|
-
if (newPath) paths.push(newPath);
|
|
212
|
+
if (tokenStart >= 0 && tokens.length < MAX_LAUNCHER_TOKENS) {
|
|
213
|
+
tokens.push({ value: token, start: tokenStart, end: limit });
|
|
306
214
|
}
|
|
307
|
-
return
|
|
308
|
-
}
|
|
309
|
-
function appendTarget(targets, value, kind, base) {
|
|
310
|
-
if (typeof value !== "string" || value.length === 0) return;
|
|
311
|
-
targets.push({
|
|
312
|
-
path: resolveTargetPath(value, base),
|
|
313
|
-
kind
|
|
314
|
-
});
|
|
215
|
+
return tokens;
|
|
315
216
|
}
|
|
316
|
-
function
|
|
317
|
-
|
|
318
|
-
targets.push({
|
|
319
|
-
path: resolveTargetPath(path, base),
|
|
320
|
-
kind: isUnresolvedPathScope(path) || isRootPathScope(path) || directoryCapable && isDirectoryAmbiguousPath(path) ? "scope" : "file"
|
|
321
|
-
});
|
|
322
|
-
};
|
|
323
|
-
if (typeof value === "string") {
|
|
324
|
-
const paths = value.split(",").map((item) => item.trim()).filter((path) => path.length > 0);
|
|
325
|
-
for (const path of paths) appendPath(path);
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
if (!Array.isArray(value)) return;
|
|
329
|
-
for (const item of value) {
|
|
330
|
-
if (typeof item !== "string") continue;
|
|
331
|
-
const path = item.trim();
|
|
332
|
-
if (path.length > 0) appendPath(path);
|
|
333
|
-
}
|
|
217
|
+
function shellTokens(raw) {
|
|
218
|
+
return boundedShellTokens(raw).map((token) => token.value);
|
|
334
219
|
}
|
|
335
|
-
function
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
220
|
+
function gitInvocationArguments(command) {
|
|
221
|
+
const argumentsList = [];
|
|
222
|
+
const quotedIndexes = new Uint8Array(command.length);
|
|
223
|
+
let activeQuote = null;
|
|
224
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
225
|
+
const char = command[index];
|
|
226
|
+
if (isQuoteBoundary(command, index, activeQuote)) {
|
|
227
|
+
quotedIndexes[index] = 1;
|
|
228
|
+
activeQuote = activeQuote === char ? null : char === "'" ? "'" : '"';
|
|
229
|
+
} else if (activeQuote !== null) {
|
|
230
|
+
quotedIndexes[index] = 1;
|
|
340
231
|
}
|
|
341
|
-
return command === "worktree" && toolInput["worktreeAction"] === "list";
|
|
342
232
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
directoryCapableLists && field === "files",
|
|
371
|
-
effectiveCwd
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
if (toolName === "install") {
|
|
375
|
-
appendTarget(targets, effectiveCwd ?? invocationCwd ?? ".", "scope");
|
|
376
|
-
}
|
|
377
|
-
if (toolName === "git") {
|
|
378
|
-
const command = toolInput["command"];
|
|
379
|
-
if (command === "worktree" && (toolInput["worktreeAction"] === "add" || toolInput["worktreeAction"] === "remove")) {
|
|
380
|
-
appendTarget(targets, toolInput["worktreePath"] ?? toolInput["worktree_path"], "file");
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
if (toolName === "scaffold") {
|
|
384
|
-
const cwdValue = toolInput["cwd"];
|
|
385
|
-
const cwd = typeof cwdValue === "string" && cwdValue.length > 0 ? cwdValue : invocationCwd ?? ".";
|
|
386
|
-
const base = normalizePath(cwd).replace(/\/$/, "");
|
|
387
|
-
const name = typeof toolInput["name"] === "string" ? toolInput["name"] : "";
|
|
388
|
-
const template = toolInput["template"];
|
|
389
|
-
const filesByTemplate = {
|
|
390
|
-
"npm-package": ["package.json", "tsconfig.json", "src/index.ts", "src/index.test.ts"],
|
|
391
|
-
"cli-tool": ["package.json", "src/index.ts"],
|
|
392
|
-
"react-component": name ? [`${name}.tsx`, `${name}.test.tsx`] : []
|
|
393
|
-
};
|
|
394
|
-
for (const file of typeof template === "string" ? filesByTemplate[template] ?? [] : []) {
|
|
395
|
-
targets.push({ path: `${base}/${file}`, kind: "file" });
|
|
396
|
-
}
|
|
397
|
-
targets.push({ path: base, kind: "scope" });
|
|
398
|
-
}
|
|
399
|
-
if (isMoveToolName(toolName)) {
|
|
400
|
-
for (const field of SOURCE_PATH_FIELDS) {
|
|
401
|
-
appendTarget(targets, toolInput[field], "deletion-scope", effectiveCwd);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
const patch = toolInput["patch"];
|
|
405
|
-
if (typeof patch === "string") {
|
|
406
|
-
const patchTargets = pathsFromPatch(patch);
|
|
407
|
-
const directory = toolInput["directory"];
|
|
408
|
-
const baseValue = typeof directory === "string" && directory.length > 0 ? directory : invocationCwd;
|
|
409
|
-
if (baseValue) {
|
|
410
|
-
const base = normalizePath(baseValue).replace(/\/$/, "");
|
|
411
|
-
targets.push(
|
|
412
|
-
...patchTargets.map((target) => ({ path: `${base}/${target}`, kind: "file" }))
|
|
413
|
-
);
|
|
414
|
-
} else {
|
|
415
|
-
targets.push(...patchTargets.map((path) => ({ path, kind: "file" })));
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
if (targets.length === 0) {
|
|
419
|
-
const cwd = effectiveCwd ?? ".";
|
|
420
|
-
targets.push({ path: normalizePath(cwd).replace(/\/$/, "") || ".", kind: "scope" });
|
|
421
|
-
}
|
|
422
|
-
const seen = /* @__PURE__ */ new Set();
|
|
423
|
-
return targets.map((target) => ({
|
|
424
|
-
...target,
|
|
425
|
-
path: relativeToInvocationCwd(target.path, invocationCwd)
|
|
426
|
-
})).filter((target) => {
|
|
427
|
-
const key = `${target.kind}:${target.path}`;
|
|
428
|
-
if (seen.has(key)) return false;
|
|
429
|
-
seen.add(key);
|
|
430
|
-
return true;
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
function operationLabel(toolName) {
|
|
434
|
-
if (toolName === "write") return "write";
|
|
435
|
-
if (toolName === "edit") return "edit";
|
|
436
|
-
return `write via "${toolName}"`;
|
|
437
|
-
}
|
|
438
|
-
var VALUE_TAKING_GIT_OPTIONS = /* @__PURE__ */ new Set([
|
|
439
|
-
"-C",
|
|
440
|
-
"-c",
|
|
441
|
-
"--git-dir",
|
|
442
|
-
"--work-tree",
|
|
443
|
-
"--namespace",
|
|
444
|
-
"--super-prefix",
|
|
445
|
-
"--exec-path",
|
|
446
|
-
"--config-env",
|
|
447
|
-
"--attr-source"
|
|
448
|
-
]);
|
|
449
|
-
var MAX_LAUNCHER_TOKENS = 256;
|
|
450
|
-
var MAX_LAUNCHER_LENGTH = 64 * 1024;
|
|
451
|
-
function boundedShellTokens(raw) {
|
|
452
|
-
const tokens = [];
|
|
453
|
-
let token = "";
|
|
454
|
-
let tokenStart = -1;
|
|
455
|
-
let quote = null;
|
|
456
|
-
const limit = Math.min(raw.length, MAX_LAUNCHER_LENGTH);
|
|
457
|
-
for (let index = 0; index < limit && tokens.length < MAX_LAUNCHER_TOKENS; index += 1) {
|
|
458
|
-
const char = raw[index] ?? "";
|
|
459
|
-
if (tokenStart < 0 && !/\s/.test(char)) tokenStart = index;
|
|
460
|
-
if (char === "\\" && quote !== "'" && index + 1 < limit) {
|
|
461
|
-
const escaped = raw[index + 1] ?? "";
|
|
462
|
-
const shellEscaped = quote === '"' ? /[$\x60"\\\r\n]/.test(escaped) : /[\s'"\\;&|()`]/.test(escaped);
|
|
463
|
-
token += shellEscaped ? escaped : `\\${escaped}`;
|
|
464
|
-
index += 1;
|
|
465
|
-
continue;
|
|
466
|
-
}
|
|
467
|
-
if (char === "'" || char === '"') {
|
|
468
|
-
if (quote === char) quote = null;
|
|
469
|
-
else if (quote === null) quote = char;
|
|
470
|
-
else token += char;
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
if (quote === null && /\s/.test(char)) {
|
|
474
|
-
if (tokenStart >= 0) tokens.push({ value: token, start: tokenStart, end: index });
|
|
475
|
-
token = "";
|
|
476
|
-
tokenStart = -1;
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
token += char;
|
|
480
|
-
}
|
|
481
|
-
if (tokenStart >= 0 && tokens.length < MAX_LAUNCHER_TOKENS) {
|
|
482
|
-
tokens.push({ value: token, start: tokenStart, end: limit });
|
|
483
|
-
}
|
|
484
|
-
return tokens;
|
|
485
|
-
}
|
|
486
|
-
function shellTokens(raw) {
|
|
487
|
-
return boundedShellTokens(raw).map((token) => token.value);
|
|
488
|
-
}
|
|
489
|
-
function gitInvocationArguments(command) {
|
|
490
|
-
const argumentsList = [];
|
|
491
|
-
const quotedIndexes = new Uint8Array(command.length);
|
|
492
|
-
let activeQuote = null;
|
|
493
|
-
for (let index = 0; index < command.length; index += 1) {
|
|
494
|
-
const char = command[index];
|
|
495
|
-
if (isQuoteBoundary(command, index, activeQuote)) {
|
|
496
|
-
quotedIndexes[index] = 1;
|
|
497
|
-
activeQuote = activeQuote === char ? null : char === "'" ? "'" : '"';
|
|
498
|
-
} else if (activeQuote !== null) {
|
|
499
|
-
quotedIndexes[index] = 1;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
const gitStart = /(?:^|[;&|\r\n]\s*|\(\s*|`\s*)git\b/gi;
|
|
503
|
-
let match = gitStart.exec(command);
|
|
504
|
-
while (match !== null) {
|
|
505
|
-
const gitOffset = match[0].toLowerCase().lastIndexOf("git");
|
|
506
|
-
if (gitOffset < 0 || quotedIndexes[match.index + gitOffset] === 1) {
|
|
507
|
-
match = gitStart.exec(command);
|
|
508
|
-
continue;
|
|
509
|
-
}
|
|
510
|
-
const start = gitStart.lastIndex;
|
|
511
|
-
let quote = null;
|
|
512
|
-
let end = start;
|
|
513
|
-
for (; end < command.length; end += 1) {
|
|
514
|
-
const char = command[end] ?? "";
|
|
515
|
-
if (char === "\\" && quote !== "'") {
|
|
516
|
-
end += 1;
|
|
517
|
-
continue;
|
|
518
|
-
}
|
|
519
|
-
if (char === "'" || char === '"') {
|
|
520
|
-
if (quote === char) quote = null;
|
|
521
|
-
else if (quote === null) quote = char;
|
|
522
|
-
continue;
|
|
523
|
-
}
|
|
524
|
-
if (quote === null && /[;&|)`\r\n]/.test(char)) break;
|
|
525
|
-
}
|
|
526
|
-
argumentsList.push(command.slice(start, end));
|
|
527
|
-
gitStart.lastIndex = Math.max(end, start);
|
|
528
|
-
match = gitStart.exec(command);
|
|
233
|
+
const gitStart = /(?:^|[;&|\r\n]\s*|\(\s*|`\s*)git\b/gi;
|
|
234
|
+
let match = gitStart.exec(command);
|
|
235
|
+
while (match !== null) {
|
|
236
|
+
const gitOffset = match[0].toLowerCase().lastIndexOf("git");
|
|
237
|
+
if (gitOffset < 0 || quotedIndexes[match.index + gitOffset] === 1) {
|
|
238
|
+
match = gitStart.exec(command);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const start = gitStart.lastIndex;
|
|
242
|
+
let quote = null;
|
|
243
|
+
let end = start;
|
|
244
|
+
for (; end < command.length; end += 1) {
|
|
245
|
+
const char = command[end] ?? "";
|
|
246
|
+
if (char === "\\" && quote !== "'") {
|
|
247
|
+
end += 1;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (char === "'" || char === '"') {
|
|
251
|
+
if (quote === char) quote = null;
|
|
252
|
+
else if (quote === null) quote = char;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (quote === null && /[;&|)`\r\n]/.test(char)) break;
|
|
256
|
+
}
|
|
257
|
+
argumentsList.push(command.slice(start, end));
|
|
258
|
+
gitStart.lastIndex = Math.max(end, start);
|
|
259
|
+
match = gitStart.exec(command);
|
|
529
260
|
}
|
|
530
261
|
return argumentsList;
|
|
531
262
|
}
|
|
@@ -598,21 +329,6 @@ function unwrapEnvSplitStringAtBoundary(command) {
|
|
|
598
329
|
}
|
|
599
330
|
return command;
|
|
600
331
|
}
|
|
601
|
-
function stripTransparentLaunchers(command) {
|
|
602
|
-
let stripped = command;
|
|
603
|
-
let previous;
|
|
604
|
-
do {
|
|
605
|
-
previous = stripped;
|
|
606
|
-
let beforeUnwrap;
|
|
607
|
-
do {
|
|
608
|
-
beforeUnwrap = stripped;
|
|
609
|
-
stripped = unwrapEnvSplitStringAtBoundary(stripped);
|
|
610
|
-
} while (stripped !== beforeUnwrap);
|
|
611
|
-
stripped = stripLauncherAtBoundary(stripped, "env", ENV_VALUE_TAKING);
|
|
612
|
-
stripped = stripLauncherAtBoundary(stripped, "sudo", SUDO_VALUE_TAKING);
|
|
613
|
-
} while (stripped !== previous);
|
|
614
|
-
return stripped;
|
|
615
|
-
}
|
|
616
332
|
var SUDO_VALUE_TAKING = /* @__PURE__ */ new Set([
|
|
617
333
|
"-u",
|
|
618
334
|
"-g",
|
|
@@ -654,6 +370,21 @@ var ENV_FLAG_OPTIONS = /* @__PURE__ */ new Set([
|
|
|
654
370
|
"--help",
|
|
655
371
|
"--version"
|
|
656
372
|
]);
|
|
373
|
+
function stripTransparentLaunchers(command) {
|
|
374
|
+
let stripped = command;
|
|
375
|
+
let previous;
|
|
376
|
+
do {
|
|
377
|
+
previous = stripped;
|
|
378
|
+
let beforeUnwrap;
|
|
379
|
+
do {
|
|
380
|
+
beforeUnwrap = stripped;
|
|
381
|
+
stripped = unwrapEnvSplitStringAtBoundary(stripped);
|
|
382
|
+
} while (stripped !== beforeUnwrap);
|
|
383
|
+
stripped = stripLauncherAtBoundary(stripped, "env", ENV_VALUE_TAKING);
|
|
384
|
+
stripped = stripLauncherAtBoundary(stripped, "sudo", SUDO_VALUE_TAKING);
|
|
385
|
+
} while (stripped !== previous);
|
|
386
|
+
return stripped;
|
|
387
|
+
}
|
|
657
388
|
function firstUnquotedShellSeparator(raw, start, end) {
|
|
658
389
|
let quote = null;
|
|
659
390
|
for (let index = start; index < end; index += 1) {
|
|
@@ -790,9 +521,7 @@ function executableCommandSubstitutions(command) {
|
|
|
790
521
|
}
|
|
791
522
|
const commandSubstitution = char === "$" && command[index + 1] === "(";
|
|
792
523
|
const processSubstitution = (char === ">" || char === "<") && command[index + 1] === "(";
|
|
793
|
-
if (!commandSubstitution && !processSubstitution ||
|
|
794
|
-
// contents as a shell command substitution.
|
|
795
|
-
commandSubstitution && command[index + 2] === "(")
|
|
524
|
+
if (!commandSubstitution && !processSubstitution || commandSubstitution && command[index + 2] === "(")
|
|
796
525
|
continue;
|
|
797
526
|
let depth = 1;
|
|
798
527
|
let innerQuote = null;
|
|
@@ -1240,6 +969,281 @@ function destructiveTargetsAtDepth(command, depth) {
|
|
|
1240
969
|
)
|
|
1241
970
|
];
|
|
1242
971
|
}
|
|
972
|
+
|
|
973
|
+
// src/path-guard/tool-targets.ts
|
|
974
|
+
var WRITE_CAPABILITIES = /* @__PURE__ */ new Set(["fs.write", "fs.write.outside-project"]);
|
|
975
|
+
var DISK_MUTATING_CAPABILITIES = /* @__PURE__ */ new Set(["package.install"]);
|
|
976
|
+
var SHELL_CAPABILITIES = /* @__PURE__ */ new Set(["shell.arbitrary", "shell.restricted", "shell.exec"]);
|
|
977
|
+
var LEGACY_SHELL_TOOLS = /* @__PURE__ */ new Set(["bash", "shell", "exec"]);
|
|
978
|
+
var LEGACY_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
979
|
+
"write",
|
|
980
|
+
"edit",
|
|
981
|
+
"patch",
|
|
982
|
+
"scaffold",
|
|
983
|
+
"format",
|
|
984
|
+
"replace",
|
|
985
|
+
"design",
|
|
986
|
+
"install"
|
|
987
|
+
]);
|
|
988
|
+
var PATH_FIELDS = [
|
|
989
|
+
"path",
|
|
990
|
+
"file_path",
|
|
991
|
+
"filePath",
|
|
992
|
+
"target",
|
|
993
|
+
"target_path",
|
|
994
|
+
"targetPath",
|
|
995
|
+
"destination",
|
|
996
|
+
"dest",
|
|
997
|
+
"output_path",
|
|
998
|
+
"outputPath",
|
|
999
|
+
"out",
|
|
1000
|
+
"file"
|
|
1001
|
+
];
|
|
1002
|
+
var PATH_LIST_FIELDS = ["files", "paths"];
|
|
1003
|
+
var SOURCE_PATH_FIELDS = [
|
|
1004
|
+
"source",
|
|
1005
|
+
"from",
|
|
1006
|
+
"src",
|
|
1007
|
+
"input_path",
|
|
1008
|
+
"inputPath",
|
|
1009
|
+
"old_path",
|
|
1010
|
+
"oldPath"
|
|
1011
|
+
];
|
|
1012
|
+
var MOVE_TOOL_NAME = /(?:^|[_.-])(move|rename)(?:$|[_.-])/i;
|
|
1013
|
+
var DIRECTORY_DELETE_TOOL_NAME = /(?:^|[_.-])(?:rmdir|(?:delete|remove)[_.-](?:dir|directory))(?:$|[_.-])/i;
|
|
1014
|
+
function segmentedToolName(toolName) {
|
|
1015
|
+
return toolName.replace(/([a-z0-9])([A-Z])/g, "$1_$2");
|
|
1016
|
+
}
|
|
1017
|
+
function isMoveToolName(toolName) {
|
|
1018
|
+
return MOVE_TOOL_NAME.test(segmentedToolName(toolName));
|
|
1019
|
+
}
|
|
1020
|
+
function isDirectoryDeleteToolName(toolName) {
|
|
1021
|
+
return DIRECTORY_DELETE_TOOL_NAME.test(segmentedToolName(toolName));
|
|
1022
|
+
}
|
|
1023
|
+
function cleanPatchPath(raw) {
|
|
1024
|
+
const trimmed = raw.trim();
|
|
1025
|
+
if (!trimmed || trimmed === "/dev/null") return null;
|
|
1026
|
+
const quoted = /^"([^"]+)"(?:\s|$)/.exec(trimmed)?.[1];
|
|
1027
|
+
const withoutTimestamp = quoted ?? trimmed.replace(/(?:\t|\s+)\d{4}-\d{2}-\d{2}(?:\s.*)?$/, "").trim();
|
|
1028
|
+
if (!withoutTimestamp || withoutTimestamp === "/dev/null") return null;
|
|
1029
|
+
return withoutTimestamp.replace(/^[ab]\//, "");
|
|
1030
|
+
}
|
|
1031
|
+
function pathsFromPatch(patch) {
|
|
1032
|
+
const paths = [];
|
|
1033
|
+
const lines = patch.split(/\r?\n/);
|
|
1034
|
+
for (let index = 0; index + 2 < lines.length; index += 1) {
|
|
1035
|
+
const oldHeader = lines[index];
|
|
1036
|
+
const newHeader = lines[index + 1];
|
|
1037
|
+
const hunkHeader = lines[index + 2];
|
|
1038
|
+
if (!oldHeader?.startsWith("--- ") || !newHeader?.startsWith("+++ ")) continue;
|
|
1039
|
+
if (!hunkHeader?.startsWith("@@ ")) continue;
|
|
1040
|
+
const oldPath = cleanPatchPath(oldHeader.slice(4));
|
|
1041
|
+
const newPath = cleanPatchPath(newHeader.slice(4));
|
|
1042
|
+
if (oldPath) paths.push(oldPath);
|
|
1043
|
+
if (newPath) paths.push(newPath);
|
|
1044
|
+
}
|
|
1045
|
+
return paths;
|
|
1046
|
+
}
|
|
1047
|
+
function appendTarget(targets, value, kind, base) {
|
|
1048
|
+
if (typeof value !== "string" || value.length === 0) return;
|
|
1049
|
+
targets.push({
|
|
1050
|
+
path: resolveTargetPath(value, base),
|
|
1051
|
+
kind
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
function appendTargetList(targets, value, directoryCapable, base) {
|
|
1055
|
+
const appendPath = (path) => {
|
|
1056
|
+
targets.push({
|
|
1057
|
+
path: resolveTargetPath(path, base),
|
|
1058
|
+
kind: isUnresolvedPathScope(path) || isRootPathScope(path) || directoryCapable && isDirectoryAmbiguousPath(path) ? "scope" : "file"
|
|
1059
|
+
});
|
|
1060
|
+
};
|
|
1061
|
+
if (typeof value === "string") {
|
|
1062
|
+
const paths = value.split(",").map((item) => item.trim()).filter((path) => path.length > 0);
|
|
1063
|
+
for (const path of paths) appendPath(path);
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
if (!Array.isArray(value)) return;
|
|
1067
|
+
for (const item of value) {
|
|
1068
|
+
if (typeof item !== "string") continue;
|
|
1069
|
+
const path = item.trim();
|
|
1070
|
+
if (path.length > 0) appendPath(path);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
function isReadOnlyInvocation(toolName, toolInput) {
|
|
1074
|
+
if (toolName === "git") {
|
|
1075
|
+
const command = toolInput["command"];
|
|
1076
|
+
if (command === "status" || command === "log" || command === "diff" || command === "fetch" || command === "commit" || command === "branch" || command === "push") {
|
|
1077
|
+
return true;
|
|
1078
|
+
}
|
|
1079
|
+
return command === "worktree" && toolInput["worktreeAction"] === "list";
|
|
1080
|
+
}
|
|
1081
|
+
if (toolName === "design") {
|
|
1082
|
+
const action = toolInput["action"] ?? "list";
|
|
1083
|
+
return action === "list" || action === "foundations" || action === "verify";
|
|
1084
|
+
}
|
|
1085
|
+
if (toolName === "format") return toolInput["check"] === true;
|
|
1086
|
+
if (toolName === "patch" || toolName === "scaffold") return toolInput["dry_run"] === true;
|
|
1087
|
+
if (toolName === "replace") return toolInput["dry_run"] !== false;
|
|
1088
|
+
return false;
|
|
1089
|
+
}
|
|
1090
|
+
function writesToDisk(input) {
|
|
1091
|
+
const capabilities = input.toolCapabilities;
|
|
1092
|
+
const capabilitiesUndeclared = !capabilities || capabilities.length === 0;
|
|
1093
|
+
const hasWriteCap = capabilities?.some((capability) => WRITE_CAPABILITIES.has(capability)) ?? false;
|
|
1094
|
+
const hasDiskMutatingCap = capabilities?.some((capability) => DISK_MUTATING_CAPABILITIES.has(capability)) ?? false;
|
|
1095
|
+
const hasCommandString = typeof input.toolInput?.["command"] === "string";
|
|
1096
|
+
if (hasCommandString && executesShell(input) && !hasWriteCap && !hasDiskMutatingCap && !(capabilitiesUndeclared && LEGACY_WRITE_TOOLS.has(input.toolName ?? "")))
|
|
1097
|
+
return false;
|
|
1098
|
+
if (!capabilitiesUndeclared) {
|
|
1099
|
+
if (hasWriteCap || hasDiskMutatingCap) return true;
|
|
1100
|
+
if (capabilities.includes("mcp.proxy")) {
|
|
1101
|
+
const segmentedName = (input.toolName ?? "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9]+/gi, "_").toLowerCase();
|
|
1102
|
+
const filesystemWriter = /(?:^|_)filesystem(?:_|$)/.test(segmentedName) && /(?:^|_)(?:add|append|copy|create|delete|edit|mkdir|move|remove|rename|write)(?:_|$)/.test(
|
|
1103
|
+
segmentedName
|
|
1104
|
+
);
|
|
1105
|
+
const toolInput = input.toolInput ?? {};
|
|
1106
|
+
const hasKnownPath = [...PATH_FIELDS, ...PATH_LIST_FIELDS].some(
|
|
1107
|
+
(field) => toolInput[field] !== void 0
|
|
1108
|
+
);
|
|
1109
|
+
if (filesystemWriter && hasKnownPath) return true;
|
|
1110
|
+
}
|
|
1111
|
+
return false;
|
|
1112
|
+
}
|
|
1113
|
+
if (input.toolMutating !== void 0) return input.toolMutating;
|
|
1114
|
+
return LEGACY_WRITE_TOOLS.has(input.toolName ?? "");
|
|
1115
|
+
}
|
|
1116
|
+
function executesShell(input) {
|
|
1117
|
+
const toolName = input.toolName ?? "";
|
|
1118
|
+
if (LEGACY_SHELL_TOOLS.has(toolName)) return true;
|
|
1119
|
+
if (toolName === "git") return false;
|
|
1120
|
+
const segmentedName = toolName.replace(/([a-z0-9])([A-Z])/g, "$1_$2");
|
|
1121
|
+
const isCommandStringRunner = /(?:^|[_.-])(?:command|exec|execute|shell|terminal)(?:$|[_.-])/i.test(segmentedName);
|
|
1122
|
+
const hasShellCapability = input.toolCapabilities?.some((capability) => SHELL_CAPABILITIES.has(capability)) ?? false;
|
|
1123
|
+
const hasCommandString = typeof input.toolInput?.["command"] === "string";
|
|
1124
|
+
return isCommandStringRunner && hasShellCapability || hasCommandString && input.toolMutating === true;
|
|
1125
|
+
}
|
|
1126
|
+
function pathsFromToolInput(toolInput, toolName, invocationCwd) {
|
|
1127
|
+
const targets = [];
|
|
1128
|
+
const effectiveCwd = effectiveToolCwd(toolInput["cwd"], invocationCwd);
|
|
1129
|
+
const directoryDelete = isDirectoryDeleteToolName(toolName);
|
|
1130
|
+
for (const field of PATH_FIELDS) {
|
|
1131
|
+
const value = toolInput[field];
|
|
1132
|
+
appendTarget(
|
|
1133
|
+
targets,
|
|
1134
|
+
value,
|
|
1135
|
+
directoryDelete ? "deletion-scope" : typeof value === "string" && isUnresolvedPathScope(value) ? "scope" : "file",
|
|
1136
|
+
effectiveCwd
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
const directoryCapableLists = toolName === "format" || toolName === "replace";
|
|
1140
|
+
for (const field of PATH_LIST_FIELDS) {
|
|
1141
|
+
appendTargetList(
|
|
1142
|
+
targets,
|
|
1143
|
+
toolInput[field],
|
|
1144
|
+
directoryCapableLists && field === "files",
|
|
1145
|
+
effectiveCwd
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
if (toolName === "install") {
|
|
1149
|
+
appendTarget(targets, effectiveCwd ?? invocationCwd ?? ".", "scope");
|
|
1150
|
+
}
|
|
1151
|
+
if (toolName === "git") {
|
|
1152
|
+
const command = toolInput["command"];
|
|
1153
|
+
if (command === "worktree" && (toolInput["worktreeAction"] === "add" || toolInput["worktreeAction"] === "remove")) {
|
|
1154
|
+
appendTarget(targets, toolInput["worktreePath"] ?? toolInput["worktree_path"], "file");
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
if (toolName === "scaffold") {
|
|
1158
|
+
const cwdValue = toolInput["cwd"];
|
|
1159
|
+
const cwd = typeof cwdValue === "string" && cwdValue.length > 0 ? cwdValue : invocationCwd ?? ".";
|
|
1160
|
+
const base = normalizePath(cwd).replace(/\/$/, "");
|
|
1161
|
+
const name = typeof toolInput["name"] === "string" ? toolInput["name"] : "";
|
|
1162
|
+
const template = toolInput["template"];
|
|
1163
|
+
const filesByTemplate = {
|
|
1164
|
+
"npm-package": ["package.json", "tsconfig.json", "src/index.ts", "src/index.test.ts"],
|
|
1165
|
+
"cli-tool": ["package.json", "src/index.ts"],
|
|
1166
|
+
"react-component": name ? [`${name}.tsx`, `${name}.test.tsx`] : []
|
|
1167
|
+
};
|
|
1168
|
+
for (const file of typeof template === "string" ? filesByTemplate[template] ?? [] : []) {
|
|
1169
|
+
targets.push({ path: `${base}/${file}`, kind: "file" });
|
|
1170
|
+
}
|
|
1171
|
+
targets.push({ path: base, kind: "scope" });
|
|
1172
|
+
}
|
|
1173
|
+
if (isMoveToolName(toolName)) {
|
|
1174
|
+
for (const field of SOURCE_PATH_FIELDS) {
|
|
1175
|
+
appendTarget(targets, toolInput[field], "deletion-scope", effectiveCwd);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
const patch = toolInput["patch"];
|
|
1179
|
+
if (typeof patch === "string") {
|
|
1180
|
+
const patchTargets = pathsFromPatch(patch);
|
|
1181
|
+
const directory = toolInput["directory"];
|
|
1182
|
+
const baseValue = typeof directory === "string" && directory.length > 0 ? directory : invocationCwd;
|
|
1183
|
+
if (baseValue) {
|
|
1184
|
+
const base = normalizePath(baseValue).replace(/\/$/, "");
|
|
1185
|
+
targets.push(
|
|
1186
|
+
...patchTargets.map((target) => ({ path: `${base}/${target}`, kind: "file" }))
|
|
1187
|
+
);
|
|
1188
|
+
} else {
|
|
1189
|
+
targets.push(...patchTargets.map((path) => ({ path, kind: "file" })));
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (targets.length === 0) {
|
|
1193
|
+
const cwd = effectiveCwd ?? ".";
|
|
1194
|
+
targets.push({ path: normalizePath(cwd).replace(/\/$/, "") || ".", kind: "scope" });
|
|
1195
|
+
}
|
|
1196
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1197
|
+
return targets.map((target) => ({
|
|
1198
|
+
...target,
|
|
1199
|
+
path: relativeToInvocationCwd(target.path, invocationCwd)
|
|
1200
|
+
})).filter((target) => {
|
|
1201
|
+
const key = `${target.kind}:${target.path}`;
|
|
1202
|
+
if (seen.has(key)) return false;
|
|
1203
|
+
seen.add(key);
|
|
1204
|
+
return true;
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
function operationLabel(toolName) {
|
|
1208
|
+
if (toolName === "write") return "write";
|
|
1209
|
+
if (toolName === "edit") return "edit";
|
|
1210
|
+
return `write via "${toolName}"`;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// src/path-guard/index.ts
|
|
1214
|
+
function createState() {
|
|
1215
|
+
return { invocations: 0, blocks: 0, warns: 0, lastBlock: null, hookUnregister: null };
|
|
1216
|
+
}
|
|
1217
|
+
var states = /* @__PURE__ */ new WeakMap();
|
|
1218
|
+
var latestState = createState();
|
|
1219
|
+
var DEFAULT_PROTECT = [
|
|
1220
|
+
"pnpm-lock.yaml",
|
|
1221
|
+
"package-lock.json",
|
|
1222
|
+
"yarn.lock",
|
|
1223
|
+
"bun.lockb",
|
|
1224
|
+
"Cargo.lock",
|
|
1225
|
+
"poetry.lock",
|
|
1226
|
+
".env",
|
|
1227
|
+
".env.*",
|
|
1228
|
+
".git/**",
|
|
1229
|
+
"**/migrations/**"
|
|
1230
|
+
];
|
|
1231
|
+
var DEFAULTS = {
|
|
1232
|
+
enabled: true,
|
|
1233
|
+
mode: "block",
|
|
1234
|
+
protect: DEFAULT_PROTECT,
|
|
1235
|
+
allow: []
|
|
1236
|
+
};
|
|
1237
|
+
function readConfig(raw) {
|
|
1238
|
+
if (!raw || typeof raw !== "object") return { ...DEFAULTS, protect: [...DEFAULT_PROTECT] };
|
|
1239
|
+
const r = raw;
|
|
1240
|
+
return {
|
|
1241
|
+
enabled: r["enabled"] !== false,
|
|
1242
|
+
mode: r["mode"] === "warn" ? "warn" : "block",
|
|
1243
|
+
protect: Array.isArray(r["protect"]) ? r["protect"].filter((p) => typeof p === "string" && p.length > 0) : [...DEFAULT_PROTECT],
|
|
1244
|
+
allow: Array.isArray(r["allow"]) ? r["allow"].filter((p) => typeof p === "string" && p.length > 0) : []
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1243
1247
|
var plugin = {
|
|
1244
1248
|
name: "path-guard",
|
|
1245
1249
|
version: "0.1.0",
|