tamperward 2.29.3 → 2.29.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/dist/cli/index.js +94 -51
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -10801,14 +10801,14 @@ function renderText(input, opts) {
|
|
|
10801
10801
|
const isBlock = f.severity === "block";
|
|
10802
10802
|
const mark = paint(isBlock ? "BLOCK" : "warn ", isBlock ? BOLD + RED : YELLOW, c);
|
|
10803
10803
|
const loc = f.file ? `${stripControl(escapeControl(f.file))}${f.line ? `:${f.line}` : ""}` : "(command)";
|
|
10804
|
-
out3.push(` ${mark} ${paint(stripControl(f.rule), BOLD, c)} ${paint(loc,
|
|
10804
|
+
out3.push(` ${mark} ${paint(stripControl(f.rule), BOLD, c)} ${paint(loc, CYAN, c)}`);
|
|
10805
10805
|
for (const l of wrap(f.message, opts.width - 4)) out3.push(` ${l}`);
|
|
10806
10806
|
const row = (key2, lines) => {
|
|
10807
10807
|
lines.forEach((l, i) => out3.push(` ${paint(pad(i === 0 ? key2 : ""), DIM, c)} ${l}`));
|
|
10808
10808
|
};
|
|
10809
10809
|
row("evidence", [paint(clip(f.evidence, body), DIM, c)]);
|
|
10810
10810
|
row("instead", wrap(f.remediation, body));
|
|
10811
|
-
if (f.signoff.required) row("sign-off", [stripControl(f.signoff.command)]);
|
|
10811
|
+
if (f.signoff.required) row("sign-off", [paint(stripControl(f.signoff.command), YELLOW, c)]);
|
|
10812
10812
|
out3.push("");
|
|
10813
10813
|
}
|
|
10814
10814
|
if (blocks2.length > 0) {
|
|
@@ -10818,7 +10818,7 @@ function renderText(input, opts) {
|
|
|
10818
10818
|
}
|
|
10819
10819
|
return out3.join("\n").replace(/\n+$/, "") + "\n";
|
|
10820
10820
|
}
|
|
10821
|
-
var ESC, RESET, BOLD, DIM, RED, YELLOW, GREEN, SIGNOFF_NOTE;
|
|
10821
|
+
var ESC, RESET, BOLD, DIM, truecolour, RED, YELLOW, GREEN, CYAN, SIGNOFF_NOTE;
|
|
10822
10822
|
var init_text = __esm({
|
|
10823
10823
|
"src/cli/render/text.ts"() {
|
|
10824
10824
|
"use strict";
|
|
@@ -10827,9 +10827,11 @@ var init_text = __esm({
|
|
|
10827
10827
|
RESET = `${ESC}[0m`;
|
|
10828
10828
|
BOLD = `${ESC}[1m`;
|
|
10829
10829
|
DIM = `${ESC}[2m`;
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10830
|
+
truecolour = (r, g, b) => `${ESC}[38;2;${r};${g};${b}m`;
|
|
10831
|
+
RED = truecolour(255, 107, 107);
|
|
10832
|
+
YELLOW = truecolour(227, 179, 65);
|
|
10833
|
+
GREEN = truecolour(63, 185, 80);
|
|
10834
|
+
CYAN = truecolour(86, 212, 221);
|
|
10833
10835
|
SIGNOFF_NOTE = "A blocking finding clears only with a human sign-off. In CI that sign-off is out-of-band \u2014 a PR label applied by a reviewer \u2014 never a file committed on the branch under review. See SPEC \xA75.4.";
|
|
10834
10836
|
}
|
|
10835
10837
|
});
|
|
@@ -12759,6 +12761,7 @@ async function startHookService(opts) {
|
|
|
12759
12761
|
removeQuietly(paths.state);
|
|
12760
12762
|
const cache3 = opts.cache ?? new SnapshotCache();
|
|
12761
12763
|
let served = 0;
|
|
12764
|
+
let inFlight = false;
|
|
12762
12765
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12763
12766
|
const respond = (sock, body) => {
|
|
12764
12767
|
try {
|
|
@@ -12789,19 +12792,27 @@ async function startHookService(opts) {
|
|
|
12789
12792
|
if (!within(root, req2.cwd)) return respond(sock, { refused: "cwd outside the bound repository" });
|
|
12790
12793
|
const named = payloadCwd(req2.raw);
|
|
12791
12794
|
if (named !== null && !within(root, named)) return respond(sock, { refused: "payload cwd outside the bound repository" });
|
|
12792
|
-
|
|
12793
|
-
|
|
12795
|
+
if (inFlight) return respond(sock, { refused: "another request is in flight" });
|
|
12796
|
+
inFlight = true;
|
|
12797
|
+
sock.write(JSON.stringify({ v: HOOK_SERVICE_PROTOCOL, version: TW_VERSION, accepted: true }) + "\n");
|
|
12798
|
+
setImmediate(() => {
|
|
12794
12799
|
try {
|
|
12795
|
-
|
|
12796
|
-
|
|
12797
|
-
|
|
12798
|
-
|
|
12799
|
-
|
|
12800
|
+
if (sock.destroyed || sock.writableEnded) return;
|
|
12801
|
+
let result;
|
|
12802
|
+
try {
|
|
12803
|
+
result = withEnv(req2.env, () => req2.kind === "PreToolUse" ? preToolUseFromRaw(req2.raw, req2.cwd) : stopFromRaw(req2.raw, req2.cwd));
|
|
12804
|
+
} catch (e) {
|
|
12805
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
12806
|
+
const reason = `Tamperward's hook service could not complete the accepted evaluation (${detail}), so it is denied rather than retried concurrently.`;
|
|
12807
|
+
const stdout = req2.kind === "PreToolUse" ? JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason } }) + "\n" : JSON.stringify({ decision: "block", reason }) + "\n";
|
|
12808
|
+
served++;
|
|
12809
|
+
return respond(sock, { exitCode: 0, stdout });
|
|
12810
|
+
}
|
|
12800
12811
|
served++;
|
|
12801
|
-
|
|
12812
|
+
respond(sock, { exitCode: result.exitCode, stdout: result.stdout });
|
|
12813
|
+
} finally {
|
|
12814
|
+
inFlight = false;
|
|
12802
12815
|
}
|
|
12803
|
-
served++;
|
|
12804
|
-
respond(sock, { exitCode: result.exitCode, stdout: result.stdout });
|
|
12805
12816
|
});
|
|
12806
12817
|
};
|
|
12807
12818
|
const server = createServer((sock) => {
|
|
@@ -12860,36 +12871,44 @@ async function startHookService(opts) {
|
|
|
12860
12871
|
function sleepSync(ms) {
|
|
12861
12872
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
12862
12873
|
}
|
|
12874
|
+
async function probeSocket(paths) {
|
|
12875
|
+
if (socketRefusal(paths) !== null) return { answered: false, status: null };
|
|
12876
|
+
const req2 = { v: HOOK_SERVICE_PROTOCOL, version: TW_VERSION, kind: "status", raw: "", cwd: process.cwd(), env: {} };
|
|
12877
|
+
const res = await exchange(paths, req2, 1e3);
|
|
12878
|
+
if (res === null) return { answered: false, status: null };
|
|
12879
|
+
if (isRecord(res) && res.v === HOOK_SERVICE_PROTOCOL && typeof res.pid === "number" && typeof res.root === "string" && typeof res.version === "string") {
|
|
12880
|
+
return { answered: true, status: { pid: res.pid, root: res.root, version: res.version } };
|
|
12881
|
+
}
|
|
12882
|
+
return { answered: true, status: null };
|
|
12883
|
+
}
|
|
12884
|
+
function describeStateFile(state) {
|
|
12885
|
+
return state ? `records pid ${state.pid}, root ${state.root}, tamperward@${state.version}` : "is absent or unreadable";
|
|
12886
|
+
}
|
|
12863
12887
|
async function stopHookService(paths) {
|
|
12888
|
+
const probe = await probeSocket(paths);
|
|
12864
12889
|
const state = readServiceState(paths);
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
|
|
12872
|
-
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
|
|
12876
|
-
|
|
12877
|
-
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
try {
|
|
12881
|
-
process.kill(state.pid, "SIGTERM");
|
|
12882
|
-
} catch {
|
|
12890
|
+
if (probe.answered) {
|
|
12891
|
+
const st = probe.status;
|
|
12892
|
+
const agrees = st !== null && state !== null && state.pid === st.pid && state.root === st.root && state.version === st.version;
|
|
12893
|
+
if (agrees && st.pid !== process.pid) {
|
|
12894
|
+
try {
|
|
12895
|
+
process.kill(st.pid, "SIGTERM");
|
|
12896
|
+
} catch {
|
|
12897
|
+
}
|
|
12898
|
+
const gone = () => !pidAlive2(st.pid) || !existsSync11(paths.socket);
|
|
12899
|
+
const until = Date.now() + STOP_WAIT_MS;
|
|
12900
|
+
while (!gone() && Date.now() < until) sleepSync(50);
|
|
12901
|
+
if (!gone()) throw new Error(`hook service pid ${st.pid} did not exit within ${STOP_WAIT_MS / 1e3}s`);
|
|
12902
|
+
removeQuietly(paths.socket);
|
|
12903
|
+
removeQuietly(paths.state);
|
|
12904
|
+
return { outcome: "stopped", pid: st.pid };
|
|
12883
12905
|
}
|
|
12884
|
-
const
|
|
12885
|
-
|
|
12886
|
-
while (!gone() && Date.now() < until) sleepSync(50);
|
|
12887
|
-
if (!gone()) throw new Error(`hook service pid ${state.pid} did not exit within ${STOP_WAIT_MS / 1e3}s`);
|
|
12888
|
-
outcome = "stopped";
|
|
12906
|
+
const detail = st ? `a listener answered on ${paths.socket} (pid ${st.pid}, root ${st.root}, tamperward@${st.version}) but the state file ${describeStateFile(state)}` : `a listener answered on ${paths.socket} but did not report a readable status (possibly a different tamperward version); the state file ${describeStateFile(state)}`;
|
|
12907
|
+
return { outcome: "mismatch", pid: st?.pid ?? state?.pid, root: st?.root ?? state?.root, detail };
|
|
12889
12908
|
}
|
|
12890
12909
|
removeQuietly(paths.socket);
|
|
12891
12910
|
removeQuietly(paths.state);
|
|
12892
|
-
return outcome;
|
|
12911
|
+
return { outcome: "not-running" };
|
|
12893
12912
|
}
|
|
12894
12913
|
function parseStart(args) {
|
|
12895
12914
|
let dir = process.cwd();
|
|
@@ -12928,8 +12947,21 @@ function runHookService(args) {
|
|
|
12928
12947
|
return -1;
|
|
12929
12948
|
}
|
|
12930
12949
|
if (sub === "stop") {
|
|
12931
|
-
return stopHookService(paths).then((
|
|
12932
|
-
|
|
12950
|
+
return stopHookService(paths).then((res) => {
|
|
12951
|
+
if (res.outcome === "stopped") {
|
|
12952
|
+
process.stdout.write(`tamperward hook-service: stopped (pid ${res.pid}); ${paths.socket} removed
|
|
12953
|
+
`);
|
|
12954
|
+
return 0;
|
|
12955
|
+
}
|
|
12956
|
+
if (res.outcome === "mismatch") {
|
|
12957
|
+
process.stderr.write(
|
|
12958
|
+
`tamperward hook-service: NOT stopped \u2014 ${res.detail}. The socket was left intact; a live listener is never orphaned. ` + (res.pid ? `If you mean to stop it, signal it directly: kill ${res.pid}.
|
|
12959
|
+
` : `Inspect the process holding ${paths.socket} to stop it.
|
|
12960
|
+
`)
|
|
12961
|
+
);
|
|
12962
|
+
return 1;
|
|
12963
|
+
}
|
|
12964
|
+
process.stdout.write(`tamperward hook-service: not running; ${paths.socket} removed
|
|
12933
12965
|
`);
|
|
12934
12966
|
return 0;
|
|
12935
12967
|
});
|
|
@@ -12937,22 +12969,33 @@ function runHookService(args) {
|
|
|
12937
12969
|
const state = readServiceState(paths);
|
|
12938
12970
|
const refusal = socketRefusal(paths);
|
|
12939
12971
|
const optIn = hookServiceEnabled() ? `${HOOK_SERVICE_ENV}=1 (hooks consult the service)` : `${HOOK_SERVICE_ENV} unset (hooks run in-process)`;
|
|
12940
|
-
if (
|
|
12941
|
-
process.stdout.write(`tamperward hook-service: not running (${refusal
|
|
12972
|
+
if (refusal !== null) {
|
|
12973
|
+
process.stdout.write(`tamperward hook-service: not running (${refusal}); ${optIn}
|
|
12942
12974
|
`);
|
|
12943
12975
|
return 0;
|
|
12944
12976
|
}
|
|
12945
12977
|
const req2 = { v: HOOK_SERVICE_PROTOCOL, version: TW_VERSION, kind: "status", raw: "", cwd: process.cwd(), env: {} };
|
|
12946
12978
|
void exchange(paths, req2, 5e3).then((res) => {
|
|
12947
|
-
if (
|
|
12948
|
-
process.stdout.write(`tamperward hook-service: not running (
|
|
12979
|
+
if (res === null) {
|
|
12980
|
+
process.stdout.write(`tamperward hook-service: not running (socket did not connect); ${optIn}
|
|
12949
12981
|
`);
|
|
12950
12982
|
exitAfterFlush(0);
|
|
12951
12983
|
return;
|
|
12952
12984
|
}
|
|
12985
|
+
if (!isRecord(res) || typeof res.served !== "number" || typeof res.pid !== "number" || typeof res.root !== "string" || typeof res.version !== "string") {
|
|
12986
|
+
process.stdout.write(
|
|
12987
|
+
`tamperward hook-service: a listener answered on ${paths.socket} but did not report a readable status (possibly a different tamperward version); the state file ${state ? `records pid ${state.pid}, tamperward@${state.version}` : "is absent or unreadable"}; ${optIn}
|
|
12988
|
+
`
|
|
12989
|
+
);
|
|
12990
|
+
exitAfterFlush(0);
|
|
12991
|
+
return;
|
|
12992
|
+
}
|
|
12953
12993
|
const cache3 = isRecord(res.cache) ? res.cache : {};
|
|
12994
|
+
const startedAt = typeof res.started_at === "string" ? res.started_at : state?.started_at ?? "unknown";
|
|
12995
|
+
const disagrees = !state || state.pid !== res.pid || state.root !== res.root || state.version !== res.version;
|
|
12996
|
+
const warn = disagrees ? `WARNING: the state file ${describeStateFile(state)}, which disagrees with the live listener; ` : "";
|
|
12954
12997
|
process.stdout.write(
|
|
12955
|
-
`tamperward hook-service: running (pid ${
|
|
12998
|
+
`tamperward hook-service: running (pid ${res.pid}, tamperward@${res.version}, root ${res.root}, since ${startedAt}); served: ${res.served}; snapshot cache hits/misses/entries: ${String(cache3.hits ?? 0)}/${String(cache3.misses ?? 0)}/${String(cache3.size ?? 0)}; ${warn}${optIn}
|
|
12956
12999
|
`
|
|
12957
13000
|
);
|
|
12958
13001
|
exitAfterFlush(0);
|
|
@@ -18153,7 +18196,7 @@ async function runOnboard(opts, io = {}) {
|
|
|
18153
18196
|
if (kind === "ok") return GREEN2;
|
|
18154
18197
|
if (kind === "warn") return YELLOW2;
|
|
18155
18198
|
if (kind === "bad") return RED2;
|
|
18156
|
-
if (kind === "info") return
|
|
18199
|
+
if (kind === "info") return CYAN2;
|
|
18157
18200
|
return DIM2;
|
|
18158
18201
|
};
|
|
18159
18202
|
const status = (label, text, kind = "info") => {
|
|
@@ -18210,7 +18253,7 @@ async function runOnboard(opts, io = {}) {
|
|
|
18210
18253
|
const section = (n) => {
|
|
18211
18254
|
sectionNo = n;
|
|
18212
18255
|
out3("");
|
|
18213
|
-
out3(paint2(n + "/" + SECTIONS.length + " " + SECTIONS[n - 1], BOLD2 +
|
|
18256
|
+
out3(paint2(n + "/" + SECTIONS.length + " " + SECTIONS[n - 1], BOLD2 + CYAN2, colour));
|
|
18214
18257
|
};
|
|
18215
18258
|
const wrote = [];
|
|
18216
18259
|
let verifyCommand;
|
|
@@ -18552,7 +18595,7 @@ function runDemo(cwd, head, runners, out3) {
|
|
|
18552
18595
|
out3("changed it while the demo ran; inspect with `git status`.");
|
|
18553
18596
|
}
|
|
18554
18597
|
}
|
|
18555
|
-
var Aborted, SECTIONS, MANUAL_CONTROLS, ESC2, RESET2, BOLD2, DIM2, RED2, YELLOW2, GREEN2,
|
|
18598
|
+
var Aborted, SECTIONS, MANUAL_CONTROLS, ESC2, RESET2, BOLD2, DIM2, RED2, YELLOW2, GREEN2, CYAN2, JS_TEST_PATH, JS_TEST_BLOCK;
|
|
18556
18599
|
var init_onboard = __esm({
|
|
18557
18600
|
"src/cli/onboard.ts"() {
|
|
18558
18601
|
"use strict";
|
|
@@ -18589,7 +18632,7 @@ var init_onboard = __esm({
|
|
|
18589
18632
|
RED2 = `${ESC2}[31m`;
|
|
18590
18633
|
YELLOW2 = `${ESC2}[33m`;
|
|
18591
18634
|
GREEN2 = `${ESC2}[32m`;
|
|
18592
|
-
|
|
18635
|
+
CYAN2 = `${ESC2}[36m`;
|
|
18593
18636
|
JS_TEST_PATH = /(?:^|\/)(?:[^/]+\.(?:test|spec)\.[cm]?[jt]sx?|__tests__\/[^/]+\.[cm]?[jt]sx?)$/;
|
|
18594
18637
|
JS_TEST_BLOCK = /^(\s*)(it|test|describe)(\s*\()/m;
|
|
18595
18638
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.29.
|
|
3
|
+
"version": "2.29.5",
|
|
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",
|