@hasna/hooks 0.11.4 → 0.11.5
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/bin/hooks-mcp.js +2 -2
- package/bin/index.js +23 -23
- package/bin/native-safety-entry.js +96 -8
- package/bin/serve.js +9 -9
- package/dist/index.js +12 -12
- package/dist/sdk/index.js +1 -1
- package/hooks/hook-trash-guard/src/hook.ts +40 -10
- package/hooks/hook-workspace-repos-guard/src/hook.ts +46 -0
- package/package.json +1 -1
package/bin/hooks-mcp.js
CHANGED
|
@@ -1419,7 +1419,7 @@ var init_redact = __esm(() => {
|
|
|
1419
1419
|
];
|
|
1420
1420
|
});
|
|
1421
1421
|
|
|
1422
|
-
//
|
|
1422
|
+
// ../../node_modules/.bun/@hasna+contracts@1.2.2+944b86d8024e79fa/node_modules/@hasna/contracts/dist/client/transport.js
|
|
1423
1423
|
import { isIP } from "net";
|
|
1424
1424
|
import { spawnSync } from "child_process";
|
|
1425
1425
|
import { closeSync as closeSync22, fstatSync as fstatSync22, openSync as openSync22, readFileSync as readFileSync22 } from "fs";
|
|
@@ -3342,7 +3342,7 @@ function writeHookSettingsPreimage(path, settings, before) {
|
|
|
3342
3342
|
// src/lib/claude-skills-coordination.ts
|
|
3343
3343
|
import { createHash as createHash6 } from "crypto";
|
|
3344
3344
|
|
|
3345
|
-
//
|
|
3345
|
+
// ../../node_modules/.bun/@hasna+skills@0.9.17+a00d3064559565bb/node_modules/@hasna/skills/dist/index.js
|
|
3346
3346
|
import { existsSync as existsSync10 } from "fs";
|
|
3347
3347
|
import { homedir as homedir5 } from "os";
|
|
3348
3348
|
import { join as join9, resolve as resolve3 } from "path";
|
package/bin/index.js
CHANGED
|
@@ -2520,7 +2520,7 @@ var init_codex_settings = __esm(() => {
|
|
|
2520
2520
|
snapshots = new WeakMap;
|
|
2521
2521
|
});
|
|
2522
2522
|
|
|
2523
|
-
//
|
|
2523
|
+
// ../../node_modules/.bun/@hasna+skills@0.9.17+a00d3064559565bb/node_modules/@hasna/skills/dist/index.js
|
|
2524
2524
|
import { existsSync as existsSync10 } from "fs";
|
|
2525
2525
|
import { homedir as homedir6 } from "os";
|
|
2526
2526
|
import { join as join10, resolve as resolve4 } from "path";
|
|
@@ -35495,7 +35495,7 @@ var init_codex_safety_trust = __esm(() => {
|
|
|
35495
35495
|
};
|
|
35496
35496
|
});
|
|
35497
35497
|
|
|
35498
|
-
//
|
|
35498
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/error.js
|
|
35499
35499
|
var require_error = __commonJS((exports) => {
|
|
35500
35500
|
class CommanderError extends Error {
|
|
35501
35501
|
constructor(exitCode, code2, message) {
|
|
@@ -35519,7 +35519,7 @@ var require_error = __commonJS((exports) => {
|
|
|
35519
35519
|
exports.InvalidArgumentError = InvalidArgumentError;
|
|
35520
35520
|
});
|
|
35521
35521
|
|
|
35522
|
-
//
|
|
35522
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/argument.js
|
|
35523
35523
|
var require_argument = __commonJS((exports) => {
|
|
35524
35524
|
var { InvalidArgumentError } = require_error();
|
|
35525
35525
|
|
|
@@ -35598,7 +35598,7 @@ var require_argument = __commonJS((exports) => {
|
|
|
35598
35598
|
exports.humanReadableArgName = humanReadableArgName;
|
|
35599
35599
|
});
|
|
35600
35600
|
|
|
35601
|
-
//
|
|
35601
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/help.js
|
|
35602
35602
|
var require_help = __commonJS((exports) => {
|
|
35603
35603
|
var { humanReadableArgName } = require_argument();
|
|
35604
35604
|
|
|
@@ -35847,7 +35847,7 @@ var require_help = __commonJS((exports) => {
|
|
|
35847
35847
|
exports.Help = Help;
|
|
35848
35848
|
});
|
|
35849
35849
|
|
|
35850
|
-
//
|
|
35850
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/option.js
|
|
35851
35851
|
var require_option = __commonJS((exports) => {
|
|
35852
35852
|
var { InvalidArgumentError } = require_error();
|
|
35853
35853
|
|
|
@@ -35998,7 +35998,7 @@ var require_option = __commonJS((exports) => {
|
|
|
35998
35998
|
exports.DualOptions = DualOptions;
|
|
35999
35999
|
});
|
|
36000
36000
|
|
|
36001
|
-
//
|
|
36001
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/suggestSimilar.js
|
|
36002
36002
|
var require_suggestSimilar = __commonJS((exports) => {
|
|
36003
36003
|
var maxDistance = 3;
|
|
36004
36004
|
function editDistance(a, b) {
|
|
@@ -36071,7 +36071,7 @@ var require_suggestSimilar = __commonJS((exports) => {
|
|
|
36071
36071
|
exports.suggestSimilar = suggestSimilar;
|
|
36072
36072
|
});
|
|
36073
36073
|
|
|
36074
|
-
//
|
|
36074
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/command.js
|
|
36075
36075
|
var require_command = __commonJS((exports) => {
|
|
36076
36076
|
var EventEmitter = __require("events").EventEmitter;
|
|
36077
36077
|
var childProcess = __require("child_process");
|
|
@@ -37314,7 +37314,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
37314
37314
|
exports.Command = Command;
|
|
37315
37315
|
});
|
|
37316
37316
|
|
|
37317
|
-
//
|
|
37317
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/index.js
|
|
37318
37318
|
var require_commander = __commonJS((exports) => {
|
|
37319
37319
|
var { Argument } = require_argument();
|
|
37320
37320
|
var { Command } = require_command();
|
|
@@ -37334,7 +37334,7 @@ var require_commander = __commonJS((exports) => {
|
|
|
37334
37334
|
exports.InvalidOptionArgumentError = InvalidArgumentError;
|
|
37335
37335
|
});
|
|
37336
37336
|
|
|
37337
|
-
//
|
|
37337
|
+
// ../../node_modules/.bun/cli-spinners@2.9.2/node_modules/cli-spinners/spinners.json
|
|
37338
37338
|
var require_spinners = __commonJS((exports, module) => {
|
|
37339
37339
|
module.exports = {
|
|
37340
37340
|
dots: {
|
|
@@ -38960,7 +38960,7 @@ var require_spinners = __commonJS((exports, module) => {
|
|
|
38960
38960
|
};
|
|
38961
38961
|
});
|
|
38962
38962
|
|
|
38963
|
-
//
|
|
38963
|
+
// ../../node_modules/.bun/cli-spinners@2.9.2/node_modules/cli-spinners/index.js
|
|
38964
38964
|
var require_cli_spinners = __commonJS((exports, module) => {
|
|
38965
38965
|
var spinners = Object.assign({}, require_spinners());
|
|
38966
38966
|
var spinnersList = Object.keys(spinners);
|
|
@@ -39144,7 +39144,7 @@ var init_profiles = __esm(() => {
|
|
|
39144
39144
|
init_app_home();
|
|
39145
39145
|
});
|
|
39146
39146
|
|
|
39147
|
-
//
|
|
39147
|
+
// ../../node_modules/.bun/@hasna+contracts@1.2.2+944b86d8024e79fa/node_modules/@hasna/contracts/dist/client/transport.js
|
|
39148
39148
|
import { isIP as isIP2 } from "net";
|
|
39149
39149
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
39150
39150
|
import { closeSync as closeSync22, fstatSync as fstatSync22, openSync as openSync22, readFileSync as readFileSync22 } from "fs";
|
|
@@ -43633,7 +43633,7 @@ async function verifyClaudeSafetyConfiguration(options = {}, dependencies = {})
|
|
|
43633
43633
|
// src/cli/index.tsx
|
|
43634
43634
|
init_codex_safety_trust();
|
|
43635
43635
|
|
|
43636
|
-
//
|
|
43636
|
+
// ../events/dist/commander.js
|
|
43637
43637
|
import { chmod, mkdir, readFile as readFile2, rename, writeFile } from "fs/promises";
|
|
43638
43638
|
import { Buffer as Buffer2 } from "buffer";
|
|
43639
43639
|
import { existsSync as existsSync22 } from "fs";
|
|
@@ -45289,7 +45289,7 @@ function replaySummary(events2, deliveries, nextCursor) {
|
|
|
45289
45289
|
// src/cli/index.tsx
|
|
45290
45290
|
import { render } from "ink";
|
|
45291
45291
|
|
|
45292
|
-
//
|
|
45292
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/esm.mjs
|
|
45293
45293
|
var import__ = __toESM(require_commander(), 1);
|
|
45294
45294
|
var {
|
|
45295
45295
|
program: program2,
|
|
@@ -45315,11 +45315,11 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
45315
45315
|
import { useState as useState7 } from "react";
|
|
45316
45316
|
import { Box as Box8, Text as Text10, useApp, useInput as useInput5 } from "ink";
|
|
45317
45317
|
|
|
45318
|
-
//
|
|
45318
|
+
// ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Indicator.js
|
|
45319
45319
|
import React from "react";
|
|
45320
45320
|
import { Box, Text } from "ink";
|
|
45321
45321
|
|
|
45322
|
-
//
|
|
45322
|
+
// ../../node_modules/.bun/is-unicode-supported@2.1.0/node_modules/is-unicode-supported/index.js
|
|
45323
45323
|
import process3 from "process";
|
|
45324
45324
|
function isUnicodeSupported() {
|
|
45325
45325
|
const { env } = process3;
|
|
@@ -45330,7 +45330,7 @@ function isUnicodeSupported() {
|
|
|
45330
45330
|
return Boolean(env.WT_SESSION) || Boolean(env.TERMINUS_SUBLIME) || env.ConEmuTask === "{cmd::Cmder}" || TERM_PROGRAM === "Terminus-Sublime" || TERM_PROGRAM === "vscode" || TERM === "xterm-256color" || TERM === "alacritty" || TERM === "rxvt-unicode" || TERM === "rxvt-unicode-256color" || env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
|
|
45331
45331
|
}
|
|
45332
45332
|
|
|
45333
|
-
//
|
|
45333
|
+
// ../../node_modules/.bun/figures@6.1.0/node_modules/figures/index.js
|
|
45334
45334
|
var common2 = {
|
|
45335
45335
|
circleQuestionMark: "(?)",
|
|
45336
45336
|
questionMarkPrefix: "(?)",
|
|
@@ -45606,23 +45606,23 @@ var figures = shouldUseMain ? mainSymbols : fallbackSymbols;
|
|
|
45606
45606
|
var figures_default = figures;
|
|
45607
45607
|
var replacements = Object.entries(specialMainSymbols);
|
|
45608
45608
|
|
|
45609
|
-
//
|
|
45609
|
+
// ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Indicator.js
|
|
45610
45610
|
function Indicator({ isSelected = false }) {
|
|
45611
45611
|
return React.createElement(Box, { marginRight: 1 }, isSelected ? React.createElement(Text, { color: "blue" }, figures_default.pointer) : React.createElement(Text, null, " "));
|
|
45612
45612
|
}
|
|
45613
45613
|
var Indicator_default = Indicator;
|
|
45614
|
-
//
|
|
45614
|
+
// ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Item.js
|
|
45615
45615
|
import * as React2 from "react";
|
|
45616
45616
|
import { Text as Text2 } from "ink";
|
|
45617
45617
|
function Item({ isSelected = false, label }) {
|
|
45618
45618
|
return React2.createElement(Text2, { color: isSelected ? "blue" : undefined }, label);
|
|
45619
45619
|
}
|
|
45620
45620
|
var Item_default = Item;
|
|
45621
|
-
//
|
|
45621
|
+
// ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/SelectInput.js
|
|
45622
45622
|
import { isDeepStrictEqual } from "util";
|
|
45623
45623
|
import React3, { useState, useEffect, useRef, useCallback } from "react";
|
|
45624
45624
|
|
|
45625
|
-
//
|
|
45625
|
+
// ../../node_modules/.bun/to-rotated@1.0.0/node_modules/to-rotated/index.js
|
|
45626
45626
|
function toRotated(array3, steps) {
|
|
45627
45627
|
if (!Array.isArray(array3)) {
|
|
45628
45628
|
throw new TypeError(`Expected an array, got \`${typeof array3}\`.`);
|
|
@@ -45644,7 +45644,7 @@ function toRotated(array3, steps) {
|
|
|
45644
45644
|
];
|
|
45645
45645
|
}
|
|
45646
45646
|
|
|
45647
|
-
//
|
|
45647
|
+
// ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/SelectInput.js
|
|
45648
45648
|
import { Box as Box2, useInput } from "ink";
|
|
45649
45649
|
function SelectInput({ items = [], isFocused = true, initialIndex = 0, indicatorComponent = Indicator_default, itemComponent = Item_default, limit: customLimit, onSelect, onHighlight }) {
|
|
45650
45650
|
const hasLimit = typeof customLimit === "number" && items.length > customLimit;
|
|
@@ -45940,7 +45940,7 @@ function HookSelect({
|
|
|
45940
45940
|
import { useState as useState4, useEffect as useEffect3 } from "react";
|
|
45941
45941
|
import { Box as Box6, Text as Text7, useInput as useInput4 } from "ink";
|
|
45942
45942
|
|
|
45943
|
-
//
|
|
45943
|
+
// ../../node_modules/.bun/ink-text-input@6.0.0+fa875fef9f949ebd/node_modules/ink-text-input/build/index.js
|
|
45944
45944
|
import React5, { useState as useState3, useEffect as useEffect2 } from "react";
|
|
45945
45945
|
import { Text as Text6, useInput as useInput3 } from "ink";
|
|
45946
45946
|
import chalk from "chalk";
|
|
@@ -46118,7 +46118,7 @@ function SearchView({
|
|
|
46118
46118
|
import { useState as useState6, useEffect as useEffect5 } from "react";
|
|
46119
46119
|
import { Box as Box7, Text as Text9 } from "ink";
|
|
46120
46120
|
|
|
46121
|
-
//
|
|
46121
|
+
// ../../node_modules/.bun/ink-spinner@5.0.0+fa875fef9f949ebd/node_modules/ink-spinner/build/index.js
|
|
46122
46122
|
var import_cli_spinners = __toESM(require_cli_spinners(), 1);
|
|
46123
46123
|
import React7, { useState as useState5, useEffect as useEffect4 } from "react";
|
|
46124
46124
|
import { Text as Text8 } from "ink";
|
|
@@ -292,7 +292,7 @@ var REFUSED_VERBS = {
|
|
|
292
292
|
unlink: "`unlink` is a delete that trash-guard does not rewrite. Re-run it as `rm -- <path>` and it is redirected into trash.",
|
|
293
293
|
shred: "`shred` destroys content irrecoverably and is never allowed. Re-run it as `rm -- <path>` and it is redirected into trash."
|
|
294
294
|
};
|
|
295
|
-
var OPAQUE_COMMANDS = new Set(["sh", "bash", "zsh", "dash", "ksh", "ash", "eval", "source", ".", "su", "runuser"]);
|
|
295
|
+
var OPAQUE_COMMANDS = new Set(["sh", "bash", "zsh", "dash", "ksh", "ash", "eval", "source", ".", "su", "runuser", "env"]);
|
|
296
296
|
var WRAPPERS = {
|
|
297
297
|
sudo: ["-u", "--user", "-g", "--group", "-p", "--prompt", "-C", "--chdir", "-h", "--host", "-r", "--role", "-t", "--type", "-U", "--other-user"],
|
|
298
298
|
doas: ["-u", "-C"],
|
|
@@ -306,7 +306,7 @@ var WRAPPERS = {
|
|
|
306
306
|
setsid: [],
|
|
307
307
|
command: [],
|
|
308
308
|
builtin: [],
|
|
309
|
-
exec: []
|
|
309
|
+
exec: ["-a"]
|
|
310
310
|
};
|
|
311
311
|
var WRAPPER_POSITIONALS = {
|
|
312
312
|
timeout: 1
|
|
@@ -318,6 +318,9 @@ var DELETE_VERB_WORD_RE = /(^|[^A-Za-z0-9_.-])(rm|rmdir|unlink|shred)(?![A-Za-z0
|
|
|
318
318
|
function mentionsDeleteVerb(text) {
|
|
319
319
|
return DELETE_VERB_WORD_RE.test(decodeWord(text));
|
|
320
320
|
}
|
|
321
|
+
function envSplitCommandText(words) {
|
|
322
|
+
return words.map((word) => word.replace(/^-[iv0]*S|^--split-string=/, " ")).join(" ");
|
|
323
|
+
}
|
|
321
324
|
var ASSIGNMENT_RE = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
322
325
|
function baseVerb(word) {
|
|
323
326
|
const slash = word.lastIndexOf("/");
|
|
@@ -340,8 +343,8 @@ function lexCommand(command) {
|
|
|
340
343
|
if (pendingRedirect)
|
|
341
344
|
redirects.add(index);
|
|
342
345
|
wordStart = -1;
|
|
346
|
+
pendingRedirect = false;
|
|
343
347
|
}
|
|
344
|
-
pendingRedirect = false;
|
|
345
348
|
};
|
|
346
349
|
const closeSegment = (end) => {
|
|
347
350
|
closeWord(end);
|
|
@@ -349,6 +352,7 @@ function lexCommand(command) {
|
|
|
349
352
|
segments.push({ words, redirects });
|
|
350
353
|
words = [];
|
|
351
354
|
redirects = new Set;
|
|
355
|
+
pendingRedirect = false;
|
|
352
356
|
};
|
|
353
357
|
const scanSingle = (from) => {
|
|
354
358
|
const end = command.indexOf("'", from + 1);
|
|
@@ -495,9 +499,13 @@ function lexCommand(command) {
|
|
|
495
499
|
continue;
|
|
496
500
|
}
|
|
497
501
|
if (ch === ">" || ch === "<") {
|
|
502
|
+
if (wordStart >= 0 && /^\d+$/.test(command.slice(wordStart, i)))
|
|
503
|
+
wordStart = -1;
|
|
498
504
|
closeWord(i);
|
|
499
505
|
pendingRedirect = true;
|
|
500
|
-
i
|
|
506
|
+
i++;
|
|
507
|
+
if (command[i] === ch || command[i] === "&" || ch === ">" && command[i] === "|" || ch === "<" && command[i] === ">")
|
|
508
|
+
i++;
|
|
501
509
|
continue;
|
|
502
510
|
}
|
|
503
511
|
if (ch === "'") {
|
|
@@ -626,6 +634,10 @@ function commandWordIndex(segment) {
|
|
|
626
634
|
let index = 0;
|
|
627
635
|
let guard = 0;
|
|
628
636
|
while (index < segment.words.length && guard++ < 32) {
|
|
637
|
+
if (segment.redirects.has(index)) {
|
|
638
|
+
index++;
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
629
641
|
const raw = segment.words[index].text;
|
|
630
642
|
const decoded = decodeWord(raw);
|
|
631
643
|
if (ASSIGNMENT_RE.test(decoded) && !decoded.startsWith("-")) {
|
|
@@ -636,10 +648,25 @@ function commandWordIndex(segment) {
|
|
|
636
648
|
index++;
|
|
637
649
|
continue;
|
|
638
650
|
}
|
|
639
|
-
const
|
|
651
|
+
const wrapper = baseVerb(decoded);
|
|
652
|
+
if (wrapper === "command") {
|
|
653
|
+
for (const word of segment.words.filter((_, at) => at > index && !segment.redirects.has(at))) {
|
|
654
|
+
const option = decodeWord(word.text);
|
|
655
|
+
if (option === "--" || !option.startsWith("-"))
|
|
656
|
+
break;
|
|
657
|
+
if (/^-[pvV]*[vV][pvV]*$/.test(option))
|
|
658
|
+
return -1;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
const wrapperValues = WRAPPERS[wrapper];
|
|
640
662
|
if (wrapperValues) {
|
|
663
|
+
const wrapperIndex = index;
|
|
641
664
|
index++;
|
|
642
665
|
while (index < segment.words.length) {
|
|
666
|
+
if (segment.redirects.has(index)) {
|
|
667
|
+
index++;
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
643
670
|
const option = decodeWord(segment.words[index].text);
|
|
644
671
|
if (!option.startsWith("-") || option === "-")
|
|
645
672
|
break;
|
|
@@ -647,11 +674,16 @@ function commandWordIndex(segment) {
|
|
|
647
674
|
index++;
|
|
648
675
|
break;
|
|
649
676
|
}
|
|
677
|
+
if (wrapper === "env" && (/^-[iv0]*S/.test(option) || /^--split-string(?:=|$)/.test(option)))
|
|
678
|
+
return wrapperIndex;
|
|
650
679
|
index++;
|
|
651
|
-
if (wrapperValues.includes(option))
|
|
680
|
+
if (wrapperValues.includes(option) || wrapper === "exec" && /^-[cl]*a$/.test(option)) {
|
|
681
|
+
while (segment.redirects.has(index))
|
|
682
|
+
index++;
|
|
652
683
|
index++;
|
|
684
|
+
}
|
|
653
685
|
}
|
|
654
|
-
let positionals = WRAPPER_POSITIONALS[
|
|
686
|
+
let positionals = WRAPPER_POSITIONALS[wrapper] ?? 0;
|
|
655
687
|
while (positionals > 0 && index < segment.words.length) {
|
|
656
688
|
const positional = decodeWord(segment.words[index].text);
|
|
657
689
|
if (positional.startsWith("-") && positional !== "-")
|
|
@@ -862,7 +894,8 @@ function scanCommand(command, options) {
|
|
|
862
894
|
});
|
|
863
895
|
continue;
|
|
864
896
|
}
|
|
865
|
-
|
|
897
|
+
const opaqueText = verb === "env" ? envSplitCommandText(segment.words.map((word) => decodeWord(word.text))) : segmentText(segment);
|
|
898
|
+
if (OPAQUE_COMMANDS.has(verb) && mentionsDeleteVerb(opaqueText)) {
|
|
866
899
|
result.refusals.push({
|
|
867
900
|
reason: `\`${verb}\` runs a command string this hook cannot rewrite in place. Run the delete directly as \`rm -- <path>\` (intercepted and redirected into trash), or as a script that uses \`trash guard\` itself.`
|
|
868
901
|
});
|
|
@@ -1305,6 +1338,61 @@ function shellWords(command) {
|
|
|
1305
1338
|
return words;
|
|
1306
1339
|
}
|
|
1307
1340
|
function segmentOperation(segment) {
|
|
1341
|
+
const trimmed = segment.trim();
|
|
1342
|
+
if (!trimmed)
|
|
1343
|
+
return "read";
|
|
1344
|
+
const parsed = lexCommand(trimmed);
|
|
1345
|
+
if (!parsed.trustworthy || parsed.substitutions.length || /\$\{[^}]*[|;()]|[<>]\(/.test(trimmed))
|
|
1346
|
+
return opaqueOperation(trimmed);
|
|
1347
|
+
const operations = parsed.segments.map((part) => {
|
|
1348
|
+
const index = commandWordIndex(part);
|
|
1349
|
+
if (index < 0)
|
|
1350
|
+
return "read";
|
|
1351
|
+
const words = part.words.filter((_, at) => at >= index && !part.redirects.has(at)).map((word) => decodeWord(word.text));
|
|
1352
|
+
const tool = words[0].split("/").pop();
|
|
1353
|
+
if (["rm", "rmdir", "unlink", "shred", "rmtree", "del"].includes(tool ?? ""))
|
|
1354
|
+
return "delete";
|
|
1355
|
+
if (tool === "trash") {
|
|
1356
|
+
let at = 1;
|
|
1357
|
+
while (at < words.length && words[at].startsWith("-")) {
|
|
1358
|
+
const flag = words[at++];
|
|
1359
|
+
if (["--help", "-h", "--version", "-V", "--identity"].includes(flag))
|
|
1360
|
+
return "read";
|
|
1361
|
+
if (["--agent", "--spool", "--files", "--info", "--config"].includes(flag))
|
|
1362
|
+
at++;
|
|
1363
|
+
else if (!["--json", "--local"].includes(flag))
|
|
1364
|
+
return "delete";
|
|
1365
|
+
}
|
|
1366
|
+
if (at === words.length || ["status", "doctor", "list", "info", "get", "pending", "station", "setup", "backup", "storage", "retrieval", "backup-storage", "backup-inspect", "backup-refresh", "help"].includes(words[at]))
|
|
1367
|
+
return "read";
|
|
1368
|
+
if (["restore", "restore-capsule"].includes(words[at]))
|
|
1369
|
+
return "write";
|
|
1370
|
+
return "delete";
|
|
1371
|
+
}
|
|
1372
|
+
if (tool === "git") {
|
|
1373
|
+
const sub = gitSubcommand(part, index);
|
|
1374
|
+
if (sub === "clean" || sub === "rm")
|
|
1375
|
+
return "delete";
|
|
1376
|
+
return sub === "clone" || sub === "init" ? "write" : "read";
|
|
1377
|
+
}
|
|
1378
|
+
if (tool === "env" && mentionsDeleteVerb(envSplitCommandText(words)))
|
|
1379
|
+
return "delete";
|
|
1380
|
+
if (["sh", "bash", "zsh", "dash", "ksh", "ash", "eval", "source", ".", "su", "runuser", "env", "xargs", "find", "busybox", "toybox"].includes(tool ?? "")) {
|
|
1381
|
+
return opaqueOperation(words.join(" "));
|
|
1382
|
+
}
|
|
1383
|
+
if (["mkdir", "mkfile", "touch", "mv", "cp", "ln", "tee", "install", "dd", "rsync", "scp", "truncate"].includes(tool ?? ""))
|
|
1384
|
+
return "write";
|
|
1385
|
+
if (tool === "curl" || tool === "wget")
|
|
1386
|
+
return WRITE_FLAGS.test(words.join(" ")) ? "write" : "read";
|
|
1387
|
+
if (tool === "sed" && words.slice(1).some((word) => /^-i|^--in-place(?:=|$)/.test(word)))
|
|
1388
|
+
return "write";
|
|
1389
|
+
if (["python", "python3", "node", "bun"].includes(tool ?? "") && words.some((word) => word === "-c" || word === "-e"))
|
|
1390
|
+
return "write";
|
|
1391
|
+
return "read";
|
|
1392
|
+
});
|
|
1393
|
+
return operations.includes("delete") ? "delete" : operations.includes("write") ? "write" : "read";
|
|
1394
|
+
}
|
|
1395
|
+
function opaqueOperation(segment) {
|
|
1308
1396
|
const trimmed = segment.trim();
|
|
1309
1397
|
if (!trimmed)
|
|
1310
1398
|
return "read";
|
package/bin/serve.js
CHANGED
|
@@ -676,7 +676,7 @@ function getHook(name) {
|
|
|
676
676
|
import { existsSync as existsSync2, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "fs";
|
|
677
677
|
import { basename, dirname, isAbsolute, join as join3, normalize, resolve as resolve2, sep } from "path";
|
|
678
678
|
|
|
679
|
-
//
|
|
679
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
680
680
|
var exports_external = {};
|
|
681
681
|
__export(exports_external, {
|
|
682
682
|
void: () => voidType,
|
|
@@ -788,7 +788,7 @@ __export(exports_external, {
|
|
|
788
788
|
BRAND: () => BRAND
|
|
789
789
|
});
|
|
790
790
|
|
|
791
|
-
//
|
|
791
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
792
792
|
var util;
|
|
793
793
|
(function(util2) {
|
|
794
794
|
util2.assertEqual = (_) => {};
|
|
@@ -919,7 +919,7 @@ var getParsedType = (data) => {
|
|
|
919
919
|
}
|
|
920
920
|
};
|
|
921
921
|
|
|
922
|
-
//
|
|
922
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
|
|
923
923
|
var ZodIssueCode = util.arrayToEnum([
|
|
924
924
|
"invalid_type",
|
|
925
925
|
"invalid_literal",
|
|
@@ -1038,7 +1038,7 @@ ZodError.create = (issues) => {
|
|
|
1038
1038
|
return error;
|
|
1039
1039
|
};
|
|
1040
1040
|
|
|
1041
|
-
//
|
|
1041
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
|
|
1042
1042
|
var errorMap = (issue, _ctx) => {
|
|
1043
1043
|
let message;
|
|
1044
1044
|
switch (issue.code) {
|
|
@@ -1141,7 +1141,7 @@ var errorMap = (issue, _ctx) => {
|
|
|
1141
1141
|
};
|
|
1142
1142
|
var en_default = errorMap;
|
|
1143
1143
|
|
|
1144
|
-
//
|
|
1144
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
|
|
1145
1145
|
var overrideErrorMap = en_default;
|
|
1146
1146
|
function setErrorMap(map) {
|
|
1147
1147
|
overrideErrorMap = map;
|
|
@@ -1149,7 +1149,7 @@ function setErrorMap(map) {
|
|
|
1149
1149
|
function getErrorMap() {
|
|
1150
1150
|
return overrideErrorMap;
|
|
1151
1151
|
}
|
|
1152
|
-
//
|
|
1152
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
1153
1153
|
var makeIssue = (params) => {
|
|
1154
1154
|
const { data, path, errorMaps, issueData } = params;
|
|
1155
1155
|
const fullPath = [...path, ...issueData.path || []];
|
|
@@ -1255,14 +1255,14 @@ var isAborted = (x) => x.status === "aborted";
|
|
|
1255
1255
|
var isDirty = (x) => x.status === "dirty";
|
|
1256
1256
|
var isValid = (x) => x.status === "valid";
|
|
1257
1257
|
var isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
|
|
1258
|
-
//
|
|
1258
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
|
|
1259
1259
|
var errorUtil;
|
|
1260
1260
|
(function(errorUtil2) {
|
|
1261
1261
|
errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {};
|
|
1262
1262
|
errorUtil2.toString = (message) => typeof message === "string" ? message : message?.message;
|
|
1263
1263
|
})(errorUtil || (errorUtil = {}));
|
|
1264
1264
|
|
|
1265
|
-
//
|
|
1265
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
|
|
1266
1266
|
class ParseInputLazyPath {
|
|
1267
1267
|
constructor(parent, value, path, key) {
|
|
1268
1268
|
this._cachedPath = [];
|
|
@@ -5362,7 +5362,7 @@ import { readFileSync as readFileSync5 } from "fs";
|
|
|
5362
5362
|
import { dirname as dirname4, join as join8 } from "path";
|
|
5363
5363
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
5364
5364
|
|
|
5365
|
-
//
|
|
5365
|
+
// ../../node_modules/.bun/@hasna+contracts@1.2.2+944b86d8024e79fa/node_modules/@hasna/contracts/dist/client/transport.js
|
|
5366
5366
|
import { spawnSync } from "child_process";
|
|
5367
5367
|
import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync22 } from "fs";
|
|
5368
5368
|
import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
|
package/dist/index.js
CHANGED
|
@@ -581,7 +581,7 @@ var init_redact = __esm(() => {
|
|
|
581
581
|
];
|
|
582
582
|
});
|
|
583
583
|
|
|
584
|
-
//
|
|
584
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
585
585
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
586
586
|
const t = typeof data;
|
|
587
587
|
switch (t) {
|
|
@@ -712,7 +712,7 @@ var init_util = __esm(() => {
|
|
|
712
712
|
]);
|
|
713
713
|
});
|
|
714
714
|
|
|
715
|
-
//
|
|
715
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
|
|
716
716
|
var ZodIssueCode, quotelessJson = (obj) => {
|
|
717
717
|
const json = JSON.stringify(obj, null, 2);
|
|
718
718
|
return json.replace(/"([^"]+)":/g, "$1:");
|
|
@@ -833,7 +833,7 @@ var init_ZodError = __esm(() => {
|
|
|
833
833
|
};
|
|
834
834
|
});
|
|
835
835
|
|
|
836
|
-
//
|
|
836
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
|
|
837
837
|
var errorMap = (issue, _ctx) => {
|
|
838
838
|
let message;
|
|
839
839
|
switch (issue.code) {
|
|
@@ -940,7 +940,7 @@ var init_en = __esm(() => {
|
|
|
940
940
|
en_default = errorMap;
|
|
941
941
|
});
|
|
942
942
|
|
|
943
|
-
//
|
|
943
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
|
|
944
944
|
function setErrorMap(map) {
|
|
945
945
|
overrideErrorMap = map;
|
|
946
946
|
}
|
|
@@ -953,7 +953,7 @@ var init_errors = __esm(() => {
|
|
|
953
953
|
overrideErrorMap = en_default;
|
|
954
954
|
});
|
|
955
955
|
|
|
956
|
-
//
|
|
956
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
957
957
|
function addIssueToContext(ctx, issueData) {
|
|
958
958
|
const overrideMap = getErrorMap();
|
|
959
959
|
const issue = makeIssue({
|
|
@@ -1058,10 +1058,10 @@ var init_parseUtil = __esm(() => {
|
|
|
1058
1058
|
});
|
|
1059
1059
|
});
|
|
1060
1060
|
|
|
1061
|
-
//
|
|
1061
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/typeAliases.js
|
|
1062
1062
|
var init_typeAliases = () => {};
|
|
1063
1063
|
|
|
1064
|
-
//
|
|
1064
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
|
|
1065
1065
|
var errorUtil;
|
|
1066
1066
|
var init_errorUtil = __esm(() => {
|
|
1067
1067
|
(function(errorUtil2) {
|
|
@@ -1070,7 +1070,7 @@ var init_errorUtil = __esm(() => {
|
|
|
1070
1070
|
})(errorUtil || (errorUtil = {}));
|
|
1071
1071
|
});
|
|
1072
1072
|
|
|
1073
|
-
//
|
|
1073
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
|
|
1074
1074
|
class ParseInputLazyPath {
|
|
1075
1075
|
constructor(parent, value, path, key) {
|
|
1076
1076
|
this._cachedPath = [];
|
|
@@ -4421,7 +4421,7 @@ var init_types = __esm(() => {
|
|
|
4421
4421
|
NEVER = INVALID;
|
|
4422
4422
|
});
|
|
4423
4423
|
|
|
4424
|
-
//
|
|
4424
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
4425
4425
|
var exports_external = {};
|
|
4426
4426
|
__export(exports_external, {
|
|
4427
4427
|
void: () => voidType,
|
|
@@ -4541,7 +4541,7 @@ var init_external = __esm(() => {
|
|
|
4541
4541
|
init_ZodError();
|
|
4542
4542
|
});
|
|
4543
4543
|
|
|
4544
|
-
//
|
|
4544
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/index.js
|
|
4545
4545
|
var init_zod = __esm(() => {
|
|
4546
4546
|
init_external();
|
|
4547
4547
|
init_external();
|
|
@@ -5101,7 +5101,7 @@ var init_profiles = __esm(() => {
|
|
|
5101
5101
|
init_app_home();
|
|
5102
5102
|
});
|
|
5103
5103
|
|
|
5104
|
-
//
|
|
5104
|
+
// ../../node_modules/.bun/@hasna+contracts@1.2.2+944b86d8024e79fa/node_modules/@hasna/contracts/dist/client/transport.js
|
|
5105
5105
|
import { isIP } from "net";
|
|
5106
5106
|
import { spawnSync } from "child_process";
|
|
5107
5107
|
import { closeSync as closeSync22, fstatSync as fstatSync22, openSync as openSync22, readFileSync as readFileSync22 } from "fs";
|
|
@@ -8106,7 +8106,7 @@ function writeHookSettingsPreimage(path, settings, before) {
|
|
|
8106
8106
|
// src/lib/claude-skills-coordination.ts
|
|
8107
8107
|
import { createHash as createHash6 } from "crypto";
|
|
8108
8108
|
|
|
8109
|
-
//
|
|
8109
|
+
// ../../node_modules/.bun/@hasna+skills@0.9.17+a00d3064559565bb/node_modules/@hasna/skills/dist/index.js
|
|
8110
8110
|
import { existsSync as existsSync10 } from "fs";
|
|
8111
8111
|
import { homedir as homedir5 } from "os";
|
|
8112
8112
|
import { join as join9, resolve as resolve3 } from "path";
|
package/dist/sdk/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
//
|
|
2
|
+
// ../../node_modules/.bun/@hasna+contracts@1.2.2+944b86d8024e79fa/node_modules/@hasna/contracts/dist/client/transport.js
|
|
3
3
|
import { isIP } from "net";
|
|
4
4
|
import { spawnSync } from "child_process";
|
|
5
5
|
import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync2 } from "fs";
|
|
@@ -103,7 +103,7 @@ const REFUSED_VERBS: Record<string, string> = {
|
|
|
103
103
|
* place. When one of these is in command position and the command mentions a
|
|
104
104
|
* delete verb, the command is refused rather than guessed at.
|
|
105
105
|
*/
|
|
106
|
-
const OPAQUE_COMMANDS = new Set(["sh", "bash", "zsh", "dash", "ksh", "ash", "eval", "source", ".", "su", "runuser"]);
|
|
106
|
+
const OPAQUE_COMMANDS = new Set(["sh", "bash", "zsh", "dash", "ksh", "ash", "eval", "source", ".", "su", "runuser", "env"]);
|
|
107
107
|
|
|
108
108
|
/** Wrapper commands whose real command follows them (with their value-taking options). */
|
|
109
109
|
const WRAPPERS: Record<string, string[]> = {
|
|
@@ -119,7 +119,7 @@ const WRAPPERS: Record<string, string[]> = {
|
|
|
119
119
|
setsid: [],
|
|
120
120
|
command: [],
|
|
121
121
|
builtin: [],
|
|
122
|
-
exec: [],
|
|
122
|
+
exec: ["-a"],
|
|
123
123
|
};
|
|
124
124
|
|
|
125
125
|
/**
|
|
@@ -156,6 +156,12 @@ export function mentionsDeleteVerb(text: string): boolean {
|
|
|
156
156
|
return DELETE_VERB_WORD_RE.test(decodeWord(text));
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/** env's split-string value may be attached to its option. This text is used
|
|
160
|
+
* only to refuse an opaque delete, never as executable text or a rewrite. */
|
|
161
|
+
export function envSplitCommandText(words: string[]): string {
|
|
162
|
+
return words.map((word) => word.replace(/^-[iv0]*S|^--split-string=/, " ")).join(" ");
|
|
163
|
+
}
|
|
164
|
+
|
|
159
165
|
const ASSIGNMENT_RE = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
160
166
|
|
|
161
167
|
/**
|
|
@@ -238,8 +244,8 @@ export function lexCommand(command: string): LexResult {
|
|
|
238
244
|
words.push({ text: command.slice(wordStart, end), start: wordStart, end });
|
|
239
245
|
if (pendingRedirect) redirects.add(index);
|
|
240
246
|
wordStart = -1;
|
|
247
|
+
pendingRedirect = false;
|
|
241
248
|
}
|
|
242
|
-
pendingRedirect = false;
|
|
243
249
|
};
|
|
244
250
|
|
|
245
251
|
const closeSegment = (end: number): void => {
|
|
@@ -247,6 +253,7 @@ export function lexCommand(command: string): LexResult {
|
|
|
247
253
|
if (words.length > 0) segments.push({ words, redirects });
|
|
248
254
|
words = [];
|
|
249
255
|
redirects = new Set<number>();
|
|
256
|
+
pendingRedirect = false;
|
|
250
257
|
};
|
|
251
258
|
|
|
252
259
|
const scanSingle = (from: number): number => {
|
|
@@ -390,9 +397,13 @@ export function lexCommand(command: string): LexResult {
|
|
|
390
397
|
continue;
|
|
391
398
|
}
|
|
392
399
|
if (ch === ">" || ch === "<") {
|
|
400
|
+
// An unquoted adjacent descriptor number belongs to the redirect, not
|
|
401
|
+
// the command. Its following operand is also never an executable.
|
|
402
|
+
if (wordStart >= 0 && /^\d+$/.test(command.slice(wordStart, i))) wordStart = -1;
|
|
393
403
|
closeWord(i);
|
|
394
404
|
pendingRedirect = true;
|
|
395
|
-
i
|
|
405
|
+
i++;
|
|
406
|
+
if (command[i] === ch || command[i] === "&" || (ch === ">" && command[i] === "|") || (ch === "<" && command[i] === ">")) i++;
|
|
396
407
|
continue;
|
|
397
408
|
}
|
|
398
409
|
if (ch === "'") {
|
|
@@ -521,10 +532,11 @@ export function decodeWord(text: string): string {
|
|
|
521
532
|
* command wrappers — NOT any word that happens to read like a verb, so
|
|
522
533
|
* `printf '%s\n' rm` is not a delete.
|
|
523
534
|
*/
|
|
524
|
-
function commandWordIndex(segment: Segment): number {
|
|
535
|
+
export function commandWordIndex(segment: Segment): number {
|
|
525
536
|
let index = 0;
|
|
526
537
|
let guard = 0;
|
|
527
538
|
while (index < segment.words.length && guard++ < 32) {
|
|
539
|
+
if (segment.redirects.has(index)) { index++; continue; }
|
|
528
540
|
const raw = segment.words[index].text;
|
|
529
541
|
const decoded = decodeWord(raw);
|
|
530
542
|
if (ASSIGNMENT_RE.test(decoded) && !decoded.startsWith("-")) {
|
|
@@ -535,20 +547,37 @@ function commandWordIndex(segment: Segment): number {
|
|
|
535
547
|
index++;
|
|
536
548
|
continue;
|
|
537
549
|
}
|
|
538
|
-
const
|
|
550
|
+
const wrapper = baseVerb(decoded);
|
|
551
|
+
// command -v/-V looks up every following name without executing one.
|
|
552
|
+
if (wrapper === "command") {
|
|
553
|
+
for (const word of segment.words.filter((_, at) => at > index && !segment.redirects.has(at))) {
|
|
554
|
+
const option = decodeWord(word.text);
|
|
555
|
+
if (option === "--" || !option.startsWith("-")) break;
|
|
556
|
+
if (/^-[pvV]*[vV][pvV]*$/.test(option)) return -1;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const wrapperValues = WRAPPERS[wrapper];
|
|
539
560
|
if (wrapperValues) {
|
|
561
|
+
const wrapperIndex = index;
|
|
540
562
|
index++;
|
|
541
563
|
while (index < segment.words.length) {
|
|
564
|
+
if (segment.redirects.has(index)) { index++; continue; }
|
|
542
565
|
const option = decodeWord(segment.words[index].text);
|
|
543
566
|
if (!option.startsWith("-") || option === "-") break;
|
|
544
567
|
if (option === "--") {
|
|
545
568
|
index++;
|
|
546
569
|
break;
|
|
547
570
|
}
|
|
571
|
+
// env -S reparses its value as a command string. Retain env in
|
|
572
|
+
// command position so the opaque-command refusal owns this form.
|
|
573
|
+
if (wrapper === "env" && (/^-[iv0]*S/.test(option) || /^--split-string(?:=|$)/.test(option))) return wrapperIndex;
|
|
548
574
|
index++;
|
|
549
|
-
if (wrapperValues.includes(option))
|
|
575
|
+
if (wrapperValues.includes(option) || (wrapper === "exec" && /^-[cl]*a$/.test(option))) {
|
|
576
|
+
while (segment.redirects.has(index)) index++;
|
|
577
|
+
index++;
|
|
578
|
+
}
|
|
550
579
|
}
|
|
551
|
-
let positionals = WRAPPER_POSITIONALS[
|
|
580
|
+
let positionals = WRAPPER_POSITIONALS[wrapper] ?? 0;
|
|
552
581
|
while (positionals > 0 && index < segment.words.length) {
|
|
553
582
|
const positional = decodeWord(segment.words[index].text);
|
|
554
583
|
if (positional.startsWith("-") && positional !== "-") break;
|
|
@@ -814,7 +843,8 @@ export function scanCommand(command: string, options: { cwd: string; home: strin
|
|
|
814
843
|
continue;
|
|
815
844
|
}
|
|
816
845
|
|
|
817
|
-
|
|
846
|
+
const opaqueText = verb === "env" ? envSplitCommandText(segment.words.map((word) => decodeWord(word.text))) : segmentText(segment);
|
|
847
|
+
if (OPAQUE_COMMANDS.has(verb) && mentionsDeleteVerb(opaqueText)) {
|
|
818
848
|
result.refusals.push({
|
|
819
849
|
reason: `\`${verb}\` runs a command string this hook cannot rewrite in place. Run the delete directly as \`rm -- <path>\` (intercepted and redirected into trash), or as a script that uses \`trash guard\` itself.`,
|
|
820
850
|
});
|
|
@@ -849,7 +879,7 @@ function segmentText(segment: Segment): string {
|
|
|
849
879
|
}
|
|
850
880
|
|
|
851
881
|
/** The git subcommand, skipping git's own value-taking global options. */
|
|
852
|
-
function gitSubcommand(segment: Segment, verbIndex: number): string | null {
|
|
882
|
+
export function gitSubcommand(segment: Segment, verbIndex: number): string | null {
|
|
853
883
|
let index = verbIndex + 1;
|
|
854
884
|
while (index < segment.words.length) {
|
|
855
885
|
const option = decodeWord(segment.words[index].text);
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync, statSync } from "fs";
|
|
53
53
|
import { homedir } from "os";
|
|
54
54
|
import { dirname, isAbsolute, join, normalize, relative, resolve, sep } from "path";
|
|
55
|
+
import { commandWordIndex, decodeWord, envSplitCommandText, gitSubcommand, lexCommand, mentionsDeleteVerb } from "../../hook-trash-guard/src/hook";
|
|
55
56
|
import {
|
|
56
57
|
getCommand,
|
|
57
58
|
readInput,
|
|
@@ -364,6 +365,51 @@ function shellWords(command: string): ShellWord[] {
|
|
|
364
365
|
function segmentOperation(segment: string): Operation {
|
|
365
366
|
const trimmed = segment.trim();
|
|
366
367
|
if (!trimmed) return "read";
|
|
368
|
+
const parsed = lexCommand(trimmed);
|
|
369
|
+
// Preserve conservative classification where the shell body is opaque.
|
|
370
|
+
if (!parsed.trustworthy || parsed.substitutions.length || /\$\{[^}]*[|;()]|[<>]\(/.test(trimmed)) return opaqueOperation(trimmed);
|
|
371
|
+
const operations = parsed.segments.map((part): Operation => {
|
|
372
|
+
const index = commandWordIndex(part);
|
|
373
|
+
if (index < 0) return "read";
|
|
374
|
+
const words = part.words.filter((_, at) => at >= index && !part.redirects.has(at)).map((word) => decodeWord(word.text));
|
|
375
|
+
const tool = words[0].split("/").pop();
|
|
376
|
+
if (["rm", "rmdir", "unlink", "shred", "rmtree", "del"].includes(tool ?? "")) return "delete";
|
|
377
|
+
if (tool === "trash") {
|
|
378
|
+
// Hasna metadata/setup verbs take identifiers, not paths to remove.
|
|
379
|
+
// Keep legacy positional Trash clients and capture verbs guarded.
|
|
380
|
+
let at = 1;
|
|
381
|
+
while (at < words.length && words[at].startsWith("-")) {
|
|
382
|
+
const flag = words[at++];
|
|
383
|
+
if (["--help", "-h", "--version", "-V", "--identity"].includes(flag)) return "read";
|
|
384
|
+
if (["--agent", "--spool", "--files", "--info", "--config"].includes(flag)) at++;
|
|
385
|
+
else if (!["--json", "--local"].includes(flag)) return "delete";
|
|
386
|
+
}
|
|
387
|
+
if (at === words.length || ["status", "doctor", "list", "info", "get", "pending", "station", "setup", "backup", "storage", "retrieval", "backup-storage", "backup-inspect", "backup-refresh", "help"].includes(words[at])) return "read";
|
|
388
|
+
if (["restore", "restore-capsule"].includes(words[at])) return "write";
|
|
389
|
+
return "delete";
|
|
390
|
+
}
|
|
391
|
+
if (tool === "git") {
|
|
392
|
+
const sub = gitSubcommand(part, index);
|
|
393
|
+
if (sub === "clean" || sub === "rm") return "delete";
|
|
394
|
+
return sub === "clone" || sub === "init" ? "write" : "read";
|
|
395
|
+
}
|
|
396
|
+
if (tool === "env" && mentionsDeleteVerb(envSplitCommandText(words))) return "delete";
|
|
397
|
+
if (["sh", "bash", "zsh", "dash", "ksh", "ash", "eval", "source", ".", "su", "runuser", "env", "xargs", "find", "busybox", "toybox"].includes(tool ?? "")) {
|
|
398
|
+
return opaqueOperation(words.join(" "));
|
|
399
|
+
}
|
|
400
|
+
if (["mkdir", "mkfile", "touch", "mv", "cp", "ln", "tee", "install", "dd", "rsync", "scp", "truncate"].includes(tool ?? "")) return "write";
|
|
401
|
+
if (tool === "curl" || tool === "wget") return WRITE_FLAGS.test(words.join(" ")) ? "write" : "read";
|
|
402
|
+
if (tool === "sed" && words.slice(1).some((word) => /^-i|^--in-place(?:=|$)/.test(word))) return "write";
|
|
403
|
+
if (["python", "python3", "node", "bun"].includes(tool ?? "") && words.some((word) => word === "-c" || word === "-e")) return "write";
|
|
404
|
+
return "read";
|
|
405
|
+
});
|
|
406
|
+
return operations.includes("delete") ? "delete" : operations.includes("write") ? "write" : "read";
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Retain the prior conservative behavior for embedded shell bodies.
|
|
410
|
+
function opaqueOperation(segment: string): Operation {
|
|
411
|
+
const trimmed = segment.trim();
|
|
412
|
+
if (!trimmed) return "read";
|
|
367
413
|
|
|
368
414
|
if (/(?:^|\s)\\?(?:[^\s;&|<>()]*\/)?(?:rm|rmdir|unlink|shred|trash|rmtree|del)(?:\s|$)/.test(trimmed)) return "delete";
|
|
369
415
|
if (/\bgit\b/.test(trimmed)) {
|
package/package.json
CHANGED