tamperward 2.23.16 → 2.23.18
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 +1 -0
- package/dist/cli/index.js +86 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -257,6 +257,7 @@ platforms.
|
|
|
257
257
|
| Capability | Linux | macOS | Windows |
|
|
258
258
|
| --- | --- | --- | --- |
|
|
259
259
|
| `check` / policy evaluation | Supported | Supported | Supported |
|
|
260
|
+
| parsed output (`--json`, `--format github`, hook/sweep JSON) on a pipe | Complete before exit | Complete before exit (asynchronous pipe drained first) | Complete before exit (asynchronous pipe drained first) |
|
|
260
261
|
| Claude hook / Stop adapter | Supported where Claude Code command hooks are available | Same | Same |
|
|
261
262
|
| `watch` / observer telemetry | Supported; backend health is reported | Supported/degraded according to `fs.watch` health | Supported/degraded according to `fs.watch` health |
|
|
262
263
|
| opt-in `hook-service` | Supported (per-user `0600` unix socket) | Supported (per-user `0600` unix socket) | **Unsupported; `start` refuses, hooks run in-process** |
|
package/dist/cli/index.js
CHANGED
|
@@ -302,6 +302,29 @@ var init_hook_client = __esm({
|
|
|
302
302
|
}
|
|
303
303
|
});
|
|
304
304
|
|
|
305
|
+
// src/cli/exit.ts
|
|
306
|
+
function exitAfterFlush(code2) {
|
|
307
|
+
process.exitCode = code2;
|
|
308
|
+
let pending = 0;
|
|
309
|
+
const done = () => {
|
|
310
|
+
pending -= 1;
|
|
311
|
+
if (pending === 0) process.exit(code2);
|
|
312
|
+
};
|
|
313
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
314
|
+
pending += 1;
|
|
315
|
+
stream.on("error", noop);
|
|
316
|
+
stream.write("", done);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
var noop;
|
|
320
|
+
var init_exit = __esm({
|
|
321
|
+
"src/cli/exit.ts"() {
|
|
322
|
+
"use strict";
|
|
323
|
+
noop = () => {
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
305
328
|
// src/lazy-deps.ts
|
|
306
329
|
import { createRequire } from "node:module";
|
|
307
330
|
function loadPicomatch() {
|
|
@@ -6756,6 +6779,25 @@ function entryShape(h) {
|
|
|
6756
6779
|
}
|
|
6757
6780
|
return { problems, runs };
|
|
6758
6781
|
}
|
|
6782
|
+
function judgeGateEntry(event, matcher, h) {
|
|
6783
|
+
const cmd = typeof h.command === "string" ? h.command : "";
|
|
6784
|
+
const a = analyseGateCommand(cmd, event);
|
|
6785
|
+
const shape = entryShape(h);
|
|
6786
|
+
const problems = [...a.problems, ...shape.problems];
|
|
6787
|
+
let runs = a.runs && shape.runs;
|
|
6788
|
+
let tools = null;
|
|
6789
|
+
if (event === "PreToolUse") {
|
|
6790
|
+
const sel = matcherSelection(matcher);
|
|
6791
|
+
tools = sel.tools;
|
|
6792
|
+
if (sel.problem) {
|
|
6793
|
+
problems.push(sel.problem);
|
|
6794
|
+
}
|
|
6795
|
+
} else if (matcher !== void 0 && !(typeof matcher === "string" && (matcher === "" || matcher === "*"))) {
|
|
6796
|
+
problems.push(`a \`matcher\` (${JSON.stringify(matcher)}) on the Stop entry`);
|
|
6797
|
+
runs = false;
|
|
6798
|
+
}
|
|
6799
|
+
return { runs, problems, pin: a.pin, tools };
|
|
6800
|
+
}
|
|
6759
6801
|
function gateCandidates(s, event) {
|
|
6760
6802
|
const out3 = [];
|
|
6761
6803
|
for (const group of eventEntries(s, event) ?? []) {
|
|
@@ -6778,22 +6820,7 @@ function gateCandidates(s, event) {
|
|
|
6778
6820
|
continue;
|
|
6779
6821
|
}
|
|
6780
6822
|
if (!MENTIONS_GATE.test(cmd)) continue;
|
|
6781
|
-
|
|
6782
|
-
const shape = entryShape(h);
|
|
6783
|
-
const problems = [...a.problems, ...shape.problems];
|
|
6784
|
-
let runs = a.runs && shape.runs;
|
|
6785
|
-
let tools = null;
|
|
6786
|
-
if (event === "PreToolUse") {
|
|
6787
|
-
const sel = matcherSelection(group.matcher);
|
|
6788
|
-
tools = sel.tools;
|
|
6789
|
-
if (sel.problem) {
|
|
6790
|
-
problems.push(sel.problem);
|
|
6791
|
-
}
|
|
6792
|
-
} else if (group.matcher !== void 0 && !(typeof group.matcher === "string" && (group.matcher === "" || group.matcher === "*"))) {
|
|
6793
|
-
problems.push(`a \`matcher\` (${JSON.stringify(group.matcher)}) on the Stop entry`);
|
|
6794
|
-
runs = false;
|
|
6795
|
-
}
|
|
6796
|
-
out3.push({ key: JSON.stringify({ m: group.matcher, h }), runs, problems, pin: a.pin, tools });
|
|
6823
|
+
out3.push({ key: JSON.stringify({ m: group.matcher, h }), ...judgeGateEntry(event, group.matcher, h) });
|
|
6797
6824
|
}
|
|
6798
6825
|
}
|
|
6799
6826
|
return out3;
|
|
@@ -10745,7 +10772,7 @@ function runHookService(args) {
|
|
|
10745
10772
|
}).catch((e) => {
|
|
10746
10773
|
process.stderr.write(`tamperward hook-service: ${e instanceof Error ? e.message : String(e)}
|
|
10747
10774
|
`);
|
|
10748
|
-
|
|
10775
|
+
exitAfterFlush(2);
|
|
10749
10776
|
});
|
|
10750
10777
|
return -1;
|
|
10751
10778
|
}
|
|
@@ -10769,14 +10796,15 @@ function runHookService(args) {
|
|
|
10769
10796
|
if (!isRecord(res) || typeof res.served !== "number") {
|
|
10770
10797
|
process.stdout.write(`tamperward hook-service: not running (pid ${state.pid} did not answer, or is another version); ${optIn}
|
|
10771
10798
|
`);
|
|
10772
|
-
|
|
10799
|
+
exitAfterFlush(0);
|
|
10800
|
+
return;
|
|
10773
10801
|
}
|
|
10774
10802
|
const cache3 = isRecord(res.cache) ? res.cache : {};
|
|
10775
10803
|
process.stdout.write(
|
|
10776
10804
|
`tamperward hook-service: running (pid ${state.pid}, tamperward@${state.version}, root ${state.root}, since ${state.started_at}); served: ${res.served}; snapshot cache hits/misses/entries: ${String(cache3.hits ?? 0)}/${String(cache3.misses ?? 0)}/${String(cache3.size ?? 0)}; ${optIn}
|
|
10777
10805
|
`
|
|
10778
10806
|
);
|
|
10779
|
-
|
|
10807
|
+
exitAfterFlush(0);
|
|
10780
10808
|
});
|
|
10781
10809
|
return -1;
|
|
10782
10810
|
}
|
|
@@ -10789,6 +10817,7 @@ var init_hook_service = __esm({
|
|
|
10789
10817
|
init_ptree_cache();
|
|
10790
10818
|
init_repo_context();
|
|
10791
10819
|
init_hook();
|
|
10820
|
+
init_exit();
|
|
10792
10821
|
init_hook_client();
|
|
10793
10822
|
MAX_REQUEST_BYTES = 64 * 1024 * 1024;
|
|
10794
10823
|
STOP_WAIT_MS = 1e4;
|
|
@@ -11164,6 +11193,17 @@ function planGitignore(cwd) {
|
|
|
11164
11193
|
apply: () => atomicReplaceFile(path, next, existingMode(path, 420))
|
|
11165
11194
|
};
|
|
11166
11195
|
}
|
|
11196
|
+
function gateVerdicts(event, groups, needle) {
|
|
11197
|
+
const out3 = [];
|
|
11198
|
+
for (const group of groups) {
|
|
11199
|
+
for (const h of group.hooks ?? []) {
|
|
11200
|
+
if (!needle.test(String(h.command ?? ""))) continue;
|
|
11201
|
+
const { runs, problems } = judgeGateEntry(event, group.matcher, h);
|
|
11202
|
+
out3.push({ event, group, h, runs, problems });
|
|
11203
|
+
}
|
|
11204
|
+
}
|
|
11205
|
+
return out3;
|
|
11206
|
+
}
|
|
11167
11207
|
function planClaudeHooks(cwd) {
|
|
11168
11208
|
const rel = ".claude/settings.json";
|
|
11169
11209
|
const path = join14(cwd, rel);
|
|
@@ -11197,10 +11237,24 @@ function planClaudeHooks(cwd) {
|
|
|
11197
11237
|
const needPre = preEntries.length === 0;
|
|
11198
11238
|
const stopEntries = entriesWith(hooks.Stop, SWEEP_RE);
|
|
11199
11239
|
const needStop = stopEntries.length === 0;
|
|
11240
|
+
const neutralised = [
|
|
11241
|
+
...gateVerdicts("PreToolUse", preEntries, HOOK_RE),
|
|
11242
|
+
...gateVerdicts("Stop", stopEntries, SWEEP_RE)
|
|
11243
|
+
].filter((v) => !v.runs);
|
|
11244
|
+
const foreign = neutralised.filter((v) => ours(String(v.h.command ?? "")) === null);
|
|
11245
|
+
if (foreign.length > 0) {
|
|
11246
|
+
return {
|
|
11247
|
+
item: "agent",
|
|
11248
|
+
path: rel,
|
|
11249
|
+
status: "error",
|
|
11250
|
+
detail: `${foreign.map((v) => `the ${v.event} gate entry is neutralised (${v.problems.join("; ")})`).join("; ")} \u2014 the runtime would not run the gate through it; remove the entry, then re-run init (refusing to rewrite a hand-written entry)`
|
|
11251
|
+
};
|
|
11252
|
+
}
|
|
11200
11253
|
const stale = preEntries.filter((m) => missingTools(m.matcher).length > 0);
|
|
11201
|
-
const
|
|
11254
|
+
const restoring = new Set(neutralised.map((v) => v.h));
|
|
11255
|
+
const repin = [...preEntries, ...stopEntries].flatMap((m) => m.hooks ?? []).filter((h) => !restoring.has(h) && stalePin(String(h.command ?? "")));
|
|
11202
11256
|
const needDisableFalse = settings.disableAllHooks !== false;
|
|
11203
|
-
if (!needPre && !needStop && stale.length === 0 && repin.length === 0 && !needDisableFalse) {
|
|
11257
|
+
if (!needPre && !needStop && stale.length === 0 && repin.length === 0 && neutralised.length === 0 && !needDisableFalse) {
|
|
11204
11258
|
return { item: "agent", path: rel, status: "ok", detail: "PreToolUse + Stop hooks already wired" };
|
|
11205
11259
|
}
|
|
11206
11260
|
const repairing = stale.length > 0 ? [...new Set(stale.flatMap((m) => missingTools(m.matcher)))] : [];
|
|
@@ -11214,9 +11268,16 @@ function planClaudeHooks(cwd) {
|
|
|
11214
11268
|
needStop && "wire Stop sweep",
|
|
11215
11269
|
repairing.length > 0 && `widen the PreToolUse matcher to cover ${repairing.join(", ")}`,
|
|
11216
11270
|
repin.length > 0 && `re-pin the hook commands to tamperward@${TW_VERSION} (was ${pins.join(", ")})`,
|
|
11271
|
+
...neutralised.map((v) => `restore the ${v.event} gate entry to the shape init writes \u2014 it is neutralised (${v.problems.join("; ")})`),
|
|
11217
11272
|
needDisableFalse && (settings.disableAllHooks === void 0 ? "declare disableAllHooks: false so the user settings file cannot switch the hooks off" : `set disableAllHooks: false (was ${JSON.stringify(settings.disableAllHooks)})`)
|
|
11218
11273
|
].filter(Boolean).join(" + "),
|
|
11219
11274
|
apply: () => {
|
|
11275
|
+
for (const v of neutralised) {
|
|
11276
|
+
for (const k of Object.keys(v.h)) delete v.h[k];
|
|
11277
|
+
v.h.type = "command";
|
|
11278
|
+
v.h.command = v.event === "PreToolUse" ? HOOK_CMD : SWEEP_CMD;
|
|
11279
|
+
if (v.event === "Stop") delete v.group.matcher;
|
|
11280
|
+
}
|
|
11220
11281
|
for (const h of repin) {
|
|
11221
11282
|
const kind = ours(String(h.command ?? ""))?.[2];
|
|
11222
11283
|
h.command = kind === "hook claude" ? HOOK_CMD : kind === "sweep claude" ? SWEEP_CMD : h.command;
|
|
@@ -11316,7 +11377,7 @@ ${block.join("\n")}
|
|
|
11316
11377
|
}
|
|
11317
11378
|
const lines = existing.split("\n");
|
|
11318
11379
|
const dead = unconditionalExitAt(lines);
|
|
11319
|
-
const at = lines.findIndex((l) => PRECOMMIT_RE.test(l));
|
|
11380
|
+
const at = lines.findIndex((l) => !/^\s*#/.test(l) && PRECOMMIT_RE.test(l));
|
|
11320
11381
|
const write = (out3) => atomicReplaceFile(path, out3.join("\n"), hookMode());
|
|
11321
11382
|
if (at !== -1 && (dead === -1 || at < dead)) {
|
|
11322
11383
|
if (!stalePin(lines[at])) {
|
|
@@ -11559,6 +11620,7 @@ var init_init = __esm({
|
|
|
11559
11620
|
init_verifier_limits();
|
|
11560
11621
|
init_wiring();
|
|
11561
11622
|
init_narrow();
|
|
11623
|
+
init_hook_tampering();
|
|
11562
11624
|
init_repo_context();
|
|
11563
11625
|
init_safe_write();
|
|
11564
11626
|
errText3 = (e) => (e instanceof Error ? e.message : String(e)).replace(/\s+/g, " ").trim();
|
|
@@ -17779,6 +17841,7 @@ var init_main = __esm({
|
|
|
17779
17841
|
|
|
17780
17842
|
// src/cli/index.ts
|
|
17781
17843
|
init_hook_client();
|
|
17844
|
+
init_exit();
|
|
17782
17845
|
import { readFileSync as readFileSync24 } from "node:fs";
|
|
17783
17846
|
function loadMain() {
|
|
17784
17847
|
return Promise.resolve().then(() => (init_main(), main_exports));
|
|
@@ -17810,4 +17873,4 @@ async function launch(argv) {
|
|
|
17810
17873
|
return main2.guardedMain(argv);
|
|
17811
17874
|
}
|
|
17812
17875
|
var code = await launch(process.argv.slice(2));
|
|
17813
|
-
if (code >= 0)
|
|
17876
|
+
if (code >= 0) exitAfterFlush(code);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.23.
|
|
3
|
+
"version": "2.23.18",
|
|
4
4
|
"description": "The deterministic agent-integrity gate. One ruleset, evaluated on the actual diff/commands as a verdict, enforced everywhere a change can be made.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "hexrift",
|