@hasna/hooks 0.10.6 → 0.10.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/README.md +82 -1
- package/bin/hooks-mcp.js +100 -44
- package/bin/index.js +7835 -6947
- package/bin/native-safety-entry.js +196 -15
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1082 -68
- package/dist/lib/claude-safety-check.d.ts +32 -0
- package/dist/lib/codex-safety-check.d.ts +58 -0
- package/dist/lib/codex-safety-trust.d.ts +54 -0
- package/dist/lib/codex-settings.d.ts +5 -1
- package/dist/lib/installer.d.ts +8 -0
- package/dist/lib/native-safety-registration.d.ts +52 -0
- package/dist/lib/native-safety.d.ts +4 -0
- package/dist/native-safety.d.ts +6 -0
- package/dist/native-safety.js +18 -0
- package/hooks/hook-workspace-repos-guard/README.md +12 -0
- package/hooks/hook-workspace-repos-guard/src/hook.ts +247 -14
- package/package.json +2 -2
- package/scripts/ensure-profiles-dir.mjs +65 -1
|
@@ -1179,6 +1179,126 @@ var OUTPUT_REDIRECT = /(?:\d+|&)?(?:>>|>\||>&|<>|>)\s*([^\s;&|<>()\x60]*)/g;
|
|
|
1179
1179
|
function withoutFdDuplication(segment) {
|
|
1180
1180
|
return segment.replace(FD_DUPLICATION, " ");
|
|
1181
1181
|
}
|
|
1182
|
+
function pipelineStages(segment) {
|
|
1183
|
+
const stages = [];
|
|
1184
|
+
let quote = null;
|
|
1185
|
+
let parens = 0;
|
|
1186
|
+
let braces = 0;
|
|
1187
|
+
let backtick = false;
|
|
1188
|
+
let start = 0;
|
|
1189
|
+
for (let i = 0;i < segment.length; i++) {
|
|
1190
|
+
const ch = segment[i];
|
|
1191
|
+
if (quote === "'") {
|
|
1192
|
+
if (ch === "'")
|
|
1193
|
+
quote = null;
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
if (ch === "\\") {
|
|
1197
|
+
i++;
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
if (ch === "(") {
|
|
1201
|
+
parens++;
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
if (ch === ")") {
|
|
1205
|
+
if (--parens < 0)
|
|
1206
|
+
return [segment];
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
if (ch === "$" && segment[i + 1] === "{") {
|
|
1210
|
+
braces++;
|
|
1211
|
+
i++;
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
if (ch === "}" && braces > 0) {
|
|
1215
|
+
braces--;
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
if (ch === "`") {
|
|
1219
|
+
backtick = !backtick;
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
if (quote === '"') {
|
|
1223
|
+
if (ch === '"')
|
|
1224
|
+
quote = null;
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1227
|
+
if (ch === "'" || ch === '"') {
|
|
1228
|
+
quote = ch;
|
|
1229
|
+
continue;
|
|
1230
|
+
}
|
|
1231
|
+
if (ch !== "|" || parens > 0 || braces > 0 || backtick || segment[i - 1] === ">")
|
|
1232
|
+
continue;
|
|
1233
|
+
stages.push(segment.slice(start, i));
|
|
1234
|
+
if (segment[i + 1] === "&")
|
|
1235
|
+
i++;
|
|
1236
|
+
start = i + 1;
|
|
1237
|
+
}
|
|
1238
|
+
if (quote || parens !== 0 || braces !== 0 || backtick)
|
|
1239
|
+
return [segment];
|
|
1240
|
+
stages.push(segment.slice(start));
|
|
1241
|
+
return stages;
|
|
1242
|
+
}
|
|
1243
|
+
function shellWords(command) {
|
|
1244
|
+
const words = [];
|
|
1245
|
+
let text = "";
|
|
1246
|
+
let raw = "";
|
|
1247
|
+
let quote = null;
|
|
1248
|
+
let inWord = false;
|
|
1249
|
+
const flush = () => {
|
|
1250
|
+
if (!inWord)
|
|
1251
|
+
return;
|
|
1252
|
+
const body = raw.replace(/^"?\$\{?HOME\}?"?(?=\/|$)/, "");
|
|
1253
|
+
const literalTilde = text.startsWith("~") && !/^~(?:\/|$)/.test(raw);
|
|
1254
|
+
words.push({ text: literalTilde ? `./${text}` : text, opaque: /[$\x60*?[]/.test(body) || /[()]/.test(body) });
|
|
1255
|
+
text = "";
|
|
1256
|
+
raw = "";
|
|
1257
|
+
inWord = false;
|
|
1258
|
+
};
|
|
1259
|
+
for (let i = 0;i < command.length; i++) {
|
|
1260
|
+
const ch = command[i];
|
|
1261
|
+
if (quote === "'") {
|
|
1262
|
+
if (ch === "'")
|
|
1263
|
+
quote = null;
|
|
1264
|
+
else
|
|
1265
|
+
text += ch;
|
|
1266
|
+
raw += ch;
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
if (ch === "\\" && i + 1 < command.length) {
|
|
1270
|
+
const next = command[i + 1];
|
|
1271
|
+
text += quote === '"' && !/[$\x60"\\\n]/.test(next) ? ch + next : next;
|
|
1272
|
+
raw += ch + next;
|
|
1273
|
+
inWord = true;
|
|
1274
|
+
i++;
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
if (quote === '"') {
|
|
1278
|
+
if (ch === '"')
|
|
1279
|
+
quote = null;
|
|
1280
|
+
else
|
|
1281
|
+
text += ch;
|
|
1282
|
+
raw += ch;
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
if (/\s/.test(ch)) {
|
|
1286
|
+
flush();
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
inWord = true;
|
|
1290
|
+
raw += ch;
|
|
1291
|
+
if (ch === "'" || ch === '"')
|
|
1292
|
+
quote = ch;
|
|
1293
|
+
else
|
|
1294
|
+
text += ch;
|
|
1295
|
+
}
|
|
1296
|
+
if (quote) {
|
|
1297
|
+
raw += "$";
|
|
1298
|
+
}
|
|
1299
|
+
flush();
|
|
1300
|
+
return words;
|
|
1301
|
+
}
|
|
1182
1302
|
function segmentOperation(segment) {
|
|
1183
1303
|
const trimmed = segment.trim();
|
|
1184
1304
|
if (!trimmed)
|
|
@@ -1281,7 +1401,6 @@ function bashTargets(command, home, cwd) {
|
|
|
1281
1401
|
for (let i = 0;i < opens; i++)
|
|
1282
1402
|
subshells.push({ current: [...current], reachable: new Set(reachable), certain });
|
|
1283
1403
|
const segment = unwrapSegment(rawSegment);
|
|
1284
|
-
const op = segmentOperation(segment);
|
|
1285
1404
|
const plainCd = segment.match(/^cd(?:\s+(?:-[A-Za-z@]+|--))*(?:\s+([^\s;&|<>()\x60]+))?$/);
|
|
1286
1405
|
const embeddedCd = plainCd ? null : segment.match(/(?:^|\s)cd(?:\s+(?:-[A-Za-z]+|--))*\s+([^\s;&|<>()\x60]+)/);
|
|
1287
1406
|
if (embeddedCd) {
|
|
@@ -1294,28 +1413,90 @@ function bashTargets(command, home, cwd) {
|
|
|
1294
1413
|
const suffix = roots.map((root) => regexEscape(root.slice(home.length + 1))).join("|");
|
|
1295
1414
|
const prefixRe = new RegExp(`(?:~|\\$HOME"*|\\$\\{HOME\\}"*|${homeLiteral}"*)/(?:${suffix})`);
|
|
1296
1415
|
const re = new RegExp(`(${prefixRe.source})([^\\s"';&|<>()\`]*|$)`, "g");
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1416
|
+
const explicitTargets = (text) => {
|
|
1417
|
+
const found = [];
|
|
1418
|
+
let m;
|
|
1419
|
+
re.lastIndex = 0;
|
|
1420
|
+
while ((m = re.exec(text)) !== null)
|
|
1421
|
+
found.push(normalize2(expandHomeSpelling2(m[0], home)).replace(/\/+$/, ""));
|
|
1422
|
+
return found;
|
|
1423
|
+
};
|
|
1424
|
+
const stages = pipelineStages(segment).map((stage) => {
|
|
1305
1425
|
const operands = [];
|
|
1306
|
-
|
|
1426
|
+
let opaqueRedirect = false;
|
|
1427
|
+
const commandPart = withoutFdDuplication(stage).replace(OUTPUT_REDIRECT, (_match, operand) => {
|
|
1428
|
+
if (!operand || /[$\x60]/.test(operand))
|
|
1429
|
+
opaqueRedirect = true;
|
|
1307
1430
|
if (operand)
|
|
1308
1431
|
operands.push(operand);
|
|
1309
1432
|
return " ";
|
|
1310
1433
|
});
|
|
1311
1434
|
const commandOp = segmentOperation(commandPart);
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1435
|
+
return { commandPart, operands, op: opaqueRedirect && commandOp === "read" ? "write" : commandOp };
|
|
1436
|
+
});
|
|
1437
|
+
const pipelineOp = stages.some((stage) => stage.op === "delete") ? "delete" : stages.some((stage) => stage.op === "write") ? "write" : "read";
|
|
1438
|
+
const bases = current.filter(underRoot);
|
|
1439
|
+
for (const stage of stages) {
|
|
1440
|
+
let foundExplicit = false;
|
|
1441
|
+
for (const path of explicitTargets(stage.commandPart)) {
|
|
1442
|
+
foundExplicit = true;
|
|
1443
|
+
targets.push({ path, op: pipelineOp });
|
|
1444
|
+
}
|
|
1445
|
+
for (const operand of stage.operands) {
|
|
1446
|
+
const explicit = explicitTargets(operand);
|
|
1447
|
+
for (const path of explicit)
|
|
1448
|
+
targets.push({ path, op: "write" });
|
|
1449
|
+
if (explicit.length > 0)
|
|
1450
|
+
continue;
|
|
1451
|
+
for (const base of bases) {
|
|
1315
1452
|
targets.push({ path: normalize2(resolve3(base, expandHomeSpelling2(operand, home))), op: "write" });
|
|
1316
1453
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1454
|
+
}
|
|
1455
|
+
if (foundExplicit || stage.op === "read")
|
|
1456
|
+
continue;
|
|
1457
|
+
const words = shellWords(stage.commandPart);
|
|
1458
|
+
let endOfOptions = false;
|
|
1459
|
+
const nonFlag = words.slice(1).flatMap((word) => {
|
|
1460
|
+
if (!endOfOptions && word.text === "--") {
|
|
1461
|
+
endOfOptions = true;
|
|
1462
|
+
return [];
|
|
1463
|
+
}
|
|
1464
|
+
if (!endOfOptions && word.text.startsWith("-"))
|
|
1465
|
+
return [];
|
|
1466
|
+
endOfOptions = true;
|
|
1467
|
+
return [word.opaque ? "." : word.text];
|
|
1468
|
+
});
|
|
1469
|
+
const everyOperand = stage.op === "delete" || /(?:^|\s)(?:mkdir|mkfile|touch|tee|truncate|install|mv)(?:\s|$)/.test(stage.commandPart) || /(?:^|\s)sed(?:\s|$)/.test(stage.commandPart) && /(?:^|\s)(?:-i\S*|--in-place\S*)(?:\s|$)/.test(stage.commandPart);
|
|
1470
|
+
const operandTargets = [];
|
|
1471
|
+
if (everyOperand) {
|
|
1472
|
+
for (const operand of nonFlag.length > 0 ? nonFlag : ["."])
|
|
1473
|
+
operandTargets.push({ operand, op: stage.op });
|
|
1474
|
+
} else {
|
|
1475
|
+
const relMatch = stage.commandPart.match(REL_OPERAND);
|
|
1476
|
+
const last = words.at(-1);
|
|
1477
|
+
operandTargets.push({ operand: relMatch ? relMatch[1] : !last || last.opaque ? "." : last.text, op: stage.op });
|
|
1478
|
+
if (/(?:^|\s)(?:cp|ln|install|mv)(?:\s|$)/.test(stage.commandPart)) {
|
|
1479
|
+
words.forEach((word, index2) => {
|
|
1480
|
+
let value;
|
|
1481
|
+
const short = word.text.match(/^-[A-Za-z]*?t(.*)$/);
|
|
1482
|
+
const long = word.text.match(/^(--t[a-z-]*)(?:=(.*))?$/);
|
|
1483
|
+
if (short && !word.text.startsWith("--")) {
|
|
1484
|
+
value = short[1] ? { text: short[1], opaque: word.opaque } : words[index2 + 1];
|
|
1485
|
+
} else if (long && "--target-directory".startsWith(long[1])) {
|
|
1486
|
+
value = long[2] !== undefined ? { text: long[2], opaque: word.opaque } : words[index2 + 1];
|
|
1487
|
+
}
|
|
1488
|
+
if (value)
|
|
1489
|
+
operandTargets.push({ operand: value.opaque || !value.text ? "." : value.text, op: stage.op });
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
if (/(?:^|\s)(?:python3?|node|bun)\b[^;&|]*\s+-[ce](?:\s|$)/.test(stage.commandPart)) {
|
|
1493
|
+
operandTargets.push({ operand: ".", op: stage.op });
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
for (const base of bases) {
|
|
1497
|
+
for (const { operand, op } of operandTargets) {
|
|
1498
|
+
targets.push({ path: normalize2(resolve3(base, expandHomeSpelling2(operand, home))), op });
|
|
1499
|
+
}
|
|
1319
1500
|
}
|
|
1320
1501
|
}
|
|
1321
1502
|
if (plainCd) {
|
package/dist/index.d.ts
CHANGED
|
@@ -94,3 +94,6 @@ export { handleServeRequest, startServeServer, DEFAULT_SERVE_PORT, } from "./ser
|
|
|
94
94
|
export type { CatalogEntry, ArtifactPayload } from "./serve.js";
|
|
95
95
|
export { provisionCloudflareResources, } from "./cf/provision.js";
|
|
96
96
|
export type { ProvisionOptions, ProvisionResult } from "./cf/provision.js";
|
|
97
|
+
export { planCodexSafetyTrust, applyCodexSafetyTrust, CodexTrustError, type CodexTrustPlan } from "./lib/codex-safety-trust.js";
|
|
98
|
+
export { verifyClaudeSafetyConfiguration, ClaudeSafetyError, type ClaudeSafetyOptions } from "./lib/claude-safety-check.js";
|
|
99
|
+
export { planNativeSafetyRegistration, applyNativeSafetyRegistration, NativeRegistrationError, type NativeRegistrationPlan, type NativeRegistrationOptions } from "./lib/native-safety-registration.js";
|