@hasna/hooks 0.6.6 → 0.6.8
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/index.js +152 -57
- package/bin/serve.js +13 -10
- package/dist/index.js +117 -21
- package/dist/lib/hook-env.d.ts +60 -4
- package/dist/lib/manifest.d.ts +11 -0
- package/dist/lib/semver.d.ts +27 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -47,7 +47,7 @@ var __export = (target, all) => {
|
|
|
47
47
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
48
48
|
var __require = import.meta.require;
|
|
49
49
|
|
|
50
|
-
// node_modules/commander/lib/error.js
|
|
50
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/error.js
|
|
51
51
|
var require_error = __commonJS((exports) => {
|
|
52
52
|
class CommanderError extends Error {
|
|
53
53
|
constructor(exitCode, code, message) {
|
|
@@ -71,7 +71,7 @@ var require_error = __commonJS((exports) => {
|
|
|
71
71
|
exports.InvalidArgumentError = InvalidArgumentError;
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
// node_modules/commander/lib/argument.js
|
|
74
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/argument.js
|
|
75
75
|
var require_argument = __commonJS((exports) => {
|
|
76
76
|
var { InvalidArgumentError } = require_error();
|
|
77
77
|
|
|
@@ -150,7 +150,7 @@ var require_argument = __commonJS((exports) => {
|
|
|
150
150
|
exports.humanReadableArgName = humanReadableArgName;
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
// node_modules/commander/lib/help.js
|
|
153
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/help.js
|
|
154
154
|
var require_help = __commonJS((exports) => {
|
|
155
155
|
var { humanReadableArgName } = require_argument();
|
|
156
156
|
|
|
@@ -399,7 +399,7 @@ var require_help = __commonJS((exports) => {
|
|
|
399
399
|
exports.Help = Help;
|
|
400
400
|
});
|
|
401
401
|
|
|
402
|
-
// node_modules/commander/lib/option.js
|
|
402
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/option.js
|
|
403
403
|
var require_option = __commonJS((exports) => {
|
|
404
404
|
var { InvalidArgumentError } = require_error();
|
|
405
405
|
|
|
@@ -550,7 +550,7 @@ var require_option = __commonJS((exports) => {
|
|
|
550
550
|
exports.DualOptions = DualOptions;
|
|
551
551
|
});
|
|
552
552
|
|
|
553
|
-
// node_modules/commander/lib/suggestSimilar.js
|
|
553
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/suggestSimilar.js
|
|
554
554
|
var require_suggestSimilar = __commonJS((exports) => {
|
|
555
555
|
var maxDistance = 3;
|
|
556
556
|
function editDistance(a, b) {
|
|
@@ -623,7 +623,7 @@ var require_suggestSimilar = __commonJS((exports) => {
|
|
|
623
623
|
exports.suggestSimilar = suggestSimilar;
|
|
624
624
|
});
|
|
625
625
|
|
|
626
|
-
// node_modules/commander/lib/command.js
|
|
626
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/lib/command.js
|
|
627
627
|
var require_command = __commonJS((exports) => {
|
|
628
628
|
var EventEmitter = __require("events").EventEmitter;
|
|
629
629
|
var childProcess = __require("child_process");
|
|
@@ -1866,7 +1866,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1866
1866
|
exports.Command = Command;
|
|
1867
1867
|
});
|
|
1868
1868
|
|
|
1869
|
-
// node_modules/commander/index.js
|
|
1869
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/index.js
|
|
1870
1870
|
var require_commander = __commonJS((exports) => {
|
|
1871
1871
|
var { Argument } = require_argument();
|
|
1872
1872
|
var { Command } = require_command();
|
|
@@ -2419,7 +2419,7 @@ var init_registry = __esm(() => {
|
|
|
2419
2419
|
];
|
|
2420
2420
|
});
|
|
2421
2421
|
|
|
2422
|
-
// node_modules/cli-spinners/spinners.json
|
|
2422
|
+
// ../../node_modules/.bun/cli-spinners@2.9.2/node_modules/cli-spinners/spinners.json
|
|
2423
2423
|
var require_spinners = __commonJS((exports, module) => {
|
|
2424
2424
|
module.exports = {
|
|
2425
2425
|
dots: {
|
|
@@ -4045,7 +4045,7 @@ var require_spinners = __commonJS((exports, module) => {
|
|
|
4045
4045
|
};
|
|
4046
4046
|
});
|
|
4047
4047
|
|
|
4048
|
-
// node_modules/cli-spinners/index.js
|
|
4048
|
+
// ../../node_modules/.bun/cli-spinners@2.9.2/node_modules/cli-spinners/index.js
|
|
4049
4049
|
var require_cli_spinners = __commonJS((exports, module) => {
|
|
4050
4050
|
var spinners = Object.assign({}, require_spinners());
|
|
4051
4051
|
var spinnersList = Object.keys(spinners);
|
|
@@ -4125,9 +4125,9 @@ function resolveApiKey() {
|
|
|
4125
4125
|
var init_config = () => {};
|
|
4126
4126
|
|
|
4127
4127
|
// src/lib/semver.ts
|
|
4128
|
-
var SEMVER_PATTERN;
|
|
4128
|
+
var NUMERIC_ID = "(0|[1-9]\\d{0,15})", ALNUM_ID = "\\d*[A-Za-z-][0-9A-Za-z-]*", SEMVER_PATTERN;
|
|
4129
4129
|
var init_semver = __esm(() => {
|
|
4130
|
-
SEMVER_PATTERN =
|
|
4130
|
+
SEMVER_PATTERN = new RegExp(`^${NUMERIC_ID}\\.${NUMERIC_ID}\\.${NUMERIC_ID}` + `(?:-(?:${NUMERIC_ID}|${ALNUM_ID})(?:\\.(?:${NUMERIC_ID}|${ALNUM_ID}))*)?` + `(?:\\+[0-9A-Za-z.-]+)?$`);
|
|
4131
4131
|
});
|
|
4132
4132
|
|
|
4133
4133
|
// src/lib/manifest.ts
|
|
@@ -4289,7 +4289,8 @@ var init_manifest = __esm(() => {
|
|
|
4289
4289
|
script: z.string().min(1, "script must name a relative path or inline content"),
|
|
4290
4290
|
script_kind: z.enum(["inline", "file"]).optional(),
|
|
4291
4291
|
args: z.array(z.string()).optional(),
|
|
4292
|
-
timeout_ms: z.number().int().positive().optional()
|
|
4292
|
+
timeout_ms: z.number().int().positive().optional(),
|
|
4293
|
+
env: z.record(z.string(), z.string()).optional()
|
|
4293
4294
|
});
|
|
4294
4295
|
ScriptContainmentError = class ScriptContainmentError extends Error {
|
|
4295
4296
|
constructor(path, hookDir, raw) {
|
|
@@ -5921,6 +5922,8 @@ var init_db_writer = __esm(() => {
|
|
|
5921
5922
|
});
|
|
5922
5923
|
|
|
5923
5924
|
// src/lib/hook-env.ts
|
|
5925
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2 } from "path";
|
|
5926
|
+
import { existsSync as existsSync10, statSync } from "fs";
|
|
5924
5927
|
function isDeniedEnvName(name) {
|
|
5925
5928
|
const upper = name.toUpperCase();
|
|
5926
5929
|
if (DENY_PREFIXES.some((prefix) => upper.startsWith(prefix)))
|
|
@@ -5929,13 +5932,74 @@ function isDeniedEnvName(name) {
|
|
|
5929
5932
|
return true;
|
|
5930
5933
|
return DENY_CONTAINS.some((token) => upper.includes(token));
|
|
5931
5934
|
}
|
|
5935
|
+
function isInterpreterInjectionEnvName(name) {
|
|
5936
|
+
if (INTERPRETER_INJECTION.has(name))
|
|
5937
|
+
return true;
|
|
5938
|
+
return INTERPRETER_INJECTION_PREFIXES.some((prefix) => name.startsWith(prefix));
|
|
5939
|
+
}
|
|
5940
|
+
function isBashFunctionEnvName(name) {
|
|
5941
|
+
return name.startsWith("BASH_FUNC_");
|
|
5942
|
+
}
|
|
5943
|
+
function isUnsafePathEntry(entry, home) {
|
|
5944
|
+
if (entry === "" || !isAbsolute2(entry))
|
|
5945
|
+
return true;
|
|
5946
|
+
if (home && (entry === home || entry.startsWith(home + "/")))
|
|
5947
|
+
return true;
|
|
5948
|
+
if (entry === "/tmp" || entry.startsWith("/tmp/"))
|
|
5949
|
+
return true;
|
|
5950
|
+
if (entry === "/var/tmp" || entry.startsWith("/var/tmp/"))
|
|
5951
|
+
return true;
|
|
5952
|
+
try {
|
|
5953
|
+
if ((statSync(entry).mode & 2) !== 0)
|
|
5954
|
+
return true;
|
|
5955
|
+
} catch {}
|
|
5956
|
+
return false;
|
|
5957
|
+
}
|
|
5958
|
+
function sanitizeHookPath(pathValue, home) {
|
|
5959
|
+
const baseline = PATH_BASELINE.slice();
|
|
5960
|
+
if (process.platform === "darwin" && existsSync10("/opt/homebrew/bin"))
|
|
5961
|
+
baseline.push("/opt/homebrew/bin");
|
|
5962
|
+
const bunDir = dirname4(process.execPath);
|
|
5963
|
+
const seen = new Set;
|
|
5964
|
+
const out = [];
|
|
5965
|
+
for (const dir of baseline) {
|
|
5966
|
+
if (seen.has(dir))
|
|
5967
|
+
continue;
|
|
5968
|
+
seen.add(dir);
|
|
5969
|
+
out.push(dir);
|
|
5970
|
+
}
|
|
5971
|
+
if (!seen.has(bunDir)) {
|
|
5972
|
+
seen.add(bunDir);
|
|
5973
|
+
out.push(bunDir);
|
|
5974
|
+
}
|
|
5975
|
+
for (const entry of pathValue.split(":")) {
|
|
5976
|
+
if (seen.has(entry))
|
|
5977
|
+
continue;
|
|
5978
|
+
if (isUnsafePathEntry(entry, home))
|
|
5979
|
+
continue;
|
|
5980
|
+
seen.add(entry);
|
|
5981
|
+
out.push(entry);
|
|
5982
|
+
}
|
|
5983
|
+
return out.join(":");
|
|
5984
|
+
}
|
|
5932
5985
|
function buildHookEnv(source, extra) {
|
|
5933
5986
|
const out = {};
|
|
5934
5987
|
for (const name of ALLOWLIST) {
|
|
5988
|
+
if (name === "PATH")
|
|
5989
|
+
continue;
|
|
5935
5990
|
const value = source[name] ?? extra?.[name];
|
|
5936
5991
|
if (value !== undefined && value !== null)
|
|
5937
5992
|
out[name] = value;
|
|
5938
5993
|
}
|
|
5994
|
+
const pathOverride = extra?.PATH;
|
|
5995
|
+
if (pathOverride !== undefined && pathOverride !== null) {
|
|
5996
|
+
out.PATH = pathOverride;
|
|
5997
|
+
} else {
|
|
5998
|
+
const parentPath = source.PATH;
|
|
5999
|
+
if (parentPath !== undefined && parentPath !== null) {
|
|
6000
|
+
out.PATH = sanitizeHookPath(parentPath, source.HOME ?? extra?.HOME);
|
|
6001
|
+
}
|
|
6002
|
+
}
|
|
5939
6003
|
for (const [from, to] of CONFIG_PROJECTIONS) {
|
|
5940
6004
|
const value = source[from] ?? extra?.[from];
|
|
5941
6005
|
if (value !== undefined && value !== null)
|
|
@@ -5947,14 +6011,44 @@ function buildHookEnv(source, extra) {
|
|
|
5947
6011
|
continue;
|
|
5948
6012
|
if (isDeniedEnvName(name))
|
|
5949
6013
|
continue;
|
|
6014
|
+
if (isInterpreterInjectionEnvName(name))
|
|
6015
|
+
continue;
|
|
6016
|
+
if (isBashFunctionEnvName(name))
|
|
6017
|
+
continue;
|
|
5950
6018
|
if (value !== undefined && value !== null)
|
|
5951
6019
|
out[name] = value;
|
|
5952
6020
|
}
|
|
5953
6021
|
return out;
|
|
5954
6022
|
}
|
|
5955
|
-
var ALLOWLIST, DENY_SUFFIXES, DENY_PREFIXES, DENY_CONTAINS, CONFIG_PROJECTIONS;
|
|
6023
|
+
var ALLOWLIST, INTERPRETER_INJECTION, INTERPRETER_INJECTION_PREFIXES, DENY_SUFFIXES, DENY_PREFIXES, DENY_CONTAINS, PATH_BASELINE, CONFIG_PROJECTIONS;
|
|
5956
6024
|
var init_hook_env = __esm(() => {
|
|
5957
6025
|
ALLOWLIST = new Set(["PATH", "HOME", "LANG", "TZ", "SHELL", "TERM", "USER", "PWD"]);
|
|
6026
|
+
INTERPRETER_INJECTION = new Set([
|
|
6027
|
+
"BASH_ENV",
|
|
6028
|
+
"ENV",
|
|
6029
|
+
"BASHOPTS",
|
|
6030
|
+
"SHELLOPTS",
|
|
6031
|
+
"NODE_OPTIONS",
|
|
6032
|
+
"NODE_PATH",
|
|
6033
|
+
"PYTHONSTARTUP",
|
|
6034
|
+
"PYTHONINSPECT",
|
|
6035
|
+
"PYTHONPATH",
|
|
6036
|
+
"PYTHONHOME",
|
|
6037
|
+
"LD_PRELOAD",
|
|
6038
|
+
"LD_LIBRARY_PATH",
|
|
6039
|
+
"GCONV_PATH",
|
|
6040
|
+
"LOCPATH",
|
|
6041
|
+
"PERL5OPT",
|
|
6042
|
+
"RUBYOPT",
|
|
6043
|
+
"NODE_EXTRA_CA_CERTS",
|
|
6044
|
+
"NODE_TLS_REJECT_UNAUTHORIZED",
|
|
6045
|
+
"SSL_CERT_FILE",
|
|
6046
|
+
"SSL_CERT_DIR",
|
|
6047
|
+
"CURL_CA_BUNDLE",
|
|
6048
|
+
"GIT_SSL_CAINFO",
|
|
6049
|
+
"AWS_CA_BUNDLE"
|
|
6050
|
+
]);
|
|
6051
|
+
INTERPRETER_INJECTION_PREFIXES = ["GIT_CONFIG_"];
|
|
5958
6052
|
DENY_SUFFIXES = ["KEY", "TOKEN", "SECRET", "PASSWORD", "PASSWD", "CREDENTIAL", "CREDENTIALS", "URL", "URI"];
|
|
5959
6053
|
DENY_PREFIXES = [
|
|
5960
6054
|
"HASNA_",
|
|
@@ -5972,6 +6066,7 @@ var init_hook_env = __esm(() => {
|
|
|
5972
6066
|
"MEMENTOS_"
|
|
5973
6067
|
];
|
|
5974
6068
|
DENY_CONTAINS = ["DATABASE_URL"];
|
|
6069
|
+
PATH_BASELINE = ["/usr/bin", "/bin", "/usr/local/bin", "/sbin", "/usr/sbin"];
|
|
5975
6070
|
CONFIG_PROJECTIONS = [
|
|
5976
6071
|
["HASNA_HOOKS_DATA_DIR", "HOOKS_DATA_DIR"],
|
|
5977
6072
|
["HASNA_HOOKS_DB_PATH", "HOOKS_DB_PATH"],
|
|
@@ -5988,7 +6083,7 @@ __export(exports_run, {
|
|
|
5988
6083
|
});
|
|
5989
6084
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
5990
6085
|
import { rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
5991
|
-
import { basename as basename2, dirname as
|
|
6086
|
+
import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute3, join as join9 } from "path";
|
|
5992
6087
|
function extensionOf(scriptPath) {
|
|
5993
6088
|
const base = basename2(scriptPath);
|
|
5994
6089
|
const dot = base.lastIndexOf(".");
|
|
@@ -6015,13 +6110,13 @@ function interpreterFor(name, scriptPath, content) {
|
|
|
6015
6110
|
if (interp === "bash" || interp === "sh")
|
|
6016
6111
|
return { command: ["/bin/bash"], tempExt: "sh" };
|
|
6017
6112
|
if (interp === "node" || interp === "bun")
|
|
6018
|
-
return { command: [
|
|
6113
|
+
return { command: [process.execPath, "run"], tempExt: BUN_EXTENSIONS.has(ext) ? ext : "ts" };
|
|
6019
6114
|
throw new Error(`Refusing to run hook '${name}': shebang '${firstLine}' is not a recognized interpreter (supported: bash/sh, node/bun)`);
|
|
6020
6115
|
}
|
|
6021
6116
|
if (BASH_EXTENSIONS.has(ext))
|
|
6022
6117
|
return { command: ["/bin/bash"], tempExt: "sh" };
|
|
6023
6118
|
if (BUN_EXTENSIONS.has(ext))
|
|
6024
|
-
return { command: [
|
|
6119
|
+
return { command: [process.execPath, "run"], tempExt: ext };
|
|
6025
6120
|
throw new Error(`Refusing to run hook '${name}': unsupported script extension '.${ext}' (supported: .sh .bash .ts .tsx .js .jsx .mjs .cjs .mts .cts, or a recognized shebang)`);
|
|
6026
6121
|
}
|
|
6027
6122
|
async function readPipeWithDeadline(stream, deadlineMs) {
|
|
@@ -6057,7 +6152,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
6057
6152
|
return chunks.join("");
|
|
6058
6153
|
}
|
|
6059
6154
|
async function executeVerifiedScript(options) {
|
|
6060
|
-
const scriptDir =
|
|
6155
|
+
const scriptDir = dirname5(options.scriptPath);
|
|
6061
6156
|
const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
|
|
6062
6157
|
const tempPath = join9(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
|
|
6063
6158
|
let proc = null;
|
|
@@ -6068,7 +6163,7 @@ async function executeVerifiedScript(options) {
|
|
|
6068
6163
|
}, options.timeout) : null;
|
|
6069
6164
|
try {
|
|
6070
6165
|
writeFileSync6(tempPath, options.content, { flag: "wx", mode: 384 });
|
|
6071
|
-
proc = Bun.spawn([...interpreter.command,
|
|
6166
|
+
proc = Bun.spawn([...interpreter.command, isAbsolute3(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
|
|
6072
6167
|
stdin: new Response(options.stdin),
|
|
6073
6168
|
stdout: "pipe",
|
|
6074
6169
|
stderr: "pipe",
|
|
@@ -6150,12 +6245,12 @@ __export(exports_custom_install, {
|
|
|
6150
6245
|
installCustomSource: () => installCustomSource,
|
|
6151
6246
|
ensureCustomDir: () => ensureCustomDir
|
|
6152
6247
|
});
|
|
6153
|
-
import { existsSync as
|
|
6248
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync7, rmSync as rmSync4 } from "fs";
|
|
6154
6249
|
import { join as join10 } from "path";
|
|
6155
6250
|
import { tmpdir } from "os";
|
|
6156
6251
|
function manifestAt(dir) {
|
|
6157
6252
|
const path = join10(dir, "manifest.json");
|
|
6158
|
-
if (!
|
|
6253
|
+
if (!existsSync11(path))
|
|
6159
6254
|
return;
|
|
6160
6255
|
try {
|
|
6161
6256
|
return parseManifest(readFileSync7(path, "utf-8"));
|
|
@@ -6201,7 +6296,7 @@ function isCustomSource(value) {
|
|
|
6201
6296
|
return true;
|
|
6202
6297
|
if (value.startsWith("http://") || value.startsWith("https://"))
|
|
6203
6298
|
return true;
|
|
6204
|
-
return
|
|
6299
|
+
return existsSync11(value);
|
|
6205
6300
|
}
|
|
6206
6301
|
async function installCustomSource(source) {
|
|
6207
6302
|
if (isGitUrl(source)) {
|
|
@@ -6215,7 +6310,7 @@ async function installCustomSource(source) {
|
|
|
6215
6310
|
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
6216
6311
|
return installManifestUrl(source);
|
|
6217
6312
|
}
|
|
6218
|
-
if (!
|
|
6313
|
+
if (!existsSync11(source)) {
|
|
6219
6314
|
throw new Error(`No such local path: ${source}`);
|
|
6220
6315
|
}
|
|
6221
6316
|
if (source.endsWith("manifest.json")) {
|
|
@@ -7278,14 +7373,14 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
|
7278
7373
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7279
7374
|
import { z as z2 } from "zod";
|
|
7280
7375
|
import { createServer } from "http";
|
|
7281
|
-
import { existsSync as
|
|
7282
|
-
import { join as join11, dirname as
|
|
7376
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
7377
|
+
import { join as join11, dirname as dirname6 } from "path";
|
|
7283
7378
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7284
7379
|
async function runVerifiedHook(name, stdin, timeoutMs) {
|
|
7285
7380
|
const { resolveHook: resolveHook2, resolveScriptPath: resolveScriptPath2 } = await Promise.resolve().then(() => (init_resolve(), exports_resolve));
|
|
7286
7381
|
const resolved = resolveHook2(name);
|
|
7287
7382
|
const hookScript = resolveScriptPath2(name);
|
|
7288
|
-
if (!resolved || !hookScript || !
|
|
7383
|
+
if (!resolved || !hookScript || !existsSync12(hookScript)) {
|
|
7289
7384
|
throw new Error(`Hook '${name}' not found`);
|
|
7290
7385
|
}
|
|
7291
7386
|
const content = readFileSync9(hookScript);
|
|
@@ -7493,7 +7588,7 @@ function createHooksServer() {
|
|
|
7493
7588
|
const settingsPath = getSettingsPath(scope);
|
|
7494
7589
|
const issues = [];
|
|
7495
7590
|
const healthy = [];
|
|
7496
|
-
const settingsExist =
|
|
7591
|
+
const settingsExist = existsSync12(settingsPath);
|
|
7497
7592
|
if (!settingsExist) {
|
|
7498
7593
|
issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
|
|
7499
7594
|
}
|
|
@@ -7503,7 +7598,7 @@ function createHooksServer() {
|
|
|
7503
7598
|
const meta = custom ? resolveHookMeta(name) : getHook(name);
|
|
7504
7599
|
let hookHealthy = true;
|
|
7505
7600
|
if (custom) {
|
|
7506
|
-
if (!
|
|
7601
|
+
if (!existsSync12(custom.scriptPath)) {
|
|
7507
7602
|
issues.push({ hook: name, issue: `Custom hook script missing: ${custom.scriptPath}`, severity: "error" });
|
|
7508
7603
|
hookHealthy = false;
|
|
7509
7604
|
}
|
|
@@ -7513,7 +7608,7 @@ function createHooksServer() {
|
|
|
7513
7608
|
continue;
|
|
7514
7609
|
}
|
|
7515
7610
|
const hookDir = getHookPath(name);
|
|
7516
|
-
if (!
|
|
7611
|
+
if (!existsSync12(join11(hookDir, "src", "hook.ts"))) {
|
|
7517
7612
|
issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
|
|
7518
7613
|
hookHealthy = false;
|
|
7519
7614
|
}
|
|
@@ -7552,7 +7647,7 @@ function createHooksServer() {
|
|
|
7552
7647
|
const hookPath = getHookPath(name);
|
|
7553
7648
|
const readmePath = join11(hookPath, "README.md");
|
|
7554
7649
|
let readme = "";
|
|
7555
|
-
if (
|
|
7650
|
+
if (existsSync12(readmePath)) {
|
|
7556
7651
|
readme = readFileSync9(readmePath, "utf-8");
|
|
7557
7652
|
}
|
|
7558
7653
|
if (verbose) {
|
|
@@ -7736,7 +7831,7 @@ function createHooksServer() {
|
|
|
7736
7831
|
const ctx = {
|
|
7737
7832
|
scope,
|
|
7738
7833
|
settings_path: settingsPath,
|
|
7739
|
-
settings_exists:
|
|
7834
|
+
settings_exists: existsSync12(settingsPath),
|
|
7740
7835
|
registered_hooks: hooks,
|
|
7741
7836
|
hook_count: hooks.length,
|
|
7742
7837
|
healthy,
|
|
@@ -7882,7 +7977,7 @@ function createHooksServer() {
|
|
|
7882
7977
|
const settingsPath = getSettingsPath(scope);
|
|
7883
7978
|
let settings = {};
|
|
7884
7979
|
try {
|
|
7885
|
-
if (
|
|
7980
|
+
if (existsSync12(settingsPath))
|
|
7886
7981
|
settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
|
|
7887
7982
|
} catch {}
|
|
7888
7983
|
if (!settings.hooks)
|
|
@@ -7892,8 +7987,8 @@ function createHooksServer() {
|
|
|
7892
7987
|
disabled.push(name);
|
|
7893
7988
|
settings.hooks.__disabled = disabled;
|
|
7894
7989
|
const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync8 } = await import("fs");
|
|
7895
|
-
const { dirname:
|
|
7896
|
-
mkdirSync8(
|
|
7990
|
+
const { dirname: dirname7 } = await import("path");
|
|
7991
|
+
mkdirSync8(dirname7(settingsPath), { recursive: true });
|
|
7897
7992
|
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
7898
7993
|
`);
|
|
7899
7994
|
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
|
|
@@ -7905,7 +8000,7 @@ function createHooksServer() {
|
|
|
7905
8000
|
const settingsPath = getSettingsPath(scope);
|
|
7906
8001
|
let settings = {};
|
|
7907
8002
|
try {
|
|
7908
|
-
if (
|
|
8003
|
+
if (existsSync12(settingsPath))
|
|
7909
8004
|
settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
|
|
7910
8005
|
} catch {}
|
|
7911
8006
|
if (settings.hooks?.__disabled) {
|
|
@@ -8266,12 +8361,12 @@ var init_server = __esm(() => {
|
|
|
8266
8361
|
init_store();
|
|
8267
8362
|
init_redact();
|
|
8268
8363
|
init_storage();
|
|
8269
|
-
__dirname3 =
|
|
8364
|
+
__dirname3 = dirname6(fileURLToPath3(import.meta.url));
|
|
8270
8365
|
pkg = { name: "@hasna/hooks", version: "0.0.0" };
|
|
8271
8366
|
try {
|
|
8272
8367
|
for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
|
|
8273
8368
|
const p = join11(__dirname3, rel);
|
|
8274
|
-
if (
|
|
8369
|
+
if (existsSync12(p)) {
|
|
8275
8370
|
pkg = JSON.parse(readFileSync9(p, "utf-8"));
|
|
8276
8371
|
break;
|
|
8277
8372
|
}
|
|
@@ -8340,7 +8435,7 @@ function startMcpHttpServer(options) {
|
|
|
8340
8435
|
var DEFAULT_MCP_HTTP_PORT = 8847, MCP_HTTP_HOST = "127.0.0.1", MCP_SERVICE_NAME = "hooks";
|
|
8341
8436
|
var init_http = () => {};
|
|
8342
8437
|
|
|
8343
|
-
//
|
|
8438
|
+
// ../events/dist/commander.js
|
|
8344
8439
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
8345
8440
|
import { Buffer as Buffer2 } from "buffer";
|
|
8346
8441
|
import { existsSync } from "fs";
|
|
@@ -9525,7 +9620,7 @@ function replaySummary(events, deliveries, nextCursor) {
|
|
|
9525
9620
|
// src/cli/index.tsx
|
|
9526
9621
|
import { render } from "ink";
|
|
9527
9622
|
|
|
9528
|
-
// node_modules/commander/esm.mjs
|
|
9623
|
+
// ../../node_modules/.bun/commander@12.1.0/node_modules/commander/esm.mjs
|
|
9529
9624
|
var import__ = __toESM(require_commander(), 1);
|
|
9530
9625
|
var {
|
|
9531
9626
|
program,
|
|
@@ -9543,19 +9638,19 @@ var {
|
|
|
9543
9638
|
|
|
9544
9639
|
// src/cli/index.tsx
|
|
9545
9640
|
import chalk2 from "chalk";
|
|
9546
|
-
import { existsSync as
|
|
9547
|
-
import { join as join12, dirname as
|
|
9641
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10, rmSync as rmSync5 } from "fs";
|
|
9642
|
+
import { join as join12, dirname as dirname7 } from "path";
|
|
9548
9643
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
9549
9644
|
|
|
9550
9645
|
// src/cli/components/App.tsx
|
|
9551
9646
|
import { useState as useState7 } from "react";
|
|
9552
9647
|
import { Box as Box8, Text as Text10, useApp, useInput as useInput5 } from "ink";
|
|
9553
9648
|
|
|
9554
|
-
// node_modules/ink-select-input/build/Indicator.js
|
|
9649
|
+
// ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Indicator.js
|
|
9555
9650
|
import React from "react";
|
|
9556
9651
|
import { Box, Text } from "ink";
|
|
9557
9652
|
|
|
9558
|
-
// node_modules/is-unicode-supported/index.js
|
|
9653
|
+
// ../../node_modules/.bun/is-unicode-supported@2.1.0/node_modules/is-unicode-supported/index.js
|
|
9559
9654
|
import process2 from "process";
|
|
9560
9655
|
function isUnicodeSupported() {
|
|
9561
9656
|
const { env } = process2;
|
|
@@ -9566,7 +9661,7 @@ function isUnicodeSupported() {
|
|
|
9566
9661
|
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";
|
|
9567
9662
|
}
|
|
9568
9663
|
|
|
9569
|
-
// node_modules/figures/index.js
|
|
9664
|
+
// ../../node_modules/.bun/figures@6.1.0/node_modules/figures/index.js
|
|
9570
9665
|
var common = {
|
|
9571
9666
|
circleQuestionMark: "(?)",
|
|
9572
9667
|
questionMarkPrefix: "(?)",
|
|
@@ -9842,23 +9937,23 @@ var figures = shouldUseMain ? mainSymbols : fallbackSymbols;
|
|
|
9842
9937
|
var figures_default = figures;
|
|
9843
9938
|
var replacements = Object.entries(specialMainSymbols);
|
|
9844
9939
|
|
|
9845
|
-
// node_modules/ink-select-input/build/Indicator.js
|
|
9940
|
+
// ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Indicator.js
|
|
9846
9941
|
function Indicator({ isSelected = false }) {
|
|
9847
9942
|
return React.createElement(Box, { marginRight: 1 }, isSelected ? React.createElement(Text, { color: "blue" }, figures_default.pointer) : React.createElement(Text, null, " "));
|
|
9848
9943
|
}
|
|
9849
9944
|
var Indicator_default = Indicator;
|
|
9850
|
-
// node_modules/ink-select-input/build/Item.js
|
|
9945
|
+
// ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/Item.js
|
|
9851
9946
|
import * as React2 from "react";
|
|
9852
9947
|
import { Text as Text2 } from "ink";
|
|
9853
9948
|
function Item({ isSelected = false, label }) {
|
|
9854
9949
|
return React2.createElement(Text2, { color: isSelected ? "blue" : undefined }, label);
|
|
9855
9950
|
}
|
|
9856
9951
|
var Item_default = Item;
|
|
9857
|
-
// node_modules/ink-select-input/build/SelectInput.js
|
|
9952
|
+
// ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/SelectInput.js
|
|
9858
9953
|
import { isDeepStrictEqual } from "util";
|
|
9859
9954
|
import React3, { useState, useEffect, useRef, useCallback } from "react";
|
|
9860
9955
|
|
|
9861
|
-
// node_modules/to-rotated/index.js
|
|
9956
|
+
// ../../node_modules/.bun/to-rotated@1.0.0/node_modules/to-rotated/index.js
|
|
9862
9957
|
function toRotated(array, steps) {
|
|
9863
9958
|
if (!Array.isArray(array)) {
|
|
9864
9959
|
throw new TypeError(`Expected an array, got \`${typeof array}\`.`);
|
|
@@ -9880,7 +9975,7 @@ function toRotated(array, steps) {
|
|
|
9880
9975
|
];
|
|
9881
9976
|
}
|
|
9882
9977
|
|
|
9883
|
-
// node_modules/ink-select-input/build/SelectInput.js
|
|
9978
|
+
// ../../node_modules/.bun/ink-select-input@6.2.0+fa875fef9f949ebd/node_modules/ink-select-input/build/SelectInput.js
|
|
9884
9979
|
import { Box as Box2, useInput } from "ink";
|
|
9885
9980
|
function SelectInput({ items = [], isFocused = true, initialIndex = 0, indicatorComponent = Indicator_default, itemComponent = Item_default, limit: customLimit, onSelect, onHighlight }) {
|
|
9886
9981
|
const hasLimit = typeof customLimit === "number" && items.length > customLimit;
|
|
@@ -10176,7 +10271,7 @@ function HookSelect({
|
|
|
10176
10271
|
import { useState as useState4, useEffect as useEffect3 } from "react";
|
|
10177
10272
|
import { Box as Box6, Text as Text7, useInput as useInput4 } from "ink";
|
|
10178
10273
|
|
|
10179
|
-
// node_modules/ink-text-input/build/index.js
|
|
10274
|
+
// ../../node_modules/.bun/ink-text-input@6.0.0+fa875fef9f949ebd/node_modules/ink-text-input/build/index.js
|
|
10180
10275
|
import React5, { useState as useState3, useEffect as useEffect2 } from "react";
|
|
10181
10276
|
import { Text as Text6, useInput as useInput3 } from "ink";
|
|
10182
10277
|
import chalk from "chalk";
|
|
@@ -10354,7 +10449,7 @@ function SearchView({
|
|
|
10354
10449
|
import { useState as useState6, useEffect as useEffect5 } from "react";
|
|
10355
10450
|
import { Box as Box7, Text as Text9 } from "ink";
|
|
10356
10451
|
|
|
10357
|
-
// node_modules/ink-spinner/build/index.js
|
|
10452
|
+
// ../../node_modules/.bun/ink-spinner@5.0.0+fa875fef9f949ebd/node_modules/ink-spinner/build/index.js
|
|
10358
10453
|
var import_cli_spinners = __toESM(require_cli_spinners(), 1);
|
|
10359
10454
|
import React7, { useState as useState5, useEffect as useEffect4 } from "react";
|
|
10360
10455
|
import { Text as Text8 } from "ink";
|
|
@@ -10726,8 +10821,8 @@ init_resolve();
|
|
|
10726
10821
|
init_store();
|
|
10727
10822
|
init_semver();
|
|
10728
10823
|
import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
|
|
10729
|
-
var __dirname4 =
|
|
10730
|
-
var pkgPath =
|
|
10824
|
+
var __dirname4 = dirname7(fileURLToPath4(import.meta.url));
|
|
10825
|
+
var pkgPath = existsSync13(join12(__dirname4, "..", "package.json")) ? join12(__dirname4, "..", "package.json") : join12(__dirname4, "..", "..", "package.json");
|
|
10731
10826
|
var pkg2 = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
10732
10827
|
var program2 = new Command;
|
|
10733
10828
|
function resolveScope(options) {
|
|
@@ -10895,7 +10990,7 @@ program2.command("run").argument("<hook>", "Hook to run").option("--profile <id>
|
|
|
10895
10990
|
process.exit(1);
|
|
10896
10991
|
}
|
|
10897
10992
|
const hookScript = resolveScriptPath2(hook);
|
|
10898
|
-
if (!hookScript || !
|
|
10993
|
+
if (!hookScript || !existsSync13(hookScript)) {
|
|
10899
10994
|
console.error(JSON.stringify({ error: `Hook script not found: ${hookScript ?? hook}` }));
|
|
10900
10995
|
process.exit(1);
|
|
10901
10996
|
}
|
|
@@ -11467,7 +11562,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
11467
11562
|
const settingsPath = getSettingsPath(scope);
|
|
11468
11563
|
const issues = [];
|
|
11469
11564
|
const healthy = [];
|
|
11470
|
-
const settingsExist =
|
|
11565
|
+
const settingsExist = existsSync13(settingsPath);
|
|
11471
11566
|
if (!settingsExist) {
|
|
11472
11567
|
issues.push({ hook: "(settings)", issue: `${settingsPath} not found`, severity: "warning" });
|
|
11473
11568
|
}
|
|
@@ -11484,7 +11579,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
11484
11579
|
const meta = custom ? resolveHookMeta(name) : getHook(name);
|
|
11485
11580
|
let hookHealthy = true;
|
|
11486
11581
|
if (custom) {
|
|
11487
|
-
if (!
|
|
11582
|
+
if (!existsSync13(custom.scriptPath)) {
|
|
11488
11583
|
issues.push({ hook: name, issue: `Custom hook script missing: ${custom.scriptPath}`, severity: "error" });
|
|
11489
11584
|
hookHealthy = false;
|
|
11490
11585
|
}
|
|
@@ -11496,7 +11591,7 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
11496
11591
|
}
|
|
11497
11592
|
const hookDir = getHookPath(name);
|
|
11498
11593
|
const hookScript = join12(hookDir, "src", "hook.ts");
|
|
11499
|
-
if (!
|
|
11594
|
+
if (!existsSync13(hookScript)) {
|
|
11500
11595
|
issues.push({ hook: name, issue: "Missing src/hook.ts in package", severity: "error" });
|
|
11501
11596
|
hookHealthy = false;
|
|
11502
11597
|
}
|
|
@@ -11672,7 +11767,7 @@ program2.command("docs").argument("[hook]", "Hook name (shows general docs if om
|
|
|
11672
11767
|
const hookPath = getHookPath(hook);
|
|
11673
11768
|
const readmePath = join12(hookPath, "README.md");
|
|
11674
11769
|
let readme = "";
|
|
11675
|
-
if (
|
|
11770
|
+
if (existsSync13(readmePath)) {
|
|
11676
11771
|
readme = readFileSync10(readmePath, "utf-8");
|
|
11677
11772
|
}
|
|
11678
11773
|
if (options.json) {
|
package/bin/serve.js
CHANGED
|
@@ -527,7 +527,7 @@ function getHook(name) {
|
|
|
527
527
|
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "fs";
|
|
528
528
|
import { basename, dirname, isAbsolute, join as join2, normalize, resolve, sep } from "path";
|
|
529
529
|
|
|
530
|
-
// node_modules/zod/v3/external.js
|
|
530
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
531
531
|
var exports_external = {};
|
|
532
532
|
__export(exports_external, {
|
|
533
533
|
void: () => voidType,
|
|
@@ -639,7 +639,7 @@ __export(exports_external, {
|
|
|
639
639
|
BRAND: () => BRAND
|
|
640
640
|
});
|
|
641
641
|
|
|
642
|
-
// node_modules/zod/v3/helpers/util.js
|
|
642
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
643
643
|
var util;
|
|
644
644
|
(function(util2) {
|
|
645
645
|
util2.assertEqual = (_) => {};
|
|
@@ -770,7 +770,7 @@ var getParsedType = (data) => {
|
|
|
770
770
|
}
|
|
771
771
|
};
|
|
772
772
|
|
|
773
|
-
// node_modules/zod/v3/ZodError.js
|
|
773
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
|
|
774
774
|
var ZodIssueCode = util.arrayToEnum([
|
|
775
775
|
"invalid_type",
|
|
776
776
|
"invalid_literal",
|
|
@@ -889,7 +889,7 @@ ZodError.create = (issues) => {
|
|
|
889
889
|
return error;
|
|
890
890
|
};
|
|
891
891
|
|
|
892
|
-
// node_modules/zod/v3/locales/en.js
|
|
892
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
|
|
893
893
|
var errorMap = (issue, _ctx) => {
|
|
894
894
|
let message;
|
|
895
895
|
switch (issue.code) {
|
|
@@ -992,7 +992,7 @@ var errorMap = (issue, _ctx) => {
|
|
|
992
992
|
};
|
|
993
993
|
var en_default = errorMap;
|
|
994
994
|
|
|
995
|
-
// node_modules/zod/v3/errors.js
|
|
995
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
|
|
996
996
|
var overrideErrorMap = en_default;
|
|
997
997
|
function setErrorMap(map) {
|
|
998
998
|
overrideErrorMap = map;
|
|
@@ -1000,7 +1000,7 @@ function setErrorMap(map) {
|
|
|
1000
1000
|
function getErrorMap() {
|
|
1001
1001
|
return overrideErrorMap;
|
|
1002
1002
|
}
|
|
1003
|
-
// node_modules/zod/v3/helpers/parseUtil.js
|
|
1003
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
1004
1004
|
var makeIssue = (params) => {
|
|
1005
1005
|
const { data, path, errorMaps, issueData } = params;
|
|
1006
1006
|
const fullPath = [...path, ...issueData.path || []];
|
|
@@ -1106,14 +1106,14 @@ var isAborted = (x) => x.status === "aborted";
|
|
|
1106
1106
|
var isDirty = (x) => x.status === "dirty";
|
|
1107
1107
|
var isValid = (x) => x.status === "valid";
|
|
1108
1108
|
var isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
|
|
1109
|
-
// node_modules/zod/v3/helpers/errorUtil.js
|
|
1109
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
|
|
1110
1110
|
var errorUtil;
|
|
1111
1111
|
(function(errorUtil2) {
|
|
1112
1112
|
errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {};
|
|
1113
1113
|
errorUtil2.toString = (message) => typeof message === "string" ? message : message?.message;
|
|
1114
1114
|
})(errorUtil || (errorUtil = {}));
|
|
1115
1115
|
|
|
1116
|
-
// node_modules/zod/v3/types.js
|
|
1116
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
|
|
1117
1117
|
class ParseInputLazyPath {
|
|
1118
1118
|
constructor(parent, value, path, key) {
|
|
1119
1119
|
this._cachedPath = [];
|
|
@@ -4523,7 +4523,9 @@ function resolveApiKey() {
|
|
|
4523
4523
|
}
|
|
4524
4524
|
|
|
4525
4525
|
// src/lib/semver.ts
|
|
4526
|
-
var
|
|
4526
|
+
var NUMERIC_ID = "(0|[1-9]\\d{0,15})";
|
|
4527
|
+
var ALNUM_ID = "\\d*[A-Za-z-][0-9A-Za-z-]*";
|
|
4528
|
+
var SEMVER_PATTERN = new RegExp(`^${NUMERIC_ID}\\.${NUMERIC_ID}\\.${NUMERIC_ID}` + `(?:-(?:${NUMERIC_ID}|${ALNUM_ID})(?:\\.(?:${NUMERIC_ID}|${ALNUM_ID}))*)?` + `(?:\\+[0-9A-Za-z.-]+)?$`);
|
|
4527
4529
|
|
|
4528
4530
|
// src/lib/manifest.ts
|
|
4529
4531
|
var HOOK_NAME_RE = /^[\w-]+$/;
|
|
@@ -4535,7 +4537,8 @@ var manifestSchema = exports_external.object({
|
|
|
4535
4537
|
script: exports_external.string().min(1, "script must name a relative path or inline content"),
|
|
4536
4538
|
script_kind: exports_external.enum(["inline", "file"]).optional(),
|
|
4537
4539
|
args: exports_external.array(exports_external.string()).optional(),
|
|
4538
|
-
timeout_ms: exports_external.number().int().positive().optional()
|
|
4540
|
+
timeout_ms: exports_external.number().int().positive().optional(),
|
|
4541
|
+
env: exports_external.record(exports_external.string(), exports_external.string()).optional()
|
|
4539
4542
|
});
|
|
4540
4543
|
function parseManifest(json) {
|
|
4541
4544
|
const parsed = JSON.parse(json);
|
package/dist/index.js
CHANGED
|
@@ -453,7 +453,7 @@ var init_redact = __esm(() => {
|
|
|
453
453
|
];
|
|
454
454
|
});
|
|
455
455
|
|
|
456
|
-
// node_modules/zod/v3/helpers/util.js
|
|
456
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
457
457
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
458
458
|
const t = typeof data;
|
|
459
459
|
switch (t) {
|
|
@@ -584,7 +584,7 @@ var init_util = __esm(() => {
|
|
|
584
584
|
]);
|
|
585
585
|
});
|
|
586
586
|
|
|
587
|
-
// node_modules/zod/v3/ZodError.js
|
|
587
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
|
|
588
588
|
var ZodIssueCode, quotelessJson = (obj) => {
|
|
589
589
|
const json = JSON.stringify(obj, null, 2);
|
|
590
590
|
return json.replace(/"([^"]+)":/g, "$1:");
|
|
@@ -705,7 +705,7 @@ var init_ZodError = __esm(() => {
|
|
|
705
705
|
};
|
|
706
706
|
});
|
|
707
707
|
|
|
708
|
-
// node_modules/zod/v3/locales/en.js
|
|
708
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
|
|
709
709
|
var errorMap = (issue, _ctx) => {
|
|
710
710
|
let message;
|
|
711
711
|
switch (issue.code) {
|
|
@@ -812,7 +812,7 @@ var init_en = __esm(() => {
|
|
|
812
812
|
en_default = errorMap;
|
|
813
813
|
});
|
|
814
814
|
|
|
815
|
-
// node_modules/zod/v3/errors.js
|
|
815
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
|
|
816
816
|
function setErrorMap(map) {
|
|
817
817
|
overrideErrorMap = map;
|
|
818
818
|
}
|
|
@@ -825,7 +825,7 @@ var init_errors = __esm(() => {
|
|
|
825
825
|
overrideErrorMap = en_default;
|
|
826
826
|
});
|
|
827
827
|
|
|
828
|
-
// node_modules/zod/v3/helpers/parseUtil.js
|
|
828
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
829
829
|
function addIssueToContext(ctx, issueData) {
|
|
830
830
|
const overrideMap = getErrorMap();
|
|
831
831
|
const issue = makeIssue({
|
|
@@ -930,10 +930,10 @@ var init_parseUtil = __esm(() => {
|
|
|
930
930
|
});
|
|
931
931
|
});
|
|
932
932
|
|
|
933
|
-
// node_modules/zod/v3/helpers/typeAliases.js
|
|
933
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/typeAliases.js
|
|
934
934
|
var init_typeAliases = () => {};
|
|
935
935
|
|
|
936
|
-
// node_modules/zod/v3/helpers/errorUtil.js
|
|
936
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
|
|
937
937
|
var errorUtil;
|
|
938
938
|
var init_errorUtil = __esm(() => {
|
|
939
939
|
(function(errorUtil2) {
|
|
@@ -942,7 +942,7 @@ var init_errorUtil = __esm(() => {
|
|
|
942
942
|
})(errorUtil || (errorUtil = {}));
|
|
943
943
|
});
|
|
944
944
|
|
|
945
|
-
// node_modules/zod/v3/types.js
|
|
945
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
|
|
946
946
|
class ParseInputLazyPath {
|
|
947
947
|
constructor(parent, value, path, key) {
|
|
948
948
|
this._cachedPath = [];
|
|
@@ -4293,7 +4293,7 @@ var init_types = __esm(() => {
|
|
|
4293
4293
|
NEVER = INVALID;
|
|
4294
4294
|
});
|
|
4295
4295
|
|
|
4296
|
-
// node_modules/zod/v3/external.js
|
|
4296
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
4297
4297
|
var exports_external = {};
|
|
4298
4298
|
__export(exports_external, {
|
|
4299
4299
|
void: () => voidType,
|
|
@@ -4413,7 +4413,7 @@ var init_external = __esm(() => {
|
|
|
4413
4413
|
init_ZodError();
|
|
4414
4414
|
});
|
|
4415
4415
|
|
|
4416
|
-
// node_modules/zod/index.js
|
|
4416
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/index.js
|
|
4417
4417
|
var init_zod = __esm(() => {
|
|
4418
4418
|
init_external();
|
|
4419
4419
|
init_external();
|
|
@@ -4474,9 +4474,9 @@ function resolveApiKey() {
|
|
|
4474
4474
|
var init_config = () => {};
|
|
4475
4475
|
|
|
4476
4476
|
// src/lib/semver.ts
|
|
4477
|
-
var SEMVER_PATTERN;
|
|
4477
|
+
var NUMERIC_ID = "(0|[1-9]\\d{0,15})", ALNUM_ID = "\\d*[A-Za-z-][0-9A-Za-z-]*", SEMVER_PATTERN;
|
|
4478
4478
|
var init_semver = __esm(() => {
|
|
4479
|
-
SEMVER_PATTERN =
|
|
4479
|
+
SEMVER_PATTERN = new RegExp(`^${NUMERIC_ID}\\.${NUMERIC_ID}\\.${NUMERIC_ID}` + `(?:-(?:${NUMERIC_ID}|${ALNUM_ID})(?:\\.(?:${NUMERIC_ID}|${ALNUM_ID}))*)?` + `(?:\\+[0-9A-Za-z.-]+)?$`);
|
|
4480
4480
|
});
|
|
4481
4481
|
|
|
4482
4482
|
// src/lib/manifest.ts
|
|
@@ -4638,7 +4638,8 @@ var init_manifest = __esm(() => {
|
|
|
4638
4638
|
script: exports_external.string().min(1, "script must name a relative path or inline content"),
|
|
4639
4639
|
script_kind: exports_external.enum(["inline", "file"]).optional(),
|
|
4640
4640
|
args: exports_external.array(exports_external.string()).optional(),
|
|
4641
|
-
timeout_ms: exports_external.number().int().positive().optional()
|
|
4641
|
+
timeout_ms: exports_external.number().int().positive().optional(),
|
|
4642
|
+
env: exports_external.record(exports_external.string(), exports_external.string()).optional()
|
|
4642
4643
|
});
|
|
4643
4644
|
ScriptContainmentError = class ScriptContainmentError extends Error {
|
|
4644
4645
|
constructor(path, hookDir, raw) {
|
|
@@ -5001,6 +5002,8 @@ var init_profiles = __esm(() => {
|
|
|
5001
5002
|
});
|
|
5002
5003
|
|
|
5003
5004
|
// src/lib/hook-env.ts
|
|
5005
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2 } from "path";
|
|
5006
|
+
import { existsSync as existsSync10, statSync } from "fs";
|
|
5004
5007
|
function isDeniedEnvName(name) {
|
|
5005
5008
|
const upper = name.toUpperCase();
|
|
5006
5009
|
if (DENY_PREFIXES.some((prefix) => upper.startsWith(prefix)))
|
|
@@ -5009,13 +5012,74 @@ function isDeniedEnvName(name) {
|
|
|
5009
5012
|
return true;
|
|
5010
5013
|
return DENY_CONTAINS.some((token) => upper.includes(token));
|
|
5011
5014
|
}
|
|
5015
|
+
function isInterpreterInjectionEnvName(name) {
|
|
5016
|
+
if (INTERPRETER_INJECTION.has(name))
|
|
5017
|
+
return true;
|
|
5018
|
+
return INTERPRETER_INJECTION_PREFIXES.some((prefix) => name.startsWith(prefix));
|
|
5019
|
+
}
|
|
5020
|
+
function isBashFunctionEnvName(name) {
|
|
5021
|
+
return name.startsWith("BASH_FUNC_");
|
|
5022
|
+
}
|
|
5023
|
+
function isUnsafePathEntry(entry, home) {
|
|
5024
|
+
if (entry === "" || !isAbsolute2(entry))
|
|
5025
|
+
return true;
|
|
5026
|
+
if (home && (entry === home || entry.startsWith(home + "/")))
|
|
5027
|
+
return true;
|
|
5028
|
+
if (entry === "/tmp" || entry.startsWith("/tmp/"))
|
|
5029
|
+
return true;
|
|
5030
|
+
if (entry === "/var/tmp" || entry.startsWith("/var/tmp/"))
|
|
5031
|
+
return true;
|
|
5032
|
+
try {
|
|
5033
|
+
if ((statSync(entry).mode & 2) !== 0)
|
|
5034
|
+
return true;
|
|
5035
|
+
} catch {}
|
|
5036
|
+
return false;
|
|
5037
|
+
}
|
|
5038
|
+
function sanitizeHookPath(pathValue, home) {
|
|
5039
|
+
const baseline = PATH_BASELINE.slice();
|
|
5040
|
+
if (process.platform === "darwin" && existsSync10("/opt/homebrew/bin"))
|
|
5041
|
+
baseline.push("/opt/homebrew/bin");
|
|
5042
|
+
const bunDir = dirname4(process.execPath);
|
|
5043
|
+
const seen = new Set;
|
|
5044
|
+
const out = [];
|
|
5045
|
+
for (const dir of baseline) {
|
|
5046
|
+
if (seen.has(dir))
|
|
5047
|
+
continue;
|
|
5048
|
+
seen.add(dir);
|
|
5049
|
+
out.push(dir);
|
|
5050
|
+
}
|
|
5051
|
+
if (!seen.has(bunDir)) {
|
|
5052
|
+
seen.add(bunDir);
|
|
5053
|
+
out.push(bunDir);
|
|
5054
|
+
}
|
|
5055
|
+
for (const entry of pathValue.split(":")) {
|
|
5056
|
+
if (seen.has(entry))
|
|
5057
|
+
continue;
|
|
5058
|
+
if (isUnsafePathEntry(entry, home))
|
|
5059
|
+
continue;
|
|
5060
|
+
seen.add(entry);
|
|
5061
|
+
out.push(entry);
|
|
5062
|
+
}
|
|
5063
|
+
return out.join(":");
|
|
5064
|
+
}
|
|
5012
5065
|
function buildHookEnv(source, extra) {
|
|
5013
5066
|
const out = {};
|
|
5014
5067
|
for (const name of ALLOWLIST) {
|
|
5068
|
+
if (name === "PATH")
|
|
5069
|
+
continue;
|
|
5015
5070
|
const value = source[name] ?? extra?.[name];
|
|
5016
5071
|
if (value !== undefined && value !== null)
|
|
5017
5072
|
out[name] = value;
|
|
5018
5073
|
}
|
|
5074
|
+
const pathOverride = extra?.PATH;
|
|
5075
|
+
if (pathOverride !== undefined && pathOverride !== null) {
|
|
5076
|
+
out.PATH = pathOverride;
|
|
5077
|
+
} else {
|
|
5078
|
+
const parentPath = source.PATH;
|
|
5079
|
+
if (parentPath !== undefined && parentPath !== null) {
|
|
5080
|
+
out.PATH = sanitizeHookPath(parentPath, source.HOME ?? extra?.HOME);
|
|
5081
|
+
}
|
|
5082
|
+
}
|
|
5019
5083
|
for (const [from, to] of CONFIG_PROJECTIONS) {
|
|
5020
5084
|
const value = source[from] ?? extra?.[from];
|
|
5021
5085
|
if (value !== undefined && value !== null)
|
|
@@ -5027,14 +5091,44 @@ function buildHookEnv(source, extra) {
|
|
|
5027
5091
|
continue;
|
|
5028
5092
|
if (isDeniedEnvName(name))
|
|
5029
5093
|
continue;
|
|
5094
|
+
if (isInterpreterInjectionEnvName(name))
|
|
5095
|
+
continue;
|
|
5096
|
+
if (isBashFunctionEnvName(name))
|
|
5097
|
+
continue;
|
|
5030
5098
|
if (value !== undefined && value !== null)
|
|
5031
5099
|
out[name] = value;
|
|
5032
5100
|
}
|
|
5033
5101
|
return out;
|
|
5034
5102
|
}
|
|
5035
|
-
var ALLOWLIST, DENY_SUFFIXES, DENY_PREFIXES, DENY_CONTAINS, CONFIG_PROJECTIONS;
|
|
5103
|
+
var ALLOWLIST, INTERPRETER_INJECTION, INTERPRETER_INJECTION_PREFIXES, DENY_SUFFIXES, DENY_PREFIXES, DENY_CONTAINS, PATH_BASELINE, CONFIG_PROJECTIONS;
|
|
5036
5104
|
var init_hook_env = __esm(() => {
|
|
5037
5105
|
ALLOWLIST = new Set(["PATH", "HOME", "LANG", "TZ", "SHELL", "TERM", "USER", "PWD"]);
|
|
5106
|
+
INTERPRETER_INJECTION = new Set([
|
|
5107
|
+
"BASH_ENV",
|
|
5108
|
+
"ENV",
|
|
5109
|
+
"BASHOPTS",
|
|
5110
|
+
"SHELLOPTS",
|
|
5111
|
+
"NODE_OPTIONS",
|
|
5112
|
+
"NODE_PATH",
|
|
5113
|
+
"PYTHONSTARTUP",
|
|
5114
|
+
"PYTHONINSPECT",
|
|
5115
|
+
"PYTHONPATH",
|
|
5116
|
+
"PYTHONHOME",
|
|
5117
|
+
"LD_PRELOAD",
|
|
5118
|
+
"LD_LIBRARY_PATH",
|
|
5119
|
+
"GCONV_PATH",
|
|
5120
|
+
"LOCPATH",
|
|
5121
|
+
"PERL5OPT",
|
|
5122
|
+
"RUBYOPT",
|
|
5123
|
+
"NODE_EXTRA_CA_CERTS",
|
|
5124
|
+
"NODE_TLS_REJECT_UNAUTHORIZED",
|
|
5125
|
+
"SSL_CERT_FILE",
|
|
5126
|
+
"SSL_CERT_DIR",
|
|
5127
|
+
"CURL_CA_BUNDLE",
|
|
5128
|
+
"GIT_SSL_CAINFO",
|
|
5129
|
+
"AWS_CA_BUNDLE"
|
|
5130
|
+
]);
|
|
5131
|
+
INTERPRETER_INJECTION_PREFIXES = ["GIT_CONFIG_"];
|
|
5038
5132
|
DENY_SUFFIXES = ["KEY", "TOKEN", "SECRET", "PASSWORD", "PASSWD", "CREDENTIAL", "CREDENTIALS", "URL", "URI"];
|
|
5039
5133
|
DENY_PREFIXES = [
|
|
5040
5134
|
"HASNA_",
|
|
@@ -5052,6 +5146,7 @@ var init_hook_env = __esm(() => {
|
|
|
5052
5146
|
"MEMENTOS_"
|
|
5053
5147
|
];
|
|
5054
5148
|
DENY_CONTAINS = ["DATABASE_URL"];
|
|
5149
|
+
PATH_BASELINE = ["/usr/bin", "/bin", "/usr/local/bin", "/sbin", "/usr/sbin"];
|
|
5055
5150
|
CONFIG_PROJECTIONS = [
|
|
5056
5151
|
["HASNA_HOOKS_DATA_DIR", "HOOKS_DATA_DIR"],
|
|
5057
5152
|
["HASNA_HOOKS_DB_PATH", "HOOKS_DB_PATH"],
|
|
@@ -5068,7 +5163,7 @@ __export(exports_run, {
|
|
|
5068
5163
|
});
|
|
5069
5164
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
5070
5165
|
import { rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
5071
|
-
import { basename as basename2, dirname as
|
|
5166
|
+
import { basename as basename2, dirname as dirname5, isAbsolute as isAbsolute3, join as join9 } from "path";
|
|
5072
5167
|
function extensionOf(scriptPath) {
|
|
5073
5168
|
const base = basename2(scriptPath);
|
|
5074
5169
|
const dot = base.lastIndexOf(".");
|
|
@@ -5095,13 +5190,13 @@ function interpreterFor(name, scriptPath, content) {
|
|
|
5095
5190
|
if (interp === "bash" || interp === "sh")
|
|
5096
5191
|
return { command: ["/bin/bash"], tempExt: "sh" };
|
|
5097
5192
|
if (interp === "node" || interp === "bun")
|
|
5098
|
-
return { command: [
|
|
5193
|
+
return { command: [process.execPath, "run"], tempExt: BUN_EXTENSIONS.has(ext) ? ext : "ts" };
|
|
5099
5194
|
throw new Error(`Refusing to run hook '${name}': shebang '${firstLine}' is not a recognized interpreter (supported: bash/sh, node/bun)`);
|
|
5100
5195
|
}
|
|
5101
5196
|
if (BASH_EXTENSIONS.has(ext))
|
|
5102
5197
|
return { command: ["/bin/bash"], tempExt: "sh" };
|
|
5103
5198
|
if (BUN_EXTENSIONS.has(ext))
|
|
5104
|
-
return { command: [
|
|
5199
|
+
return { command: [process.execPath, "run"], tempExt: ext };
|
|
5105
5200
|
throw new Error(`Refusing to run hook '${name}': unsupported script extension '.${ext}' (supported: .sh .bash .ts .tsx .js .jsx .mjs .cjs .mts .cts, or a recognized shebang)`);
|
|
5106
5201
|
}
|
|
5107
5202
|
async function readPipeWithDeadline(stream, deadlineMs) {
|
|
@@ -5137,7 +5232,7 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
5137
5232
|
return chunks.join("");
|
|
5138
5233
|
}
|
|
5139
5234
|
async function executeVerifiedScript(options) {
|
|
5140
|
-
const scriptDir =
|
|
5235
|
+
const scriptDir = dirname5(options.scriptPath);
|
|
5141
5236
|
const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
|
|
5142
5237
|
const tempPath = join9(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
|
|
5143
5238
|
let proc = null;
|
|
@@ -5148,7 +5243,7 @@ async function executeVerifiedScript(options) {
|
|
|
5148
5243
|
}, options.timeout) : null;
|
|
5149
5244
|
try {
|
|
5150
5245
|
writeFileSync6(tempPath, options.content, { flag: "wx", mode: 384 });
|
|
5151
|
-
proc = Bun.spawn([...interpreter.command,
|
|
5246
|
+
proc = Bun.spawn([...interpreter.command, isAbsolute3(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
|
|
5152
5247
|
stdin: new Response(options.stdin),
|
|
5153
5248
|
stdout: "pipe",
|
|
5154
5249
|
stderr: "pipe",
|
|
@@ -6964,7 +7059,7 @@ init_profiles();
|
|
|
6964
7059
|
init_config();
|
|
6965
7060
|
init_store();
|
|
6966
7061
|
init_manifest();
|
|
6967
|
-
import { existsSync as
|
|
7062
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
6968
7063
|
|
|
6969
7064
|
// src/lib/custom-install.ts
|
|
6970
7065
|
init_config();
|
|
@@ -7594,7 +7689,7 @@ async function runHook(name, input, options = {}) {
|
|
|
7594
7689
|
throw new Error(`Hook '${name}' not found`);
|
|
7595
7690
|
const hookScript = getHookPath(name) + "/src/hook.ts";
|
|
7596
7691
|
const resolvedScript = custom2?.scriptPath;
|
|
7597
|
-
const script = resolvedScript ?? (
|
|
7692
|
+
const script = resolvedScript ?? (existsSync11(hookScript) ? hookScript : undefined);
|
|
7598
7693
|
if (!script)
|
|
7599
7694
|
throw new Error(`Hook script not found: ${name}`);
|
|
7600
7695
|
const { sha256Of: sha256Of2, checkScriptHash: checkScriptHash2 } = await Promise.resolve().then(() => (init_store(), exports_store));
|
|
@@ -7629,6 +7724,7 @@ async function runHook(name, input, options = {}) {
|
|
|
7629
7724
|
scriptPath: script,
|
|
7630
7725
|
content,
|
|
7631
7726
|
args: custom2?.manifest.args ?? [],
|
|
7727
|
+
env: custom2?.manifest.env,
|
|
7632
7728
|
stdin: JSON.stringify(hookInput),
|
|
7633
7729
|
timeout: effectiveTimeout ?? undefined
|
|
7634
7730
|
}));
|
package/dist/lib/hook-env.d.ts
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* parent's process.env wholesale hands every credential the agent can reach
|
|
7
7
|
* to arbitrary code. The child gets:
|
|
8
8
|
*
|
|
9
|
-
* 1. a fixed allowlist of non-secret session variables (
|
|
10
|
-
*
|
|
9
|
+
* 1. a fixed allowlist of non-secret session variables (HOME, LANG, TZ,
|
|
10
|
+
* SHELL, TERM, USER, PWD) plus PATH — PATH rebuilt from a trusted
|
|
11
|
+
* baseline with writable-directory entries removed (or a per-hook
|
|
12
|
+
* manifest env.PATH override, verbatim);
|
|
11
13
|
* 2. non-secret HOOKS_* projections of the parent's HASNA_HOOKS_* config
|
|
12
14
|
* (data dir, DB path, lock path, config path) so in-process DB/config
|
|
13
15
|
* resolution inside the hook still finds the store the parent configured;
|
|
@@ -24,6 +26,20 @@
|
|
|
24
26
|
* ANTHROPIC_, POSTGRES_, MYSQL_, REDIS_, MONGO_, MEMENTOS_
|
|
25
27
|
* contains: DATABASE_URL (any variant)
|
|
26
28
|
*
|
|
29
|
+
* The deny list alone is NOT the whole boundary (bug cf99cf76): it strips
|
|
30
|
+
* credential-shaped NAMES from the parent env, but a credential can be
|
|
31
|
+
* re-imported from a FILE through interpreter machinery. BASH_ENV tells bash
|
|
32
|
+
* to source a file before running any non-interactive script, and ENV does
|
|
33
|
+
* the same for interactive shells — a parent whose BASH_ENV points at e.g.
|
|
34
|
+
* hasna-cloud-env.sh hands the hook child a process that re-exports the
|
|
35
|
+
* fleet credential env AFTER the deny list ran. The interpreter-injection
|
|
36
|
+
* set below strips every variable that makes a child interpreter run or
|
|
37
|
+
* source code the hook did not ask for (sourcing vectors, interpreter option
|
|
38
|
+
* lists, module/startup injection, dynamic-loader preloads, TLS-trust
|
|
39
|
+
* redirection, gconv/locale module injection). Exact case-sensitive names:
|
|
40
|
+
* POSIX interpreters read these literals, and a differently-cased variant is
|
|
41
|
+
* an inert, unrelated variable.
|
|
42
|
+
*
|
|
27
43
|
* Names that merely resemble credentials (e.g. HOOKS_DATA_DIR) survive; a
|
|
28
44
|
* path is not a credential.
|
|
29
45
|
*/
|
|
@@ -32,11 +48,51 @@
|
|
|
32
48
|
* hook child process. Case-insensitive.
|
|
33
49
|
*/
|
|
34
50
|
export declare function isDeniedEnvName(name: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* True when a variable NAME is interpreter-injection (bug cf99cf76): it can
|
|
53
|
+
* make a child interpreter run or source code the hook did not ask for.
|
|
54
|
+
* Exact case-sensitive match — interpreters read these literals verbatim.
|
|
55
|
+
*/
|
|
56
|
+
export declare function isInterpreterInjectionEnvName(name: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* True when a variable NAME is a bash exported-function entry
|
|
59
|
+
* (BASH_FUNC_<name>%%): bash imports EXPORTED FUNCTIONS from the environment
|
|
60
|
+
* into the child shell, where they shadow commands (env/cat/git/node) and
|
|
61
|
+
* run attacker code on the hook's first command. The %% suffix is bash's
|
|
62
|
+
* export encoding; the prefix test covers every form (%% , single %, none).
|
|
63
|
+
*
|
|
64
|
+
* Aliases are deliberately NOT denied: bash cannot import aliases from the
|
|
65
|
+
* environment (BASH_ALIASES is an associative array and is never exported) —
|
|
66
|
+
* functions are the only env-importable code vector for bash, so the strip
|
|
67
|
+
* is precisely the BASH_FUNC_* prefix.
|
|
68
|
+
*/
|
|
69
|
+
export declare function isBashFunctionEnvName(name: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* True when a PATH entry may hold attacker-controlled executables: an empty
|
|
72
|
+
* or relative entry (resolves against the child's cwd), an entry under the
|
|
73
|
+
* user's home, an entry under a shared tmp tree, or a world-writable
|
|
74
|
+
* directory (any user can drop a fake binary there).
|
|
75
|
+
*/
|
|
76
|
+
export declare function isUnsafePathEntry(entry: string, home: string | undefined): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Rebuild a PATH for the hook child from a trusted baseline: the system
|
|
79
|
+
* directories (plus /opt/homebrew/bin on macOS when present) and the
|
|
80
|
+
* runner's own bun directory (the interpreter the hook runs under, trusted
|
|
81
|
+
* by definition even when it lives under $HOME). Parent entries are kept
|
|
82
|
+
* only when they do not live under $HOME, /tmp, /var/tmp, or a world-writable
|
|
83
|
+
* path — a fake `node`/`git` planted in a writable directory must never
|
|
84
|
+
* execute on the hook's first command. An explicit per-hook env.PATH
|
|
85
|
+
* (manifest) override bypasses this entirely: the hook author declares the
|
|
86
|
+
* complete PATH it needs.
|
|
87
|
+
*/
|
|
88
|
+
export declare function sanitizeHookPath(pathValue: string, home: string | undefined): string;
|
|
35
89
|
/**
|
|
36
90
|
* Build the sanitized environment for a hook child process.
|
|
37
91
|
*
|
|
38
92
|
* `source` is the parent's environment; `extra` is any caller-supplied env
|
|
39
|
-
* the hook may need. The deny list
|
|
40
|
-
* reintroduce a credential-bearing name
|
|
93
|
+
* the hook may need. The deny list AND the interpreter-injection set apply
|
|
94
|
+
* to BOTH — a caller cannot reintroduce a credential-bearing name, and
|
|
95
|
+
* cannot reintroduce a variable that would make the child's interpreter
|
|
96
|
+
* source or run code (bug cf99cf76).
|
|
41
97
|
*/
|
|
42
98
|
export declare function buildHookEnv(source: Record<string, string | undefined>, extra?: Record<string, string | undefined>): Record<string, string>;
|
package/dist/lib/manifest.d.ts
CHANGED
|
@@ -18,11 +18,21 @@ export declare const manifestSchema: z.ZodObject<{
|
|
|
18
18
|
script_kind: z.ZodOptional<z.ZodEnum<["inline", "file"]>>;
|
|
19
19
|
args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
20
20
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
21
|
+
/**
|
|
22
|
+
* Per-hook environment passed to the hook child (reviewer efcad315).
|
|
23
|
+
* Every name is still filtered through the credential deny list and the
|
|
24
|
+
* interpreter-injection strip; `env.PATH` here is the documented way to
|
|
25
|
+
* override the sanitized default PATH — it is passed verbatim, bypassing
|
|
26
|
+
* PATH sanitization (the hook author declares the complete PATH the hook
|
|
27
|
+
* needs).
|
|
28
|
+
*/
|
|
29
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
21
30
|
}, "strip", z.ZodTypeAny, {
|
|
22
31
|
version: string;
|
|
23
32
|
events: string[];
|
|
24
33
|
name: string;
|
|
25
34
|
script: string;
|
|
35
|
+
env?: Record<string, string> | undefined;
|
|
26
36
|
description?: string | undefined;
|
|
27
37
|
script_kind?: "inline" | "file" | undefined;
|
|
28
38
|
args?: string[] | undefined;
|
|
@@ -32,6 +42,7 @@ export declare const manifestSchema: z.ZodObject<{
|
|
|
32
42
|
events: string[];
|
|
33
43
|
name: string;
|
|
34
44
|
script: string;
|
|
45
|
+
env?: Record<string, string> | undefined;
|
|
35
46
|
description?: string | undefined;
|
|
36
47
|
script_kind?: "inline" | "file" | undefined;
|
|
37
48
|
args?: string[] | undefined;
|
package/dist/lib/semver.d.ts
CHANGED
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
* while the artifact routes demanded a bare `\d+\.\d+\.\d+` at the end of
|
|
8
8
|
* the path, so a pinned prerelease install 404'd. This is the one pattern:
|
|
9
9
|
* full semver with optional prerelease and build metadata.
|
|
10
|
+
*
|
|
11
|
+
* Numeric identifiers are STRICT (reviewer efcad315): per semver.org §2,
|
|
12
|
+
* numeric identifiers MUST NOT carry leading zeroes ("1.0.0-01" is not
|
|
13
|
+
* "1.0.0-1" — it is not semver at all), and identifiers longer than 16
|
|
14
|
+
* digits are rejected as invalid so no surface can ever store a version the
|
|
15
|
+
* comparator cannot order exactly (Number() precision ends at
|
|
16
|
+
* MAX_SAFE_INTEGER; the comparator itself uses BigInt, see compareVersions).
|
|
10
17
|
*/
|
|
11
18
|
export declare const SEMVER_PATTERN: RegExp;
|
|
12
19
|
/**
|
|
@@ -17,3 +24,23 @@ export declare const SEMVER_PATTERN: RegExp;
|
|
|
17
24
|
export declare const SEMVER_PATH_SEGMENT = "[0-9A-Za-z.+_-]+";
|
|
18
25
|
export declare function isValidSemver(value: string): boolean;
|
|
19
26
|
export declare function semverError(value: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Full semver precedence (semver.org §11), shared by every surface that
|
|
29
|
+
* must ORDER published versions — the registry latest pointer (bug
|
|
30
|
+
* 6e412e52) compares with this and nothing else.
|
|
31
|
+
*
|
|
32
|
+
* Returns <0 when a sorts before b, 0 when equal, >0 when a sorts after b.
|
|
33
|
+
* Build metadata never participates. Prerelease identifiers compare by the
|
|
34
|
+
* spec's rules: numeric identifiers numerically, numeric before alphanumeric,
|
|
35
|
+
* fewer identifiers before more, release before any prerelease.
|
|
36
|
+
*
|
|
37
|
+
* Numeric identifiers compare as BigInt (reviewer efcad315): Number()
|
|
38
|
+
* loses precision past MAX_SAFE_INTEGER, which made near-16-digit numeric
|
|
39
|
+
* identifiers compare equal and made "1.0.0-01" vs "1.0.0-1" compare >0 in
|
|
40
|
+
* BOTH directions (Number("01") === Number("1") while the strings differ).
|
|
41
|
+
* BigInt keeps the comparison exact and antisymmetric at any length; the
|
|
42
|
+
* validation pattern rejects leading-zero and >16-digit identifiers as
|
|
43
|
+
* invalid, and this comparator stays correct even if an old invalid value
|
|
44
|
+
* ever reaches it.
|
|
45
|
+
*/
|
|
46
|
+
export declare function compareVersions(a: string, b: string): number;
|
package/package.json
CHANGED