@hasna/hooks 0.10.3 → 0.10.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/README.md +55 -0
- package/bin/hooks-mcp.js +4 -1
- package/bin/index.js +589 -111
- package/bin/native-safety-entry.js +266 -70
- package/dist/index.js +4 -1
- package/dist/lib/native-safety.d.ts +1 -0
- package/dist/native-safety.d.ts +88 -0
- package/dist/native-safety.js +401 -0
- package/hooks/hook-trash-guard/src/binary.ts +158 -0
- package/hooks/hook-trash-guard/src/hook.ts +38 -43
- package/hooks/native-safety-entry.ts +10 -2
- package/package.json +6 -2
- package/scripts/validate-package.ts +12 -3
package/bin/index.js
CHANGED
|
@@ -4519,6 +4519,9 @@ function buildNativeSafetyCommand(binding) {
|
|
|
4519
4519
|
return prefix + [encoded, binding.runtime, binding.home, binding.path].map(quote).join(" ");
|
|
4520
4520
|
}
|
|
4521
4521
|
function nativeSafetyRegistration(command) {
|
|
4522
|
+
return nativeSafetyCommandBinding(command)?.name;
|
|
4523
|
+
}
|
|
4524
|
+
function nativeSafetyCommandBinding(command) {
|
|
4522
4525
|
if (!command.startsWith(prefix))
|
|
4523
4526
|
return;
|
|
4524
4527
|
const encoded = command.slice(prefix.length).match(/^'([A-Za-z0-9_-]+)' /)?.[1];
|
|
@@ -4526,7 +4529,7 @@ function nativeSafetyRegistration(command) {
|
|
|
4526
4529
|
return;
|
|
4527
4530
|
try {
|
|
4528
4531
|
const binding = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
4529
|
-
return buildNativeSafetyCommand(binding) === command ? binding
|
|
4532
|
+
return buildNativeSafetyCommand(binding) === command ? binding : undefined;
|
|
4530
4533
|
} catch {
|
|
4531
4534
|
return;
|
|
4532
4535
|
}
|
|
@@ -37385,25 +37388,25 @@ var init_redact = __esm(() => {
|
|
|
37385
37388
|
});
|
|
37386
37389
|
|
|
37387
37390
|
// src/lib/profiles.ts
|
|
37388
|
-
import { existsSync as
|
|
37389
|
-
import { join as
|
|
37391
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync10, readdirSync as readdirSync8, rmSync, cpSync as cpSync2 } from "fs";
|
|
37392
|
+
import { join as join19 } from "path";
|
|
37390
37393
|
function getProfilesDir() {
|
|
37391
|
-
const newDir =
|
|
37392
|
-
const oldDir =
|
|
37393
|
-
if (!
|
|
37394
|
-
|
|
37394
|
+
const newDir = join19(getEffectiveDataRoot(), "profiles");
|
|
37395
|
+
const oldDir = join19(getHomeDir(), ".hooks", "profiles");
|
|
37396
|
+
if (!existsSync15(newDir) && existsSync15(oldDir)) {
|
|
37397
|
+
mkdirSync11(newDir, { recursive: true });
|
|
37395
37398
|
cpSync2(oldDir, newDir, { recursive: true });
|
|
37396
37399
|
}
|
|
37397
37400
|
return newDir;
|
|
37398
37401
|
}
|
|
37399
37402
|
function ensureProfilesDir() {
|
|
37400
37403
|
const dir = getProfilesDir();
|
|
37401
|
-
if (!
|
|
37402
|
-
|
|
37404
|
+
if (!existsSync15(dir)) {
|
|
37405
|
+
mkdirSync11(dir, { recursive: true });
|
|
37403
37406
|
}
|
|
37404
37407
|
}
|
|
37405
37408
|
function profilePath(id) {
|
|
37406
|
-
return
|
|
37409
|
+
return join19(getProfilesDir(), `${id}.json`);
|
|
37407
37410
|
}
|
|
37408
37411
|
function shortUuid() {
|
|
37409
37412
|
return crypto.randomUUID().slice(0, 8);
|
|
@@ -37422,30 +37425,30 @@ function createProfile(input2) {
|
|
|
37422
37425
|
if (input2.name) {
|
|
37423
37426
|
profile.name = input2.name;
|
|
37424
37427
|
}
|
|
37425
|
-
|
|
37428
|
+
writeFileSync10(profilePath(id), JSON.stringify(profile, null, 2) + `
|
|
37426
37429
|
`);
|
|
37427
37430
|
return profile;
|
|
37428
37431
|
}
|
|
37429
37432
|
function getProfile(id) {
|
|
37430
37433
|
const path = profilePath(id);
|
|
37431
37434
|
try {
|
|
37432
|
-
if (!
|
|
37435
|
+
if (!existsSync15(path))
|
|
37433
37436
|
return null;
|
|
37434
|
-
return JSON.parse(
|
|
37437
|
+
return JSON.parse(readFileSync14(path, "utf-8"));
|
|
37435
37438
|
} catch {
|
|
37436
37439
|
return null;
|
|
37437
37440
|
}
|
|
37438
37441
|
}
|
|
37439
37442
|
function listProfiles() {
|
|
37440
37443
|
const dir = getProfilesDir();
|
|
37441
|
-
if (!
|
|
37444
|
+
if (!existsSync15(dir))
|
|
37442
37445
|
return [];
|
|
37443
37446
|
try {
|
|
37444
37447
|
const files = readdirSync8(dir).filter((f) => f.endsWith(".json"));
|
|
37445
37448
|
const profiles = [];
|
|
37446
37449
|
for (const file2 of files) {
|
|
37447
37450
|
try {
|
|
37448
|
-
const content =
|
|
37451
|
+
const content = readFileSync14(join19(dir, file2), "utf-8");
|
|
37449
37452
|
profiles.push(JSON.parse(content));
|
|
37450
37453
|
} catch {}
|
|
37451
37454
|
}
|
|
@@ -37459,7 +37462,7 @@ function touchProfile(id) {
|
|
|
37459
37462
|
if (!profile)
|
|
37460
37463
|
return;
|
|
37461
37464
|
profile.last_seen_at = new Date().toISOString();
|
|
37462
|
-
|
|
37465
|
+
writeFileSync10(profilePath(id), JSON.stringify(profile, null, 2) + `
|
|
37463
37466
|
`);
|
|
37464
37467
|
}
|
|
37465
37468
|
function exportProfiles() {
|
|
@@ -37475,11 +37478,11 @@ function importProfiles(profiles) {
|
|
|
37475
37478
|
continue;
|
|
37476
37479
|
}
|
|
37477
37480
|
const path = profilePath(profile.agent_id);
|
|
37478
|
-
if (
|
|
37481
|
+
if (existsSync15(path)) {
|
|
37479
37482
|
skipped++;
|
|
37480
37483
|
continue;
|
|
37481
37484
|
}
|
|
37482
|
-
|
|
37485
|
+
writeFileSync10(path, JSON.stringify(profile, null, 2) + `
|
|
37483
37486
|
`);
|
|
37484
37487
|
imported++;
|
|
37485
37488
|
}
|
|
@@ -37491,16 +37494,16 @@ var init_profiles = __esm(() => {
|
|
|
37491
37494
|
|
|
37492
37495
|
// ../../node_modules/.bun/@hasna+contracts@1.2.2+944b86d8024e79fa/node_modules/@hasna/contracts/dist/client/transport.js
|
|
37493
37496
|
import { isIP as isIP2 } from "net";
|
|
37494
|
-
import { spawnSync } from "child_process";
|
|
37497
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
37495
37498
|
import { closeSync as closeSync22, fstatSync as fstatSync22, openSync as openSync22, readFileSync as readFileSync22 } from "fs";
|
|
37496
37499
|
import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
|
|
37497
37500
|
import { hostname as osHostname } from "os";
|
|
37498
|
-
import { isAbsolute as
|
|
37501
|
+
import { isAbsolute as isAbsolute17, join as join22 } from "path";
|
|
37499
37502
|
import { createRequire as createRequire3 } from "module";
|
|
37500
37503
|
import { fileURLToPath as fileURLToPath22 } from "url";
|
|
37501
|
-
import { closeSync as
|
|
37504
|
+
import { closeSync as closeSync6, fstatSync as fstatSync5, lstatSync as lstatSync10, openSync as openSync6, readFileSync as readFileSync15, realpathSync as realpathSync9, statSync as statSync2 } from "fs";
|
|
37502
37505
|
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
37503
|
-
import { basename as basename5, dirname as
|
|
37506
|
+
import { basename as basename5, dirname as dirname19, join as join20, relative, sep as sep3 } from "path";
|
|
37504
37507
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
|
|
37505
37508
|
function envToken2(name) {
|
|
37506
37509
|
return name.toUpperCase().replace(/-/g, "_");
|
|
@@ -37574,16 +37577,16 @@ function o(e2, r2, t2) {
|
|
|
37574
37577
|
}
|
|
37575
37578
|
}
|
|
37576
37579
|
function entryPoint(directory2) {
|
|
37577
|
-
const root =
|
|
37578
|
-
const fd =
|
|
37580
|
+
const root = realpathSync9(directory2);
|
|
37581
|
+
const fd = openSync6(join20(root, "package.json"), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
|
|
37579
37582
|
let pkg;
|
|
37580
37583
|
try {
|
|
37581
|
-
const stat2 =
|
|
37584
|
+
const stat2 = fstatSync5(fd);
|
|
37582
37585
|
if (!stat2.isFile() || stat2.size > MAX_PACKAGE_BYTES3)
|
|
37583
37586
|
throw new Error("Invalid Secrets package metadata");
|
|
37584
|
-
pkg = JSON.parse(
|
|
37587
|
+
pkg = JSON.parse(readFileSync15(fd, "utf8"));
|
|
37585
37588
|
} finally {
|
|
37586
|
-
|
|
37589
|
+
closeSync6(fd);
|
|
37587
37590
|
}
|
|
37588
37591
|
if (!pkg || typeof pkg !== "object" || !("name" in pkg) || pkg.name !== PACKAGE) {
|
|
37589
37592
|
throw new Error("Invalid Secrets package identity");
|
|
@@ -37595,21 +37598,21 @@ function entryPoint(directory2) {
|
|
|
37595
37598
|
if (/[\\%?#\0]/.test(target) || parts.some((part) => !part || part === "." || part === ".." || part === "node_modules")) {
|
|
37596
37599
|
throw new Error("Invalid Secrets import export");
|
|
37597
37600
|
}
|
|
37598
|
-
const file2 =
|
|
37601
|
+
const file2 = realpathSync9(join20(root, ...parts));
|
|
37599
37602
|
const within = relative(root, file2);
|
|
37600
|
-
if (!within || within === ".." || within.startsWith(`..${sep3}`) || !
|
|
37603
|
+
if (!within || within === ".." || within.startsWith(`..${sep3}`) || !statSync2(file2).isFile()) {
|
|
37601
37604
|
throw new Error("Secrets import export escapes its package or is not a file");
|
|
37602
37605
|
}
|
|
37603
37606
|
return pathToFileURL(file2).href;
|
|
37604
37607
|
}
|
|
37605
37608
|
function resolveInstalledSecrets(parentUrl) {
|
|
37606
|
-
let directory2 =
|
|
37609
|
+
let directory2 = dirname19(fileURLToPath3(parentUrl));
|
|
37607
37610
|
for (;; ) {
|
|
37608
37611
|
if (basename5(directory2) !== "node_modules") {
|
|
37609
|
-
const candidate =
|
|
37612
|
+
const candidate = join20(directory2, "node_modules", PACKAGE);
|
|
37610
37613
|
let absent = false;
|
|
37611
37614
|
try {
|
|
37612
|
-
|
|
37615
|
+
lstatSync10(candidate);
|
|
37613
37616
|
} catch (error61) {
|
|
37614
37617
|
if (error61.code !== "ENOENT")
|
|
37615
37618
|
throw error61;
|
|
@@ -37618,7 +37621,7 @@ function resolveInstalledSecrets(parentUrl) {
|
|
|
37618
37621
|
if (!absent)
|
|
37619
37622
|
return entryPoint(candidate);
|
|
37620
37623
|
}
|
|
37621
|
-
const parent =
|
|
37624
|
+
const parent = dirname19(directory2);
|
|
37622
37625
|
if (parent === directory2)
|
|
37623
37626
|
throw new Error("Secrets SDK is not installed for this consumer");
|
|
37624
37627
|
directory2 = parent;
|
|
@@ -37630,7 +37633,7 @@ function homeDir(env) {
|
|
|
37630
37633
|
}
|
|
37631
37634
|
function absoluteOverride(env, key) {
|
|
37632
37635
|
const value = env[key]?.trim();
|
|
37633
|
-
return value &&
|
|
37636
|
+
return value && isAbsolute17(value) ? value : null;
|
|
37634
37637
|
}
|
|
37635
37638
|
function hasnaHomeDir(env) {
|
|
37636
37639
|
const override = absoluteOverride(env, HASNA_HOME_ENV_KEY);
|
|
@@ -37858,7 +37861,7 @@ function isAmbientEnvironment(env) {
|
|
|
37858
37861
|
return env === process.env || env[AMBIENT_ENVIRONMENT3] === true;
|
|
37859
37862
|
}
|
|
37860
37863
|
function defaultKeychainRunner(argv) {
|
|
37861
|
-
const result =
|
|
37864
|
+
const result = spawnSync2(KEYCHAIN_SECURITY_BIN, [...argv], {
|
|
37862
37865
|
encoding: "utf8",
|
|
37863
37866
|
stdio: ["ignore", "pipe", "pipe"],
|
|
37864
37867
|
timeout: KEYCHAIN_SPAWN_TIMEOUT_MS
|
|
@@ -38737,8 +38740,8 @@ var init_db_writer = __esm(() => {
|
|
|
38737
38740
|
});
|
|
38738
38741
|
|
|
38739
38742
|
// src/lib/hook-env.ts
|
|
38740
|
-
import { dirname as
|
|
38741
|
-
import { existsSync as
|
|
38743
|
+
import { dirname as dirname20, isAbsolute as isAbsolute18 } from "path";
|
|
38744
|
+
import { existsSync as existsSync16, realpathSync as realpathSync10, statSync as statSync4 } from "fs";
|
|
38742
38745
|
import { tmpdir } from "os";
|
|
38743
38746
|
function isDeniedEnvName(name) {
|
|
38744
38747
|
const upper = name.toUpperCase();
|
|
@@ -38757,7 +38760,7 @@ function isBashFunctionEnvName(name) {
|
|
|
38757
38760
|
return name.startsWith("BASH_FUNC_");
|
|
38758
38761
|
}
|
|
38759
38762
|
function isUnsafePathEntry(entry, home) {
|
|
38760
|
-
if (entry === "" || !
|
|
38763
|
+
if (entry === "" || !isAbsolute18(entry))
|
|
38761
38764
|
return true;
|
|
38762
38765
|
if (home && (entry === home || entry.startsWith(home + "/")))
|
|
38763
38766
|
return true;
|
|
@@ -38767,7 +38770,7 @@ function isUnsafePathEntry(entry, home) {
|
|
|
38767
38770
|
return true;
|
|
38768
38771
|
const canonical4 = (path) => {
|
|
38769
38772
|
try {
|
|
38770
|
-
return
|
|
38773
|
+
return realpathSync10(path);
|
|
38771
38774
|
} catch {
|
|
38772
38775
|
return path;
|
|
38773
38776
|
}
|
|
@@ -38779,16 +38782,16 @@ function isUnsafePathEntry(entry, home) {
|
|
|
38779
38782
|
return true;
|
|
38780
38783
|
}
|
|
38781
38784
|
try {
|
|
38782
|
-
if ((
|
|
38785
|
+
if ((statSync4(entry).mode & 2) !== 0)
|
|
38783
38786
|
return true;
|
|
38784
38787
|
} catch {}
|
|
38785
38788
|
return false;
|
|
38786
38789
|
}
|
|
38787
38790
|
function sanitizeHookPath(pathValue, home) {
|
|
38788
38791
|
const baseline = PATH_BASELINE.slice();
|
|
38789
|
-
if (process.platform === "darwin" &&
|
|
38792
|
+
if (process.platform === "darwin" && existsSync16("/opt/homebrew/bin"))
|
|
38790
38793
|
baseline.push("/opt/homebrew/bin");
|
|
38791
|
-
const bunDir =
|
|
38794
|
+
const bunDir = dirname20(process.execPath);
|
|
38792
38795
|
const seen = new Set;
|
|
38793
38796
|
const out = [];
|
|
38794
38797
|
for (const dir of baseline) {
|
|
@@ -38912,8 +38915,8 @@ __export(exports_run, {
|
|
|
38912
38915
|
HookTimeoutError: () => HookTimeoutError
|
|
38913
38916
|
});
|
|
38914
38917
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
38915
|
-
import { rmSync as rmSync2, writeFileSync as
|
|
38916
|
-
import { basename as basename9, dirname as
|
|
38918
|
+
import { rmSync as rmSync2, writeFileSync as writeFileSync11 } from "fs";
|
|
38919
|
+
import { basename as basename9, dirname as dirname21, isAbsolute as isAbsolute19, join as join24 } from "path";
|
|
38917
38920
|
function extensionOf(scriptPath) {
|
|
38918
38921
|
const base = basename9(scriptPath);
|
|
38919
38922
|
const dot = base.lastIndexOf(".");
|
|
@@ -38982,9 +38985,9 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
38982
38985
|
return chunks.join("");
|
|
38983
38986
|
}
|
|
38984
38987
|
async function executeVerifiedScript(options) {
|
|
38985
|
-
const scriptDir =
|
|
38988
|
+
const scriptDir = dirname21(options.scriptPath);
|
|
38986
38989
|
const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
|
|
38987
|
-
const tempPath =
|
|
38990
|
+
const tempPath = join24(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
|
|
38988
38991
|
let proc = null;
|
|
38989
38992
|
let timedOut = false;
|
|
38990
38993
|
const timeoutTimer = options.timeout ? setTimeout(() => {
|
|
@@ -38992,8 +38995,8 @@ async function executeVerifiedScript(options) {
|
|
|
38992
38995
|
killGroup(proc, { leaderAlive: proc !== null && proc.exitCode === null });
|
|
38993
38996
|
}, options.timeout) : null;
|
|
38994
38997
|
try {
|
|
38995
|
-
|
|
38996
|
-
proc = Bun.spawn([...interpreter.command,
|
|
38998
|
+
writeFileSync11(tempPath, options.content, { flag: "wx", mode: 384 });
|
|
38999
|
+
proc = Bun.spawn([...interpreter.command, isAbsolute19(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
|
|
38997
39000
|
stdin: new Response(options.stdin),
|
|
38998
39001
|
stdout: "pipe",
|
|
38999
39002
|
stderr: "pipe",
|
|
@@ -39075,15 +39078,15 @@ __export(exports_custom_install, {
|
|
|
39075
39078
|
installCustomSource: () => installCustomSource,
|
|
39076
39079
|
ensureCustomDir: () => ensureCustomDir
|
|
39077
39080
|
});
|
|
39078
|
-
import { existsSync as
|
|
39079
|
-
import { join as
|
|
39081
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync12, mkdtempSync, readFileSync as readFileSync17, rmSync as rmSync4 } from "fs";
|
|
39082
|
+
import { join as join27 } from "path";
|
|
39080
39083
|
import { tmpdir as tmpdir2 } from "os";
|
|
39081
39084
|
function manifestAt(dir) {
|
|
39082
|
-
const path =
|
|
39083
|
-
if (!
|
|
39085
|
+
const path = join27(dir, "manifest.json");
|
|
39086
|
+
if (!existsSync17(path))
|
|
39084
39087
|
return;
|
|
39085
39088
|
try {
|
|
39086
|
-
return parseManifest(
|
|
39089
|
+
return parseManifest(readFileSync17(path, "utf-8"));
|
|
39087
39090
|
} catch {
|
|
39088
39091
|
return;
|
|
39089
39092
|
}
|
|
@@ -39102,8 +39105,8 @@ function writeFromManifest(manifest, srcDir, source, kind) {
|
|
|
39102
39105
|
return { name, version: manifest.version, kind, source, dir, scriptPath };
|
|
39103
39106
|
}
|
|
39104
39107
|
async function cloneToTemp(gitUrl) {
|
|
39105
|
-
const tmp = mkdtempSync(
|
|
39106
|
-
const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl,
|
|
39108
|
+
const tmp = mkdtempSync(join27(tmpdir2(), "hooks-install-"));
|
|
39109
|
+
const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join27(tmp, "repo")], {
|
|
39107
39110
|
stdout: "pipe",
|
|
39108
39111
|
stderr: "pipe"
|
|
39109
39112
|
});
|
|
@@ -39116,7 +39119,7 @@ async function cloneToTemp(gitUrl) {
|
|
|
39116
39119
|
rmSync4(tmp, { recursive: true, force: true });
|
|
39117
39120
|
throw new Error(`git clone failed (${exitCode}): ${stderr.trim()}`);
|
|
39118
39121
|
}
|
|
39119
|
-
return
|
|
39122
|
+
return join27(tmp, "repo");
|
|
39120
39123
|
}
|
|
39121
39124
|
function isGitUrl(value) {
|
|
39122
39125
|
return value.startsWith("git@") || value.startsWith("ssh://") || value.startsWith("file://") || value.endsWith(".git");
|
|
@@ -39126,7 +39129,7 @@ function isCustomSource(value) {
|
|
|
39126
39129
|
return true;
|
|
39127
39130
|
if (value.startsWith("http://") || value.startsWith("https://"))
|
|
39128
39131
|
return true;
|
|
39129
|
-
return
|
|
39132
|
+
return existsSync17(value);
|
|
39130
39133
|
}
|
|
39131
39134
|
async function installCustomSource(source) {
|
|
39132
39135
|
if (isGitUrl(source)) {
|
|
@@ -39140,11 +39143,11 @@ async function installCustomSource(source) {
|
|
|
39140
39143
|
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
39141
39144
|
return installManifestUrl(source);
|
|
39142
39145
|
}
|
|
39143
|
-
if (!
|
|
39146
|
+
if (!existsSync17(source)) {
|
|
39144
39147
|
throw new Error(`No such local path: ${source}`);
|
|
39145
39148
|
}
|
|
39146
39149
|
if (source.endsWith("manifest.json")) {
|
|
39147
|
-
const manifest = parseManifest(
|
|
39150
|
+
const manifest = parseManifest(readFileSync17(source, "utf-8"));
|
|
39148
39151
|
return writeFromManifest(manifest, source.substring(0, source.lastIndexOf("/")), source, "local");
|
|
39149
39152
|
}
|
|
39150
39153
|
return copyManifestDir(source, source, "local");
|
|
@@ -39175,7 +39178,7 @@ async function installManifestUrl(url2) {
|
|
|
39175
39178
|
}
|
|
39176
39179
|
function ensureCustomDir() {
|
|
39177
39180
|
const dir = getCustomHooksDir();
|
|
39178
|
-
|
|
39181
|
+
mkdirSync12(dir, { recursive: true });
|
|
39179
39182
|
return dir;
|
|
39180
39183
|
}
|
|
39181
39184
|
var init_custom_install = __esm(() => {
|
|
@@ -39193,13 +39196,13 @@ __export(exports_sync, {
|
|
|
39193
39196
|
fetchPinnedHook: () => fetchPinnedHook,
|
|
39194
39197
|
commitSyncArtifacts: () => commitSyncArtifacts
|
|
39195
39198
|
});
|
|
39196
|
-
import { createHash as
|
|
39197
|
-
import { readFileSync as
|
|
39199
|
+
import { createHash as createHash12 } from "crypto";
|
|
39200
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
39198
39201
|
function sha256OfText(text3) {
|
|
39199
|
-
return
|
|
39202
|
+
return createHash12("sha256").update(text3).digest("hex");
|
|
39200
39203
|
}
|
|
39201
39204
|
function sha256OfPath(path) {
|
|
39202
|
-
return
|
|
39205
|
+
return createHash12("sha256").update(readFileSync19(path)).digest("hex");
|
|
39203
39206
|
}
|
|
39204
39207
|
function collectBundledCatalog() {
|
|
39205
39208
|
const out = [];
|
|
@@ -40354,13 +40357,13 @@ __export(exports_serve, {
|
|
|
40354
40357
|
SERVE_HOST: () => SERVE_HOST,
|
|
40355
40358
|
DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT
|
|
40356
40359
|
});
|
|
40357
|
-
import { readFileSync as
|
|
40358
|
-
import { dirname as
|
|
40360
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
40361
|
+
import { dirname as dirname22, join as join29 } from "path";
|
|
40359
40362
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
40360
40363
|
function packageVersion() {
|
|
40361
40364
|
try {
|
|
40362
|
-
const pkgPath =
|
|
40363
|
-
return JSON.parse(
|
|
40365
|
+
const pkgPath = join29(dirname22(fileURLToPath4(import.meta.url)), "..", "package.json");
|
|
40366
|
+
return JSON.parse(readFileSync20(pkgPath, "utf-8")).version || "0.0.0";
|
|
40364
40367
|
} catch {
|
|
40365
40368
|
return "0.0.0";
|
|
40366
40369
|
}
|
|
@@ -40660,17 +40663,17 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
|
40660
40663
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
40661
40664
|
import { z as z3 } from "zod";
|
|
40662
40665
|
import { createServer } from "http";
|
|
40663
|
-
import { existsSync as
|
|
40664
|
-
import { join as
|
|
40666
|
+
import { existsSync as existsSync18, readFileSync as readFileSync21 } from "fs";
|
|
40667
|
+
import { join as join30, dirname as dirname23 } from "path";
|
|
40665
40668
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
40666
40669
|
async function runVerifiedHook(name, stdin, timeoutMs) {
|
|
40667
40670
|
const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
|
|
40668
40671
|
const resolved = resolveHook2(name);
|
|
40669
40672
|
const hookScript = resolveScriptPath2(name);
|
|
40670
|
-
if (!resolved || !hookScript || !
|
|
40673
|
+
if (!resolved || !hookScript || !existsSync18(hookScript)) {
|
|
40671
40674
|
throw new Error(`Hook '${name}' not found`);
|
|
40672
40675
|
}
|
|
40673
|
-
const content =
|
|
40676
|
+
const content = readFileSync21(hookScript);
|
|
40674
40677
|
const sha4 = sha256Of(content);
|
|
40675
40678
|
const check2 = checkScriptHash(name, sha4);
|
|
40676
40679
|
if (!check2.ok) {
|
|
@@ -40875,7 +40878,7 @@ function createHooksServer() {
|
|
|
40875
40878
|
const settingsPath = getSettingsPath(scope);
|
|
40876
40879
|
const issues = [];
|
|
40877
40880
|
const healthy = [];
|
|
40878
|
-
const settingsExist =
|
|
40881
|
+
const settingsExist = existsSync18(settingsPath);
|
|
40879
40882
|
if (!settingsExist) {
|
|
40880
40883
|
issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
|
|
40881
40884
|
}
|
|
@@ -40885,7 +40888,7 @@ function createHooksServer() {
|
|
|
40885
40888
|
const meta3 = custom2 ? resolveHookMeta(name) : getHook(name);
|
|
40886
40889
|
let hookHealthy = true;
|
|
40887
40890
|
if (custom2) {
|
|
40888
|
-
if (!
|
|
40891
|
+
if (!existsSync18(custom2.scriptPath)) {
|
|
40889
40892
|
issues.push({ hook: name, issue: `Custom hook script missing: ${custom2.scriptPath}`, severity: "error" });
|
|
40890
40893
|
hookHealthy = false;
|
|
40891
40894
|
}
|
|
@@ -40895,14 +40898,14 @@ function createHooksServer() {
|
|
|
40895
40898
|
continue;
|
|
40896
40899
|
}
|
|
40897
40900
|
const hookDir = getHookPath(name);
|
|
40898
|
-
if (!
|
|
40901
|
+
if (!existsSync18(join30(hookDir, "src", "hook.ts"))) {
|
|
40899
40902
|
issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
|
|
40900
40903
|
hookHealthy = false;
|
|
40901
40904
|
}
|
|
40902
40905
|
}
|
|
40903
40906
|
if (meta3 && settingsExist) {
|
|
40904
40907
|
try {
|
|
40905
|
-
const settings = JSON.parse(
|
|
40908
|
+
const settings = JSON.parse(readFileSync21(settingsPath, "utf-8"));
|
|
40906
40909
|
if (!hookRegisteredInSettings(settings, name, meta3.event, meta3.matcher)) {
|
|
40907
40910
|
const eventName = meta3.event.split(":")[0];
|
|
40908
40911
|
issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
|
|
@@ -40939,10 +40942,10 @@ function createHooksServer() {
|
|
|
40939
40942
|
return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
|
|
40940
40943
|
}
|
|
40941
40944
|
const hookPath = getHookPath(name);
|
|
40942
|
-
const readmePath =
|
|
40945
|
+
const readmePath = join30(hookPath, "README.md");
|
|
40943
40946
|
let readme = "";
|
|
40944
|
-
if (
|
|
40945
|
-
readme =
|
|
40947
|
+
if (existsSync18(readmePath)) {
|
|
40948
|
+
readme = readFileSync21(readmePath, "utf-8");
|
|
40946
40949
|
}
|
|
40947
40950
|
if (verbose) {
|
|
40948
40951
|
return { content: [{ type: "text", text: JSON.stringify({ ...meta3, readme }) }] };
|
|
@@ -41125,7 +41128,7 @@ function createHooksServer() {
|
|
|
41125
41128
|
const ctx = {
|
|
41126
41129
|
scope,
|
|
41127
41130
|
settings_path: settingsPath,
|
|
41128
|
-
settings_exists:
|
|
41131
|
+
settings_exists: existsSync18(settingsPath),
|
|
41129
41132
|
registered_hooks: hooks,
|
|
41130
41133
|
hook_count: hooks.length,
|
|
41131
41134
|
healthy,
|
|
@@ -41271,8 +41274,8 @@ function createHooksServer() {
|
|
|
41271
41274
|
const settingsPath = getSettingsPath(scope);
|
|
41272
41275
|
let settings = {};
|
|
41273
41276
|
try {
|
|
41274
|
-
if (
|
|
41275
|
-
settings = JSON.parse(
|
|
41277
|
+
if (existsSync18(settingsPath))
|
|
41278
|
+
settings = JSON.parse(readFileSync21(settingsPath, "utf-8"));
|
|
41276
41279
|
} catch {}
|
|
41277
41280
|
if (!settings.hooks)
|
|
41278
41281
|
settings.hooks = {};
|
|
@@ -41294,8 +41297,8 @@ function createHooksServer() {
|
|
|
41294
41297
|
const settingsPath = getSettingsPath(scope);
|
|
41295
41298
|
let settings = {};
|
|
41296
41299
|
try {
|
|
41297
|
-
if (
|
|
41298
|
-
settings = JSON.parse(
|
|
41300
|
+
if (existsSync18(settingsPath))
|
|
41301
|
+
settings = JSON.parse(readFileSync21(settingsPath, "utf-8"));
|
|
41299
41302
|
} catch {}
|
|
41300
41303
|
if (settings.hooks?.__disabled) {
|
|
41301
41304
|
settings.hooks.__disabled = settings.hooks.__disabled.filter((n2) => n2 !== name);
|
|
@@ -41665,13 +41668,13 @@ var init_server = __esm(() => {
|
|
|
41665
41668
|
init_redact();
|
|
41666
41669
|
init_authority();
|
|
41667
41670
|
init_storage();
|
|
41668
|
-
__dirname3 =
|
|
41671
|
+
__dirname3 = dirname23(fileURLToPath5(import.meta.url));
|
|
41669
41672
|
pkg = { name: "@hasna/hooks", version: "0.0.0" };
|
|
41670
41673
|
try {
|
|
41671
41674
|
for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
|
|
41672
|
-
const p =
|
|
41673
|
-
if (
|
|
41674
|
-
pkg = JSON.parse(
|
|
41675
|
+
const p = join30(__dirname3, rel);
|
|
41676
|
+
if (existsSync18(p)) {
|
|
41677
|
+
pkg = JSON.parse(readFileSync21(p, "utf-8"));
|
|
41675
41678
|
break;
|
|
41676
41679
|
}
|
|
41677
41680
|
}
|
|
@@ -43413,8 +43416,8 @@ var {
|
|
|
43413
43416
|
|
|
43414
43417
|
// src/cli/index.tsx
|
|
43415
43418
|
import chalk2 from "chalk";
|
|
43416
|
-
import { existsSync as
|
|
43417
|
-
import { join as
|
|
43419
|
+
import { existsSync as existsSync19, readFileSync as readFileSync23, rmSync as rmSync6 } from "fs";
|
|
43420
|
+
import { join as join31, dirname as dirname24 } from "path";
|
|
43418
43421
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
43419
43422
|
|
|
43420
43423
|
// src/cli/components/App.tsx
|
|
@@ -44588,6 +44591,439 @@ function App({ initialHooks, overwrite = false }) {
|
|
|
44588
44591
|
|
|
44589
44592
|
// src/cli/index.tsx
|
|
44590
44593
|
init_native_safety();
|
|
44594
|
+
|
|
44595
|
+
// src/native-safety.ts
|
|
44596
|
+
init_native_safety();
|
|
44597
|
+
import { execFile } from "child_process";
|
|
44598
|
+
import { createHash as createHash7, randomUUID as randomUUID5 } from "crypto";
|
|
44599
|
+
import { closeSync as closeSync4, constants as constants3, existsSync as existsSync14, fstatSync as fstatSync3, fsyncSync, linkSync as linkSync3, lstatSync as lstatSync7, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync9, realpathSync as realpathSync7, renameSync as renameSync3, unlinkSync as unlinkSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
44600
|
+
import { homedir as homedir7 } from "os";
|
|
44601
|
+
import { dirname as dirname11, isAbsolute as isAbsolute7, join as join13, resolve as resolve15 } from "path";
|
|
44602
|
+
|
|
44603
|
+
class NativeSafetyError extends Error {
|
|
44604
|
+
code;
|
|
44605
|
+
reason;
|
|
44606
|
+
constructor(code2, reason) {
|
|
44607
|
+
super(`Native deletion safety refused (${code2}). Run the reviewed Hooks safety setup before using shell or patch tools.`);
|
|
44608
|
+
this.code = code2;
|
|
44609
|
+
this.reason = reason;
|
|
44610
|
+
this.name = "NativeSafetyError";
|
|
44611
|
+
}
|
|
44612
|
+
}
|
|
44613
|
+
var executionSchema = "hasna.hooks.execution.v1";
|
|
44614
|
+
var hash = (value) => createHash7("sha256").update(value).digest("hex");
|
|
44615
|
+
var filename = "sumi-trash-guard.json";
|
|
44616
|
+
var owner2 = (uid2) => uid2 === 0 || uid2 === process.getuid?.();
|
|
44617
|
+
function safeDirectory(path) {
|
|
44618
|
+
for (let cursor = path;; cursor = dirname11(cursor)) {
|
|
44619
|
+
const stat2 = lstatSync7(cursor);
|
|
44620
|
+
if (!stat2.isDirectory() || !owner2(stat2.uid) || stat2.mode & 18)
|
|
44621
|
+
throw new NativeSafetyError("binding_invalid");
|
|
44622
|
+
if (dirname11(cursor) === cursor)
|
|
44623
|
+
return;
|
|
44624
|
+
}
|
|
44625
|
+
}
|
|
44626
|
+
function canonicalHome(home) {
|
|
44627
|
+
if (!isAbsolute7(home) || home.includes("\x00") || realpathSync7(home) !== resolve15(home))
|
|
44628
|
+
throw new NativeSafetyError("binding_invalid");
|
|
44629
|
+
safeDirectory(home);
|
|
44630
|
+
return resolve15(home);
|
|
44631
|
+
}
|
|
44632
|
+
function readOwned(path) {
|
|
44633
|
+
safeDirectory(dirname11(path));
|
|
44634
|
+
const fd = openSync4(path, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
|
|
44635
|
+
try {
|
|
44636
|
+
const stat2 = fstatSync3(fd);
|
|
44637
|
+
if (!stat2.isFile() || !owner2(stat2.uid) || stat2.mode & 63 || stat2.size > 32768)
|
|
44638
|
+
throw new NativeSafetyError("binding_invalid");
|
|
44639
|
+
const value = readFileSync9(fd, "utf8");
|
|
44640
|
+
if (Buffer.byteLength(value) > 32768)
|
|
44641
|
+
throw new NativeSafetyError("binding_invalid");
|
|
44642
|
+
return value;
|
|
44643
|
+
} finally {
|
|
44644
|
+
closeSync4(fd);
|
|
44645
|
+
}
|
|
44646
|
+
}
|
|
44647
|
+
function parseRecord(text3, home) {
|
|
44648
|
+
const record3 = JSON.parse(text3);
|
|
44649
|
+
if (!record3 || Array.isArray(record3) || Object.keys(record3).sort().join() !== "command,harness,schema" || record3.schema !== "hasna.hooks.native-safety.v1" || record3.harness !== "sumi" || typeof record3.command !== "string")
|
|
44650
|
+
throw new NativeSafetyError("binding_invalid");
|
|
44651
|
+
const binding = nativeSafetyCommandBinding(record3.command);
|
|
44652
|
+
if (binding?.name !== "trash-guard" || binding.home !== home)
|
|
44653
|
+
throw new NativeSafetyError("binding_invalid");
|
|
44654
|
+
return record3;
|
|
44655
|
+
}
|
|
44656
|
+
function syncDirectory(path) {
|
|
44657
|
+
const fd = openSync4(path, constants3.O_RDONLY | constants3.O_DIRECTORY | constants3.O_NOFOLLOW);
|
|
44658
|
+
try {
|
|
44659
|
+
fsyncSync(fd);
|
|
44660
|
+
} finally {
|
|
44661
|
+
closeSync4(fd);
|
|
44662
|
+
}
|
|
44663
|
+
}
|
|
44664
|
+
function readNativeSafetyRegistration(home = homedir7()) {
|
|
44665
|
+
try {
|
|
44666
|
+
home = canonicalHome(home);
|
|
44667
|
+
const path = join13(home, ".hasna/hooks/native", filename), text3 = readOwned(path);
|
|
44668
|
+
return { record: parseRecord(text3, home), sha256: hash(text3), path };
|
|
44669
|
+
} catch (error61) {
|
|
44670
|
+
if (error61 instanceof NativeSafetyError)
|
|
44671
|
+
throw error61;
|
|
44672
|
+
throw new NativeSafetyError(error61?.code === "ENOENT" ? "binding_missing" : "binding_invalid");
|
|
44673
|
+
}
|
|
44674
|
+
}
|
|
44675
|
+
function registerNativeSafety(options = {}) {
|
|
44676
|
+
const home = canonicalHome(options.home ?? homedir7());
|
|
44677
|
+
const record3 = { schema: "hasna.hooks.native-safety.v1", harness: "sumi", command: options.command ?? installedNativeSafetyCommand("trash-guard") };
|
|
44678
|
+
const text3 = JSON.stringify(record3) + `
|
|
44679
|
+
`;
|
|
44680
|
+
parseRecord(text3, home);
|
|
44681
|
+
for (const dir of [join13(home, ".hasna"), join13(home, ".hasna/hooks"), join13(home, ".hasna/hooks/native")]) {
|
|
44682
|
+
try {
|
|
44683
|
+
mkdirSync9(dir, { mode: 448 });
|
|
44684
|
+
syncDirectory(dirname11(dir));
|
|
44685
|
+
} catch (error61) {
|
|
44686
|
+
if (error61.code !== "EEXIST")
|
|
44687
|
+
throw error61;
|
|
44688
|
+
}
|
|
44689
|
+
safeDirectory(dir);
|
|
44690
|
+
}
|
|
44691
|
+
const directory2 = join13(home, ".hasna/hooks/native"), path = join13(directory2, filename), lock = path + ".lock";
|
|
44692
|
+
let lockFD;
|
|
44693
|
+
try {
|
|
44694
|
+
lockFD = openSync4(lock, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
|
|
44695
|
+
} catch {
|
|
44696
|
+
throw new NativeSafetyError("binding_busy");
|
|
44697
|
+
}
|
|
44698
|
+
const lockIdentity = fstatSync3(lockFD);
|
|
44699
|
+
let temporaryIdentity;
|
|
44700
|
+
const temporary = join13(directory2, `.${filename}.${randomUUID5()}`);
|
|
44701
|
+
try {
|
|
44702
|
+
writeFileSync8(lockFD, JSON.stringify({ pid: process.pid, expectedSHA256: options.expectedSHA256 ?? "absent" }));
|
|
44703
|
+
fsyncSync(lockFD);
|
|
44704
|
+
syncDirectory(directory2);
|
|
44705
|
+
let before;
|
|
44706
|
+
try {
|
|
44707
|
+
before = readNativeSafetyRegistration(home);
|
|
44708
|
+
} catch (error61) {
|
|
44709
|
+
if (!(error61 instanceof NativeSafetyError) || error61.code !== "binding_missing")
|
|
44710
|
+
throw error61;
|
|
44711
|
+
}
|
|
44712
|
+
if (before?.sha256 === hash(text3))
|
|
44713
|
+
return { ok: true, changed: false, path, sha256: before.sha256 };
|
|
44714
|
+
if (before ? options.expectedSHA256 !== before.sha256 : options.expectedSHA256 !== undefined && options.expectedSHA256 !== "absent")
|
|
44715
|
+
throw new NativeSafetyError("binding_changed");
|
|
44716
|
+
if (before) {
|
|
44717
|
+
const backup = path + ".before-" + before.sha256;
|
|
44718
|
+
if (existsSync14(backup)) {
|
|
44719
|
+
if (hash(readOwned(backup)) !== before.sha256)
|
|
44720
|
+
throw new NativeSafetyError("binding_changed");
|
|
44721
|
+
} else {
|
|
44722
|
+
const original = readOwned(path);
|
|
44723
|
+
if (hash(original) !== before.sha256)
|
|
44724
|
+
throw new NativeSafetyError("binding_changed");
|
|
44725
|
+
const fd2 = openSync4(backup, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
|
|
44726
|
+
try {
|
|
44727
|
+
writeFileSync8(fd2, original);
|
|
44728
|
+
fsyncSync(fd2);
|
|
44729
|
+
} finally {
|
|
44730
|
+
closeSync4(fd2);
|
|
44731
|
+
}
|
|
44732
|
+
syncDirectory(directory2);
|
|
44733
|
+
}
|
|
44734
|
+
}
|
|
44735
|
+
const fd = openSync4(temporary, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
|
|
44736
|
+
try {
|
|
44737
|
+
temporaryIdentity = fstatSync3(fd);
|
|
44738
|
+
writeFileSync8(fd, text3);
|
|
44739
|
+
fsyncSync(fd);
|
|
44740
|
+
} finally {
|
|
44741
|
+
closeSync4(fd);
|
|
44742
|
+
}
|
|
44743
|
+
if (before) {
|
|
44744
|
+
if (hash(readOwned(path)) !== before.sha256)
|
|
44745
|
+
throw new NativeSafetyError("binding_changed");
|
|
44746
|
+
renameSync3(temporary, path);
|
|
44747
|
+
} else {
|
|
44748
|
+
linkSync3(temporary, path);
|
|
44749
|
+
unlinkSync5(temporary);
|
|
44750
|
+
}
|
|
44751
|
+
syncDirectory(directory2);
|
|
44752
|
+
const after = readNativeSafetyRegistration(home);
|
|
44753
|
+
if (after.sha256 !== hash(text3))
|
|
44754
|
+
throw new NativeSafetyError("binding_changed");
|
|
44755
|
+
return { ok: true, changed: true, path, sha256: after.sha256 };
|
|
44756
|
+
} finally {
|
|
44757
|
+
closeSync4(lockFD);
|
|
44758
|
+
const currentTemporary = lstatSync7(temporary, { throwIfNoEntry: false });
|
|
44759
|
+
if (temporaryIdentity && currentTemporary?.dev === temporaryIdentity.dev && currentTemporary.ino === temporaryIdentity.ino)
|
|
44760
|
+
unlinkSync5(temporary);
|
|
44761
|
+
const currentLock = lstatSync7(lock, { throwIfNoEntry: false });
|
|
44762
|
+
if (currentLock?.dev !== lockIdentity.dev || currentLock.ino !== lockIdentity.ino)
|
|
44763
|
+
throw new NativeSafetyError("binding_changed");
|
|
44764
|
+
unlinkSync5(lock);
|
|
44765
|
+
syncDirectory(directory2);
|
|
44766
|
+
}
|
|
44767
|
+
}
|
|
44768
|
+
async function evaluateNativeSafetyForExecution(event, options) {
|
|
44769
|
+
if (!options || typeof options.agent !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:@-]{0,127}$/.test(options.agent))
|
|
44770
|
+
throw new NativeSafetyError("input_invalid");
|
|
44771
|
+
let snapshot2;
|
|
44772
|
+
try {
|
|
44773
|
+
snapshot2 = JSON.parse(JSON.stringify(event));
|
|
44774
|
+
} catch {
|
|
44775
|
+
throw new NativeSafetyError("input_invalid");
|
|
44776
|
+
}
|
|
44777
|
+
if (snapshot2?.tool_input && Object.hasOwn(snapshot2.tool_input, "hasna_execution"))
|
|
44778
|
+
throw new NativeSafetyError("input_invalid");
|
|
44779
|
+
const before = readNativeSafetyRegistration(options.home);
|
|
44780
|
+
const binding = nativeSafetyCommandBinding(before.record.command);
|
|
44781
|
+
const decision = await evaluateRecord(snapshot2, before.record, options.signal, options.agent);
|
|
44782
|
+
if (readNativeSafetyRegistration(options.home).sha256 !== before.sha256)
|
|
44783
|
+
throw new NativeSafetyError("binding_changed");
|
|
44784
|
+
return decision.decision === "rewrite" ? {
|
|
44785
|
+
decision: "rewrite",
|
|
44786
|
+
execution: { command: decision.command, shell: "/bin/sh", cwd: snapshot2.cwd, env: { HOME: binding.home, PATH: binding.path } }
|
|
44787
|
+
} : decision;
|
|
44788
|
+
}
|
|
44789
|
+
async function evaluateRecord(event, record3, signal, executionAgent) {
|
|
44790
|
+
if (!event || event.hook_event_name !== "PreToolUse" || !["Bash", "apply_patch"].includes(event.tool_name) || typeof event.cwd !== "string" || !isAbsolute7(event.cwd) || !event.tool_input || typeof event.tool_input !== "object" || Array.isArray(event.tool_input))
|
|
44791
|
+
throw new NativeSafetyError("input_invalid");
|
|
44792
|
+
const input2 = JSON.stringify(executionAgent === undefined ? event : { ...event, hasna_execution: { schema: executionSchema, agent: executionAgent } });
|
|
44793
|
+
if (Buffer.byteLength(input2) > 1048576)
|
|
44794
|
+
throw new NativeSafetyError("input_invalid");
|
|
44795
|
+
const output2 = await new Promise((accept, reject) => {
|
|
44796
|
+
const child = execFile("/bin/sh", ["-c", record3.command], { cwd: "/", env: {}, encoding: "utf8", timeout: 8000, maxBuffer: 1048576, signal }, (error61, stdout, stderr) => {
|
|
44797
|
+
if (error61 || stderr) {
|
|
44798
|
+
reject(new NativeSafetyError("guard_failed"));
|
|
44799
|
+
return;
|
|
44800
|
+
}
|
|
44801
|
+
accept(stdout);
|
|
44802
|
+
});
|
|
44803
|
+
child.stdin?.on("error", () => {});
|
|
44804
|
+
child.stdin?.end(input2);
|
|
44805
|
+
});
|
|
44806
|
+
if (!output2)
|
|
44807
|
+
return { decision: "continue" };
|
|
44808
|
+
try {
|
|
44809
|
+
const value = JSON.parse(output2);
|
|
44810
|
+
if (Object.keys(value).join() !== "hookSpecificOutput")
|
|
44811
|
+
throw new Error;
|
|
44812
|
+
const verdict = value.hookSpecificOutput;
|
|
44813
|
+
if (verdict.hookEventName !== "PreToolUse")
|
|
44814
|
+
throw new Error;
|
|
44815
|
+
if (verdict.permissionDecision === "deny" && Object.keys(verdict).sort().join() === "hookEventName,permissionDecision,permissionDecisionReason" && typeof verdict.permissionDecisionReason === "string" && verdict.permissionDecisionReason.trim())
|
|
44816
|
+
return { decision: "deny", reason: verdict.permissionDecisionReason };
|
|
44817
|
+
if (event.tool_name === "Bash" && verdict.permissionDecision === "allow" && Object.keys(verdict).sort().join() === "hookEventName,permissionDecision,updatedInput" && typeof verdict.updatedInput?.command === "string" && verdict.updatedInput.command.trim() && (executionAgent === undefined || verdict.updatedInput.hasna_execution === executionSchema))
|
|
44818
|
+
return { decision: "rewrite", command: verdict.updatedInput.command };
|
|
44819
|
+
} catch {}
|
|
44820
|
+
throw new NativeSafetyError("guard_failed");
|
|
44821
|
+
}
|
|
44822
|
+
async function verifyNativeSafetyCommand(command, home = homedir7()) {
|
|
44823
|
+
home = canonicalHome(home);
|
|
44824
|
+
const record3 = parseRecord(JSON.stringify({ schema: "hasna.hooks.native-safety.v1", harness: "sumi", command }), home);
|
|
44825
|
+
const verdict = await evaluateRecord({ hook_event_name: "PreToolUse", tool_name: "Bash", cwd: home, tool_input: { command: "rm -- .hasna-trash-verification-only" } }, record3);
|
|
44826
|
+
if (verdict.decision !== "rewrite")
|
|
44827
|
+
throw new NativeSafetyError("guard_failed", verdict.decision === "deny" ? verdict.reason.slice(0, 512) : undefined);
|
|
44828
|
+
}
|
|
44829
|
+
async function verifyNativeSafetyRegistration(home = homedir7()) {
|
|
44830
|
+
const before = readNativeSafetyRegistration(home);
|
|
44831
|
+
await verifyNativeSafetyCommand(before.record.command, home);
|
|
44832
|
+
const after = readNativeSafetyRegistration(home);
|
|
44833
|
+
if (after.sha256 !== before.sha256)
|
|
44834
|
+
throw new NativeSafetyError("binding_changed");
|
|
44835
|
+
return { path: before.path, sha256: before.sha256 };
|
|
44836
|
+
}
|
|
44837
|
+
async function verifyNativeSafetyExecutionRegistration(home = homedir7()) {
|
|
44838
|
+
const before = readNativeSafetyRegistration(home);
|
|
44839
|
+
const verdict = await evaluateNativeSafetyForExecution({
|
|
44840
|
+
hook_event_name: "PreToolUse",
|
|
44841
|
+
tool_name: "Bash",
|
|
44842
|
+
cwd: home,
|
|
44843
|
+
tool_input: { command: "rm -- .hasna-trash-verification-only" }
|
|
44844
|
+
}, { home, agent: "native-safety-verification" });
|
|
44845
|
+
if (verdict.decision !== "rewrite")
|
|
44846
|
+
throw new NativeSafetyError("guard_failed", verdict.decision === "deny" ? verdict.reason.slice(0, 512) : undefined);
|
|
44847
|
+
if (readNativeSafetyRegistration(home).sha256 !== before.sha256)
|
|
44848
|
+
throw new NativeSafetyError("binding_changed");
|
|
44849
|
+
return { path: before.path, sha256: before.sha256 };
|
|
44850
|
+
}
|
|
44851
|
+
|
|
44852
|
+
// hooks/hook-trash-guard/src/binary.ts
|
|
44853
|
+
import { dirname as dirname17, isAbsolute as isAbsolute15, join as join17, resolve as resolve17 } from "path";
|
|
44854
|
+
import { accessSync, closeSync as closeSync5, constants as constants4, fstatSync as fstatSync4, lstatSync as lstatSync9, openSync as openSync5, readFileSync as readFileSync12, readlinkSync as readlinkSync3, readSync as readSync2, realpathSync as realpathSync8, statSync } from "fs";
|
|
44855
|
+
import { spawnSync } from "child_process";
|
|
44856
|
+
function trustedOwner(uid2) {
|
|
44857
|
+
return uid2 === 0 || uid2 === process.getuid?.();
|
|
44858
|
+
}
|
|
44859
|
+
function safeExecutableFile(path, systemBinAlias = false) {
|
|
44860
|
+
let current2 = path;
|
|
44861
|
+
for (let hop = 0;hop < 32; hop++) {
|
|
44862
|
+
if (!isAbsolute15(current2) || current2.length > 4096 || !safeBinaryDirectory("/"))
|
|
44863
|
+
return false;
|
|
44864
|
+
const parts = current2.split("/"), name = parts.pop();
|
|
44865
|
+
if (!name || name === "." || name === "..")
|
|
44866
|
+
return false;
|
|
44867
|
+
let parent = "/";
|
|
44868
|
+
for (const part of parts) {
|
|
44869
|
+
if (!part || part === ".")
|
|
44870
|
+
continue;
|
|
44871
|
+
if (part === "..") {
|
|
44872
|
+
parent = dirname17(parent);
|
|
44873
|
+
continue;
|
|
44874
|
+
}
|
|
44875
|
+
const next = join17(parent, part), info2 = lstatSync9(next);
|
|
44876
|
+
if (systemBinAlias && next === "/bin" && info2.isSymbolicLink() && info2.uid === 0) {
|
|
44877
|
+
const target = realpathSync8(next);
|
|
44878
|
+
if (!safeBinaryDirectory(target))
|
|
44879
|
+
return false;
|
|
44880
|
+
parent = target;
|
|
44881
|
+
continue;
|
|
44882
|
+
}
|
|
44883
|
+
if (!info2.isDirectory() || !trustedOwner(info2.uid) || info2.mode & 18)
|
|
44884
|
+
return false;
|
|
44885
|
+
parent = next;
|
|
44886
|
+
}
|
|
44887
|
+
const candidate = join17(parent, name), info = lstatSync9(candidate);
|
|
44888
|
+
if (!trustedOwner(info.uid))
|
|
44889
|
+
return false;
|
|
44890
|
+
if (info.isSymbolicLink()) {
|
|
44891
|
+
const target = readlinkSync3(candidate);
|
|
44892
|
+
current2 = isAbsolute15(target) ? target : parent + "/" + target;
|
|
44893
|
+
continue;
|
|
44894
|
+
}
|
|
44895
|
+
return info.isFile() && Boolean(info.mode & 73) && !(info.mode & 18);
|
|
44896
|
+
}
|
|
44897
|
+
return false;
|
|
44898
|
+
}
|
|
44899
|
+
function safeInterpreterSearchDirectory(path) {
|
|
44900
|
+
if (!isAbsolute15(path) || resolve17(path) !== (path.replace(/\/+$/, "") || "/"))
|
|
44901
|
+
return false;
|
|
44902
|
+
for (let cursor = resolve17(path);; cursor = dirname17(cursor)) {
|
|
44903
|
+
try {
|
|
44904
|
+
lstatSync9(cursor);
|
|
44905
|
+
return safeBinaryDirectory(cursor);
|
|
44906
|
+
} catch (error61) {
|
|
44907
|
+
if (error61.code !== "ENOENT")
|
|
44908
|
+
return false;
|
|
44909
|
+
}
|
|
44910
|
+
if (dirname17(cursor) === cursor)
|
|
44911
|
+
return false;
|
|
44912
|
+
}
|
|
44913
|
+
}
|
|
44914
|
+
function safeScriptInterpreter(executable, pathValue) {
|
|
44915
|
+
const fd = openSync5(executable, constants4.O_RDONLY | constants4.O_NONBLOCK | constants4.O_NOFOLLOW);
|
|
44916
|
+
let header;
|
|
44917
|
+
try {
|
|
44918
|
+
if (!fstatSync4(fd).isFile())
|
|
44919
|
+
return false;
|
|
44920
|
+
const bytes = Buffer.alloc(256), length = readSync2(fd, bytes, 0, bytes.length, 0);
|
|
44921
|
+
header = bytes.subarray(0, length).toString("utf8").split(`
|
|
44922
|
+
`)[0];
|
|
44923
|
+
} finally {
|
|
44924
|
+
closeSync5(fd);
|
|
44925
|
+
}
|
|
44926
|
+
const direct = /^#!(\/[A-Za-z0-9_./-]+)$/.exec(header);
|
|
44927
|
+
if (direct && direct[1] !== "/usr/bin/env")
|
|
44928
|
+
return safeExecutableFile(direct[1], true);
|
|
44929
|
+
if (header !== "#!/usr/bin/env bun" || !safeExecutableFile("/usr/bin/env", true))
|
|
44930
|
+
return false;
|
|
44931
|
+
for (const dir of pathValue.split(":").slice(0, 64)) {
|
|
44932
|
+
if (!safeInterpreterSearchDirectory(dir))
|
|
44933
|
+
return false;
|
|
44934
|
+
const interpreter = resolve17(dir, "bun");
|
|
44935
|
+
try {
|
|
44936
|
+
if (!statSync(interpreter).isFile())
|
|
44937
|
+
continue;
|
|
44938
|
+
accessSync(interpreter, constants4.X_OK);
|
|
44939
|
+
} catch {
|
|
44940
|
+
continue;
|
|
44941
|
+
}
|
|
44942
|
+
return safeExecutableFile(interpreter, true);
|
|
44943
|
+
}
|
|
44944
|
+
return false;
|
|
44945
|
+
}
|
|
44946
|
+
function safeBinaryDirectory(path) {
|
|
44947
|
+
let current2 = path;
|
|
44948
|
+
for (;; ) {
|
|
44949
|
+
const info = lstatSync9(current2);
|
|
44950
|
+
if (!info.isDirectory() || !trustedOwner(info.uid) || (info.mode & 18) !== 0)
|
|
44951
|
+
return false;
|
|
44952
|
+
const parent = dirname17(current2);
|
|
44953
|
+
if (parent === current2)
|
|
44954
|
+
return true;
|
|
44955
|
+
current2 = parent;
|
|
44956
|
+
}
|
|
44957
|
+
}
|
|
44958
|
+
function inspectTrashBinary(env = process.env) {
|
|
44959
|
+
const pathValue = env.PATH ?? "";
|
|
44960
|
+
const deadline = Date.now() + 2000;
|
|
44961
|
+
let failure = "missing";
|
|
44962
|
+
const reject = (code2) => {
|
|
44963
|
+
if (failure === "missing" || failure === "unrecognized_package" || code2.startsWith("unsafe_"))
|
|
44964
|
+
failure = code2;
|
|
44965
|
+
};
|
|
44966
|
+
for (const dir of pathValue.split(":").slice(0, 64)) {
|
|
44967
|
+
if (!isAbsolute15(dir) || Date.now() >= deadline)
|
|
44968
|
+
continue;
|
|
44969
|
+
const candidate = resolve17(dir, "trash");
|
|
44970
|
+
try {
|
|
44971
|
+
const stat2 = statSync(candidate);
|
|
44972
|
+
if (!stat2.isFile() || !trustedOwner(stat2.uid) || (stat2.mode & 73) === 0 || (stat2.mode & 18) !== 0) {
|
|
44973
|
+
reject("unsafe_executable");
|
|
44974
|
+
continue;
|
|
44975
|
+
}
|
|
44976
|
+
const executable = realpathSync8(candidate);
|
|
44977
|
+
if (!safeBinaryDirectory(resolve17(dir)) || !safeBinaryDirectory(dirname17(executable))) {
|
|
44978
|
+
reject("unsafe_directory");
|
|
44979
|
+
continue;
|
|
44980
|
+
}
|
|
44981
|
+
if (!safeExecutableFile(candidate)) {
|
|
44982
|
+
reject("unsafe_directory");
|
|
44983
|
+
continue;
|
|
44984
|
+
}
|
|
44985
|
+
const packageRoot = resolve17(dirname17(executable), "../..");
|
|
44986
|
+
const manifestPath = join17(packageRoot, "package.json");
|
|
44987
|
+
const manifestStat = statSync(manifestPath);
|
|
44988
|
+
if (!manifestStat.isFile() || !trustedOwner(manifestStat.uid) || manifestStat.size > 16384 || (manifestStat.mode & 18) !== 0 || lstatSync9(manifestPath).isSymbolicLink()) {
|
|
44989
|
+
reject("unsafe_manifest");
|
|
44990
|
+
continue;
|
|
44991
|
+
}
|
|
44992
|
+
const manifest = JSON.parse(readFileSync12(manifestPath, "utf8"));
|
|
44993
|
+
if (manifest.name !== "@hasna/trash" || typeof manifest.version !== "string" || typeof manifest.bin?.trash !== "string" || realpathSync8(resolve17(packageRoot, manifest.bin.trash)) !== executable) {
|
|
44994
|
+
reject("unrecognized_package");
|
|
44995
|
+
continue;
|
|
44996
|
+
}
|
|
44997
|
+
try {
|
|
44998
|
+
if (!safeScriptInterpreter(executable, pathValue)) {
|
|
44999
|
+
reject("unsafe_interpreter");
|
|
45000
|
+
continue;
|
|
45001
|
+
}
|
|
45002
|
+
} catch {
|
|
45003
|
+
reject("unsafe_interpreter");
|
|
45004
|
+
continue;
|
|
45005
|
+
}
|
|
45006
|
+
const probe = spawnSync(candidate, ["--identity"], {
|
|
45007
|
+
encoding: "utf8",
|
|
45008
|
+
timeout: Math.max(1, Math.min(500, deadline - Date.now())),
|
|
45009
|
+
maxBuffer: 2048,
|
|
45010
|
+
env: { PATH: pathValue },
|
|
45011
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
45012
|
+
});
|
|
45013
|
+
if (probe.error || probe.status !== 0) {
|
|
45014
|
+
reject("identity_failed");
|
|
45015
|
+
continue;
|
|
45016
|
+
}
|
|
45017
|
+
const identity3 = JSON.parse(probe.stdout);
|
|
45018
|
+
if (identity3.name === "@hasna/trash" && identity3.version === manifest.version && identity3.guardProtocol === "hasna.trash.guard.v1")
|
|
45019
|
+
return { ok: true, path: candidate, version: manifest.version };
|
|
45020
|
+
reject("unsupported_protocol");
|
|
45021
|
+
} catch {}
|
|
45022
|
+
}
|
|
45023
|
+
return { ok: false, code: failure };
|
|
45024
|
+
}
|
|
45025
|
+
|
|
45026
|
+
// src/cli/index.tsx
|
|
44591
45027
|
init_registry();
|
|
44592
45028
|
init_installer();
|
|
44593
45029
|
init_registration();
|
|
@@ -44603,9 +45039,9 @@ init_local_opt_in();
|
|
|
44603
45039
|
init_transport2();
|
|
44604
45040
|
init_db();
|
|
44605
45041
|
import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
|
|
44606
|
-
var __dirname4 =
|
|
44607
|
-
var pkgPath =
|
|
44608
|
-
var pkg2 = JSON.parse(
|
|
45042
|
+
var __dirname4 = dirname24(fileURLToPath6(import.meta.url));
|
|
45043
|
+
var pkgPath = existsSync19(join31(__dirname4, "..", "package.json")) ? join31(__dirname4, "..", "package.json") : join31(__dirname4, "..", "..", "package.json");
|
|
45044
|
+
var pkg2 = JSON.parse(readFileSync23(pkgPath, "utf-8"));
|
|
44609
45045
|
var program3 = new Command;
|
|
44610
45046
|
function resolveScope(options) {
|
|
44611
45047
|
if (options.project)
|
|
@@ -44685,7 +45121,7 @@ async function openLocalStoreOrExit(json3) {
|
|
|
44685
45121
|
function readToken(tokenFile) {
|
|
44686
45122
|
if (tokenFile) {
|
|
44687
45123
|
try {
|
|
44688
|
-
return
|
|
45124
|
+
return readFileSync23(tokenFile, "utf-8").trim();
|
|
44689
45125
|
} catch {
|
|
44690
45126
|
return;
|
|
44691
45127
|
}
|
|
@@ -44823,7 +45259,7 @@ program3.command("init").description("Register a new agent profile with a unique
|
|
|
44823
45259
|
if (profile.name) {
|
|
44824
45260
|
console.log(` ${chalk2.dim("Name:")} ${profile.name}`);
|
|
44825
45261
|
}
|
|
44826
|
-
console.log(` ${chalk2.dim("Profile:")} ${
|
|
45262
|
+
console.log(` ${chalk2.dim("Profile:")} ${join31(getProfilesDir(), `${profile.agent_id}.json`)}`);
|
|
44827
45263
|
console.log();
|
|
44828
45264
|
console.log(chalk2.dim(" Install hooks with this profile:"));
|
|
44829
45265
|
console.log(` hooks install gitguard --profile ${profile.agent_id}`);
|
|
@@ -44847,11 +45283,11 @@ program3.command("run").argument("<hook>", "Hook to run").option("--profile <id>
|
|
|
44847
45283
|
process.exit(1);
|
|
44848
45284
|
}
|
|
44849
45285
|
const hookScript = resolveScriptPath2(hook);
|
|
44850
|
-
if (!hookScript || !
|
|
45286
|
+
if (!hookScript || !existsSync19(hookScript)) {
|
|
44851
45287
|
console.error(JSON.stringify({ error: `Hook script not found: ${hookScript ?? hook}` }));
|
|
44852
45288
|
process.exit(1);
|
|
44853
45289
|
}
|
|
44854
|
-
const content =
|
|
45290
|
+
const content = readFileSync23(hookScript);
|
|
44855
45291
|
const sha4 = sha256Of2(content);
|
|
44856
45292
|
const check2 = checkScriptHash2(hook, sha4);
|
|
44857
45293
|
if (!check2.ok) {
|
|
@@ -45445,7 +45881,7 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
45445
45881
|
});
|
|
45446
45882
|
}
|
|
45447
45883
|
const staleNames = new Set(stale.map((finding) => finding.hook));
|
|
45448
|
-
const settingsExist =
|
|
45884
|
+
const settingsExist = existsSync19(settingsPath);
|
|
45449
45885
|
if (!settingsExist) {
|
|
45450
45886
|
issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
|
|
45451
45887
|
}
|
|
@@ -45453,7 +45889,7 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
45453
45889
|
let wiringCount = 0;
|
|
45454
45890
|
if (settingsExist) {
|
|
45455
45891
|
try {
|
|
45456
|
-
const settings = JSON.parse(
|
|
45892
|
+
const settings = JSON.parse(readFileSync23(settingsPath, "utf-8"));
|
|
45457
45893
|
wiringCount = countSettingsWiring(settings);
|
|
45458
45894
|
} catch {}
|
|
45459
45895
|
}
|
|
@@ -45464,7 +45900,7 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
45464
45900
|
const meta3 = custom2 ? resolveHookMeta(name) : getHook(name);
|
|
45465
45901
|
let hookHealthy = true;
|
|
45466
45902
|
if (custom2) {
|
|
45467
|
-
if (!
|
|
45903
|
+
if (!existsSync19(custom2.scriptPath)) {
|
|
45468
45904
|
issues.push({ hook: name, issue: `Custom hook script missing: ${custom2.scriptPath}`, severity: "error" });
|
|
45469
45905
|
hookHealthy = false;
|
|
45470
45906
|
}
|
|
@@ -45475,15 +45911,15 @@ program3.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
45475
45911
|
continue;
|
|
45476
45912
|
}
|
|
45477
45913
|
const hookDir = getHookPath(name);
|
|
45478
|
-
const hookScript =
|
|
45479
|
-
if (!
|
|
45914
|
+
const hookScript = join31(hookDir, "src", "hook.ts");
|
|
45915
|
+
if (!existsSync19(hookScript)) {
|
|
45480
45916
|
issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
|
|
45481
45917
|
hookHealthy = false;
|
|
45482
45918
|
}
|
|
45483
45919
|
}
|
|
45484
45920
|
if (meta3 && settingsExist) {
|
|
45485
45921
|
try {
|
|
45486
|
-
const settings = JSON.parse(
|
|
45922
|
+
const settings = JSON.parse(readFileSync23(settingsPath, "utf-8"));
|
|
45487
45923
|
if (!hookRegisteredInSettings(settings, name, meta3.event, meta3.matcher)) {
|
|
45488
45924
|
const eventName = meta3.event.split(":")[0];
|
|
45489
45925
|
issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
|
|
@@ -45633,18 +46069,18 @@ program3.command("update").argument("[hooks...]", "Hooks to update (defaults to
|
|
|
45633
46069
|
const result = installHook(arg, { scope, overwrite: true });
|
|
45634
46070
|
const resolved = resolveHook2(arg);
|
|
45635
46071
|
if (result.success && resolved) {
|
|
45636
|
-
const
|
|
45637
|
-
setPinnedHook2(resolved.name, { version: resolved.version, sha256:
|
|
46072
|
+
const hash4 = await sha256File2(resolved.scriptPath);
|
|
46073
|
+
setPinnedHook2(resolved.name, { version: resolved.version, sha256: hash4, source: resolved.source });
|
|
45638
46074
|
if (!isLocalStoreRefused2()) {
|
|
45639
46075
|
upsertHookRecord2(getDb2(), {
|
|
45640
46076
|
name: resolved.name,
|
|
45641
46077
|
version: resolved.version,
|
|
45642
|
-
sha256:
|
|
46078
|
+
sha256: hash4,
|
|
45643
46079
|
source_type: resolved.source,
|
|
45644
46080
|
last_verified_at: new Date().toISOString()
|
|
45645
46081
|
});
|
|
45646
46082
|
}
|
|
45647
|
-
results2.push({ ...result, pinned: { version: resolved.version, sha256:
|
|
46083
|
+
results2.push({ ...result, pinned: { version: resolved.version, sha256: hash4 } });
|
|
45648
46084
|
continue;
|
|
45649
46085
|
}
|
|
45650
46086
|
results2.push(result);
|
|
@@ -45686,10 +46122,10 @@ program3.command("docs").argument("[hook]", "Hook name (shows general docs if om
|
|
|
45686
46122
|
return;
|
|
45687
46123
|
}
|
|
45688
46124
|
const hookPath = getHookPath(hook);
|
|
45689
|
-
const readmePath =
|
|
46125
|
+
const readmePath = join31(hookPath, "README.md");
|
|
45690
46126
|
let readme = "";
|
|
45691
|
-
if (
|
|
45692
|
-
readme =
|
|
46127
|
+
if (existsSync19(readmePath)) {
|
|
46128
|
+
readme = readFileSync23(readmePath, "utf-8");
|
|
45693
46129
|
}
|
|
45694
46130
|
if (options.json) {
|
|
45695
46131
|
console.log(JSON.stringify({ ...meta3, readme }));
|
|
@@ -46317,7 +46753,38 @@ program3.command("mcp").option("-s, --stdio", "Use stdio transport (one process
|
|
|
46317
46753
|
}
|
|
46318
46754
|
});
|
|
46319
46755
|
registerEventsCommands(program3, { source: "hooks" });
|
|
46320
|
-
program3.command("safety").description("
|
|
46756
|
+
var safety = program3.command("safety").description("Inspect and register bundled native deletion guards without a registry credential");
|
|
46757
|
+
safety.command("inspect").description("Check the installed Trash guard identity and filesystem safety without app credentials").action(() => {
|
|
46758
|
+
const result = inspectTrashBinary();
|
|
46759
|
+
console.log(JSON.stringify({ guard: "trash-guard", ...result }));
|
|
46760
|
+
if (!result.ok)
|
|
46761
|
+
process.exitCode = 1;
|
|
46762
|
+
});
|
|
46763
|
+
safety.command("install <guard>").option("--target <target>", "codex, claude or sumi", "codex").option("--scope <scope>", "global or project", "global").option("--overwrite", "Replace this guard in place; native trust is still required").option("--expected-binding-sha256 <sha256>", "Exact existing Sumi binding digest required for an upgrade").action(async (guard, options) => {
|
|
46764
|
+
if (options.target === "sumi") {
|
|
46765
|
+
try {
|
|
46766
|
+
if (guard !== "trash-guard" || options.scope !== "global" || options.overwrite)
|
|
46767
|
+
throw new NativeSafetyError("input_invalid");
|
|
46768
|
+
const command = installedNativeSafetyCommand("trash-guard");
|
|
46769
|
+
const binding = nativeSafetyCommandBinding(command);
|
|
46770
|
+
const installed = inspectTrashBinary({ PATH: binding.path });
|
|
46771
|
+
if (!installed.ok) {
|
|
46772
|
+
console.log(JSON.stringify({ ok: false, target: "sumi", code: installed.code }));
|
|
46773
|
+
process.exitCode = 1;
|
|
46774
|
+
return;
|
|
46775
|
+
}
|
|
46776
|
+
await verifyNativeSafetyCommand(command);
|
|
46777
|
+
const registered = registerNativeSafety({ command, expectedSHA256: options.expectedBindingSha256 });
|
|
46778
|
+
const verified = await verifyNativeSafetyRegistration();
|
|
46779
|
+
if (verified.sha256 !== registered.sha256)
|
|
46780
|
+
throw new NativeSafetyError("binding_changed");
|
|
46781
|
+
console.log(JSON.stringify({ target: "sumi", ...registered }));
|
|
46782
|
+
} catch (error61) {
|
|
46783
|
+
console.log(JSON.stringify({ ok: false, target: "sumi", code: error61 instanceof NativeSafetyError ? error61.code : "binding_invalid", ...error61 instanceof NativeSafetyError && error61.reason ? { reason: error61.reason } : {} }));
|
|
46784
|
+
process.exitCode = 1;
|
|
46785
|
+
}
|
|
46786
|
+
return;
|
|
46787
|
+
}
|
|
46321
46788
|
if (!isNativeSafetyName(guard) || !["codex", "claude"].includes(options.target) || !["global", "project"].includes(options.scope)) {
|
|
46322
46789
|
console.error("Unsupported bundled safety registration");
|
|
46323
46790
|
process.exitCode = 1;
|
|
@@ -46328,6 +46795,17 @@ program3.command("safety").description("Register bundled native deletion guards
|
|
|
46328
46795
|
if (!result.success)
|
|
46329
46796
|
process.exitCode = 1;
|
|
46330
46797
|
});
|
|
46798
|
+
safety.command("verify").requiredOption("--target <target>", "sumi").option("--execution", "Also require the pinned native execution capability; no file operation is run").description("Verify a required native binding and its supervisor without running a file operation").action(async (options) => {
|
|
46799
|
+
try {
|
|
46800
|
+
if (options.target !== "sumi")
|
|
46801
|
+
throw new NativeSafetyError("input_invalid");
|
|
46802
|
+
const current2 = await (options.execution ? verifyNativeSafetyExecutionRegistration() : verifyNativeSafetyRegistration());
|
|
46803
|
+
console.log(JSON.stringify({ ok: true, target: "sumi", sha256: current2.sha256, guardVerified: true, ...options.execution ? { executionCapabilityVerified: true } : {}, nativeAdoptionVerified: false }));
|
|
46804
|
+
} catch (error61) {
|
|
46805
|
+
console.log(JSON.stringify({ ok: false, target: "sumi", code: error61 instanceof NativeSafetyError ? error61.code : "guard_failed", ...error61 instanceof NativeSafetyError && error61.reason ? { reason: error61.reason } : {} }));
|
|
46806
|
+
process.exitCode = 1;
|
|
46807
|
+
}
|
|
46808
|
+
});
|
|
46331
46809
|
var API_INDEPENDENT_COMMANDS = new Set([
|
|
46332
46810
|
"safety",
|
|
46333
46811
|
"serve",
|