tamperward 2.23.15 → 2.23.17
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 +171 -101
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -6756,6 +6756,25 @@ function entryShape(h) {
|
|
|
6756
6756
|
}
|
|
6757
6757
|
return { problems, runs };
|
|
6758
6758
|
}
|
|
6759
|
+
function judgeGateEntry(event, matcher, h) {
|
|
6760
|
+
const cmd = typeof h.command === "string" ? h.command : "";
|
|
6761
|
+
const a = analyseGateCommand(cmd, event);
|
|
6762
|
+
const shape = entryShape(h);
|
|
6763
|
+
const problems = [...a.problems, ...shape.problems];
|
|
6764
|
+
let runs = a.runs && shape.runs;
|
|
6765
|
+
let tools = null;
|
|
6766
|
+
if (event === "PreToolUse") {
|
|
6767
|
+
const sel = matcherSelection(matcher);
|
|
6768
|
+
tools = sel.tools;
|
|
6769
|
+
if (sel.problem) {
|
|
6770
|
+
problems.push(sel.problem);
|
|
6771
|
+
}
|
|
6772
|
+
} else if (matcher !== void 0 && !(typeof matcher === "string" && (matcher === "" || matcher === "*"))) {
|
|
6773
|
+
problems.push(`a \`matcher\` (${JSON.stringify(matcher)}) on the Stop entry`);
|
|
6774
|
+
runs = false;
|
|
6775
|
+
}
|
|
6776
|
+
return { runs, problems, pin: a.pin, tools };
|
|
6777
|
+
}
|
|
6759
6778
|
function gateCandidates(s, event) {
|
|
6760
6779
|
const out3 = [];
|
|
6761
6780
|
for (const group of eventEntries(s, event) ?? []) {
|
|
@@ -6778,22 +6797,7 @@ function gateCandidates(s, event) {
|
|
|
6778
6797
|
continue;
|
|
6779
6798
|
}
|
|
6780
6799
|
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 });
|
|
6800
|
+
out3.push({ key: JSON.stringify({ m: group.matcher, h }), ...judgeGateEntry(event, group.matcher, h) });
|
|
6797
6801
|
}
|
|
6798
6802
|
}
|
|
6799
6803
|
return out3;
|
|
@@ -10885,10 +10889,55 @@ var init_verifier_limits = __esm({
|
|
|
10885
10889
|
}
|
|
10886
10890
|
});
|
|
10887
10891
|
|
|
10892
|
+
// src/safe-write.ts
|
|
10893
|
+
import { randomBytes } from "node:crypto";
|
|
10894
|
+
import { lstatSync as lstatSync7, renameSync as renameSync3, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
10895
|
+
function writeTargetKind(path) {
|
|
10896
|
+
let st;
|
|
10897
|
+
try {
|
|
10898
|
+
st = lstatSync7(path, { throwIfNoEntry: false });
|
|
10899
|
+
} catch (e) {
|
|
10900
|
+
if (errnoCode(e) === "ENOTDIR") return "absent";
|
|
10901
|
+
throw e;
|
|
10902
|
+
}
|
|
10903
|
+
if (!st) return "absent";
|
|
10904
|
+
if (st.isSymbolicLink()) return "symlink";
|
|
10905
|
+
return st.isFile() ? "file" : "irregular";
|
|
10906
|
+
}
|
|
10907
|
+
function refuseNonRegular(path) {
|
|
10908
|
+
switch (writeTargetKind(path)) {
|
|
10909
|
+
case "symlink":
|
|
10910
|
+
return "refusing: symlink \u2014 the target would be written instead of this path; replace the link with a regular file, then re-run";
|
|
10911
|
+
case "irregular":
|
|
10912
|
+
return "refusing: not a regular file \u2014 a directory or special file stands where a file is expected; remove it, then re-run";
|
|
10913
|
+
default:
|
|
10914
|
+
return null;
|
|
10915
|
+
}
|
|
10916
|
+
}
|
|
10917
|
+
function existingMode(path, fallback) {
|
|
10918
|
+
if (writeTargetKind(path) !== "file") return fallback;
|
|
10919
|
+
return lstatSync7(path).mode & 511;
|
|
10920
|
+
}
|
|
10921
|
+
function atomicReplaceFile(path, content, mode) {
|
|
10922
|
+
const tmp = `${path}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`;
|
|
10923
|
+
try {
|
|
10924
|
+
writeFileSync7(tmp, content, { encoding: "utf8", flag: "wx", mode });
|
|
10925
|
+
renameSync3(tmp, path);
|
|
10926
|
+
} finally {
|
|
10927
|
+
rmSync5(tmp, { force: true });
|
|
10928
|
+
}
|
|
10929
|
+
}
|
|
10930
|
+
var init_safe_write = __esm({
|
|
10931
|
+
"src/safe-write.ts"() {
|
|
10932
|
+
"use strict";
|
|
10933
|
+
init_narrow();
|
|
10934
|
+
}
|
|
10935
|
+
});
|
|
10936
|
+
|
|
10888
10937
|
// src/cli/init.ts
|
|
10889
10938
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
10890
10939
|
import { createHash as createHash6 } from "node:crypto";
|
|
10891
|
-
import { existsSync as existsSync11,
|
|
10940
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync12, realpathSync as realpathSync4 } from "node:fs";
|
|
10892
10941
|
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute8, join as join14, relative as relative3, resolve as resolve4 } from "node:path";
|
|
10893
10942
|
function gitConfig(cwd, key2) {
|
|
10894
10943
|
try {
|
|
@@ -11050,6 +11099,8 @@ function verifierSetupMessage(cwd) {
|
|
|
11050
11099
|
}
|
|
11051
11100
|
function planPolicy(cwd) {
|
|
11052
11101
|
const path = join14(cwd, POLICY_FILE);
|
|
11102
|
+
const refused = refuseNonRegular(path);
|
|
11103
|
+
if (refused) return { item: "policy", path: POLICY_FILE, status: "error", detail: refused };
|
|
11053
11104
|
if (existsSync11(path)) {
|
|
11054
11105
|
try {
|
|
11055
11106
|
loadPolicy(cwd);
|
|
@@ -11063,7 +11114,7 @@ function planPolicy(cwd) {
|
|
|
11063
11114
|
path: POLICY_FILE,
|
|
11064
11115
|
status: "create",
|
|
11065
11116
|
detail: "baseline policy with commented overrides",
|
|
11066
|
-
apply: () =>
|
|
11117
|
+
apply: () => atomicReplaceFile(path, POLICY_CONTENT, 420)
|
|
11067
11118
|
};
|
|
11068
11119
|
}
|
|
11069
11120
|
function planGitignore(cwd) {
|
|
@@ -11071,23 +11122,15 @@ function planGitignore(cwd) {
|
|
|
11071
11122
|
const path = join14(cwd, rel);
|
|
11072
11123
|
if (!existsSync11(join14(cwd, "node_modules"))) return null;
|
|
11073
11124
|
let existing = "";
|
|
11074
|
-
|
|
11075
|
-
|
|
11076
|
-
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
return { item: "gitignore", path: rel, status: "error", detail: "cannot inspect \u2014 " + errText3(e) };
|
|
11080
|
-
}
|
|
11081
|
-
if (!st.isFile()) {
|
|
11082
|
-
return {
|
|
11083
|
-
item: "gitignore",
|
|
11084
|
-
path: rel,
|
|
11085
|
-
status: "error",
|
|
11086
|
-
detail: st.isSymbolicLink() ? "is a symbolic link \u2014 refusing to follow it" : "exists but is not a regular file \u2014 refusing to write"
|
|
11087
|
-
};
|
|
11088
|
-
}
|
|
11089
|
-
existing = readFileSync12(path, "utf8");
|
|
11125
|
+
let kind;
|
|
11126
|
+
try {
|
|
11127
|
+
kind = writeTargetKind(path);
|
|
11128
|
+
} catch (e) {
|
|
11129
|
+
return { item: "gitignore", path: rel, status: "error", detail: "cannot inspect \u2014 " + errText3(e) };
|
|
11090
11130
|
}
|
|
11131
|
+
const refused = refuseNonRegular(path);
|
|
11132
|
+
if (refused) return { item: "gitignore", path: rel, status: "error", detail: refused };
|
|
11133
|
+
if (kind === "file") existing = readFileSync12(path, "utf8");
|
|
11091
11134
|
try {
|
|
11092
11135
|
const tracked = execFileSync6("git", ["ls-files", "--cached", "--", "node_modules"], {
|
|
11093
11136
|
cwd,
|
|
@@ -11120,14 +11163,27 @@ function planGitignore(cwd) {
|
|
|
11120
11163
|
return {
|
|
11121
11164
|
item: "gitignore",
|
|
11122
11165
|
path: rel,
|
|
11123
|
-
status:
|
|
11166
|
+
status: kind === "file" ? "update" : "create",
|
|
11124
11167
|
detail: "exclude installed Node dependencies from repository diffs",
|
|
11125
|
-
apply: () =>
|
|
11168
|
+
apply: () => atomicReplaceFile(path, next, existingMode(path, 420))
|
|
11126
11169
|
};
|
|
11127
11170
|
}
|
|
11171
|
+
function gateVerdicts(event, groups, needle) {
|
|
11172
|
+
const out3 = [];
|
|
11173
|
+
for (const group of groups) {
|
|
11174
|
+
for (const h of group.hooks ?? []) {
|
|
11175
|
+
if (!needle.test(String(h.command ?? ""))) continue;
|
|
11176
|
+
const { runs, problems } = judgeGateEntry(event, group.matcher, h);
|
|
11177
|
+
out3.push({ event, group, h, runs, problems });
|
|
11178
|
+
}
|
|
11179
|
+
}
|
|
11180
|
+
return out3;
|
|
11181
|
+
}
|
|
11128
11182
|
function planClaudeHooks(cwd) {
|
|
11129
11183
|
const rel = ".claude/settings.json";
|
|
11130
11184
|
const path = join14(cwd, rel);
|
|
11185
|
+
const refused = refuseNonRegular(path);
|
|
11186
|
+
if (refused) return { item: "agent", path: rel, status: "error", detail: refused };
|
|
11131
11187
|
let settings = {};
|
|
11132
11188
|
if (existsSync11(path)) {
|
|
11133
11189
|
let parsed;
|
|
@@ -11156,10 +11212,24 @@ function planClaudeHooks(cwd) {
|
|
|
11156
11212
|
const needPre = preEntries.length === 0;
|
|
11157
11213
|
const stopEntries = entriesWith(hooks.Stop, SWEEP_RE);
|
|
11158
11214
|
const needStop = stopEntries.length === 0;
|
|
11215
|
+
const neutralised = [
|
|
11216
|
+
...gateVerdicts("PreToolUse", preEntries, HOOK_RE),
|
|
11217
|
+
...gateVerdicts("Stop", stopEntries, SWEEP_RE)
|
|
11218
|
+
].filter((v) => !v.runs);
|
|
11219
|
+
const foreign = neutralised.filter((v) => ours(String(v.h.command ?? "")) === null);
|
|
11220
|
+
if (foreign.length > 0) {
|
|
11221
|
+
return {
|
|
11222
|
+
item: "agent",
|
|
11223
|
+
path: rel,
|
|
11224
|
+
status: "error",
|
|
11225
|
+
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)`
|
|
11226
|
+
};
|
|
11227
|
+
}
|
|
11159
11228
|
const stale = preEntries.filter((m) => missingTools(m.matcher).length > 0);
|
|
11160
|
-
const
|
|
11229
|
+
const restoring = new Set(neutralised.map((v) => v.h));
|
|
11230
|
+
const repin = [...preEntries, ...stopEntries].flatMap((m) => m.hooks ?? []).filter((h) => !restoring.has(h) && stalePin(String(h.command ?? "")));
|
|
11161
11231
|
const needDisableFalse = settings.disableAllHooks !== false;
|
|
11162
|
-
if (!needPre && !needStop && stale.length === 0 && repin.length === 0 && !needDisableFalse) {
|
|
11232
|
+
if (!needPre && !needStop && stale.length === 0 && repin.length === 0 && neutralised.length === 0 && !needDisableFalse) {
|
|
11163
11233
|
return { item: "agent", path: rel, status: "ok", detail: "PreToolUse + Stop hooks already wired" };
|
|
11164
11234
|
}
|
|
11165
11235
|
const repairing = stale.length > 0 ? [...new Set(stale.flatMap((m) => missingTools(m.matcher)))] : [];
|
|
@@ -11173,9 +11243,16 @@ function planClaudeHooks(cwd) {
|
|
|
11173
11243
|
needStop && "wire Stop sweep",
|
|
11174
11244
|
repairing.length > 0 && `widen the PreToolUse matcher to cover ${repairing.join(", ")}`,
|
|
11175
11245
|
repin.length > 0 && `re-pin the hook commands to tamperward@${TW_VERSION} (was ${pins.join(", ")})`,
|
|
11246
|
+
...neutralised.map((v) => `restore the ${v.event} gate entry to the shape init writes \u2014 it is neutralised (${v.problems.join("; ")})`),
|
|
11176
11247
|
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)})`)
|
|
11177
11248
|
].filter(Boolean).join(" + "),
|
|
11178
11249
|
apply: () => {
|
|
11250
|
+
for (const v of neutralised) {
|
|
11251
|
+
for (const k of Object.keys(v.h)) delete v.h[k];
|
|
11252
|
+
v.h.type = "command";
|
|
11253
|
+
v.h.command = v.event === "PreToolUse" ? HOOK_CMD : SWEEP_CMD;
|
|
11254
|
+
if (v.event === "Stop") delete v.group.matcher;
|
|
11255
|
+
}
|
|
11179
11256
|
for (const h of repin) {
|
|
11180
11257
|
const kind = ours(String(h.command ?? ""))?.[2];
|
|
11181
11258
|
h.command = kind === "hook claude" ? HOOK_CMD : kind === "sweep claude" ? SWEEP_CMD : h.command;
|
|
@@ -11194,7 +11271,7 @@ function planClaudeHooks(cwd) {
|
|
|
11194
11271
|
}
|
|
11195
11272
|
if (needDisableFalse) settings.disableAllHooks = false;
|
|
11196
11273
|
mkdirSync6(dirname4(path), { recursive: true });
|
|
11197
|
-
|
|
11274
|
+
atomicReplaceFile(path, JSON.stringify(settings, null, 2) + "\n", existingMode(path, 420));
|
|
11198
11275
|
}
|
|
11199
11276
|
};
|
|
11200
11277
|
}
|
|
@@ -11251,10 +11328,10 @@ function planPreCommit(cwd) {
|
|
|
11251
11328
|
}
|
|
11252
11329
|
}
|
|
11253
11330
|
const rel = display(cwd, path);
|
|
11254
|
-
|
|
11255
|
-
|
|
11256
|
-
}
|
|
11331
|
+
const refused = refuseNonRegular(path);
|
|
11332
|
+
if (refused) return { item: "pre-commit", path: rel, status: "error", detail: refused };
|
|
11257
11333
|
const existing = existsSync11(path) ? readFileSync12(path, "utf8") : null;
|
|
11334
|
+
const hookMode = () => existingMode(path, 493) | 73;
|
|
11258
11335
|
if (existing === null) {
|
|
11259
11336
|
return {
|
|
11260
11337
|
item: "pre-commit",
|
|
@@ -11263,10 +11340,9 @@ function planPreCommit(cwd) {
|
|
|
11263
11340
|
detail: `create via ${note}`,
|
|
11264
11341
|
apply: () => {
|
|
11265
11342
|
mkdirSync6(dirname4(path), { recursive: true });
|
|
11266
|
-
|
|
11343
|
+
atomicReplaceFile(path, `#!/bin/sh
|
|
11267
11344
|
${block.join("\n")}
|
|
11268
|
-
|
|
11269
|
-
chmodSync2(path, 493);
|
|
11345
|
+
`, hookMode());
|
|
11270
11346
|
}
|
|
11271
11347
|
};
|
|
11272
11348
|
}
|
|
@@ -11276,11 +11352,8 @@ ${block.join("\n")}
|
|
|
11276
11352
|
}
|
|
11277
11353
|
const lines = existing.split("\n");
|
|
11278
11354
|
const dead = unconditionalExitAt(lines);
|
|
11279
|
-
const at = lines.findIndex((l) => PRECOMMIT_RE.test(l));
|
|
11280
|
-
const write = (out3) =>
|
|
11281
|
-
writeFileSync7(path, out3.join("\n"));
|
|
11282
|
-
chmodSync2(path, 493);
|
|
11283
|
-
};
|
|
11355
|
+
const at = lines.findIndex((l) => !/^\s*#/.test(l) && PRECOMMIT_RE.test(l));
|
|
11356
|
+
const write = (out3) => atomicReplaceFile(path, out3.join("\n"), hookMode());
|
|
11284
11357
|
if (at !== -1 && (dead === -1 || at < dead)) {
|
|
11285
11358
|
if (!stalePin(lines[at])) {
|
|
11286
11359
|
return { item: "pre-commit", path: rel, status: "ok", detail: "already runs the staged check" };
|
|
@@ -11343,9 +11416,11 @@ ${block.join("\n")}
|
|
|
11343
11416
|
function planCodeowners(cwd) {
|
|
11344
11417
|
const rel = ".github/CODEOWNERS";
|
|
11345
11418
|
const existingRel = [".github/CODEOWNERS", "CODEOWNERS", "docs/CODEOWNERS"].find(
|
|
11346
|
-
(r) =>
|
|
11419
|
+
(r) => writeTargetKind(join14(cwd, r)) !== "absent"
|
|
11347
11420
|
);
|
|
11348
11421
|
const path = join14(cwd, existingRel ?? rel);
|
|
11422
|
+
const refused = refuseNonRegular(path);
|
|
11423
|
+
if (refused) return { item: "codeowners", path: existingRel ?? rel, status: "error", detail: refused };
|
|
11349
11424
|
const existing = existingRel ? readFileSync12(path, "utf8") : null;
|
|
11350
11425
|
const missing = CODEOWNERS_PATHS.filter((p) => existing === null || !coveredBy(existing, p));
|
|
11351
11426
|
if (existingRel !== void 0 && missing.length === 0) {
|
|
@@ -11373,7 +11448,7 @@ ${CODEOWNERS_MARK}
|
|
|
11373
11448
|
} : {},
|
|
11374
11449
|
apply: () => {
|
|
11375
11450
|
mkdirSync6(dirname4(path), { recursive: true });
|
|
11376
|
-
|
|
11451
|
+
atomicReplaceFile(path, existing === null ? block.replace(/^\n/, "") : existing.replace(/\n?$/, "\n") + block, existingMode(path, 420));
|
|
11377
11452
|
}
|
|
11378
11453
|
};
|
|
11379
11454
|
}
|
|
@@ -11382,8 +11457,10 @@ function planWorkflow(cwd, force) {
|
|
|
11382
11457
|
const path = join14(cwd, rel);
|
|
11383
11458
|
const write = () => {
|
|
11384
11459
|
mkdirSync6(dirname4(path), { recursive: true });
|
|
11385
|
-
|
|
11460
|
+
atomicReplaceFile(path, workflowFile(WORKFLOW_CONTENT), existingMode(path, 420));
|
|
11386
11461
|
};
|
|
11462
|
+
const refused = refuseNonRegular(path);
|
|
11463
|
+
if (refused) return { item: "ci", path: rel, status: "error", detail: refused };
|
|
11387
11464
|
if (!existsSync11(path)) {
|
|
11388
11465
|
return {
|
|
11389
11466
|
item: "ci",
|
|
@@ -11518,7 +11595,9 @@ var init_init = __esm({
|
|
|
11518
11595
|
init_verifier_limits();
|
|
11519
11596
|
init_wiring();
|
|
11520
11597
|
init_narrow();
|
|
11598
|
+
init_hook_tampering();
|
|
11521
11599
|
init_repo_context();
|
|
11600
|
+
init_safe_write();
|
|
11522
11601
|
errText3 = (e) => (e instanceof Error ? e.message : String(e)).replace(/\s+/g, " ").trim();
|
|
11523
11602
|
isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
11524
11603
|
HOOK_RE = /\btamperward(?:@\S+)?\s+hook\s+claude\b/;
|
|
@@ -11693,7 +11772,7 @@ import {
|
|
|
11693
11772
|
readFileSync as readFileSync13,
|
|
11694
11773
|
readlinkSync as readlinkSync2,
|
|
11695
11774
|
realpathSync as realpathSync5,
|
|
11696
|
-
statSync as
|
|
11775
|
+
statSync as statSync3
|
|
11697
11776
|
} from "node:fs";
|
|
11698
11777
|
import {
|
|
11699
11778
|
basename as basename3,
|
|
@@ -11735,7 +11814,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
11735
11814
|
root.kind + " root moved from " + JSON.stringify(root.realPath) + " to " + JSON.stringify(currentReal)
|
|
11736
11815
|
);
|
|
11737
11816
|
}
|
|
11738
|
-
if (!
|
|
11817
|
+
if (!statSync3(root.path).isDirectory()) {
|
|
11739
11818
|
throw new Unattestable(root.kind + " root " + JSON.stringify(root.path) + " is not a directory");
|
|
11740
11819
|
}
|
|
11741
11820
|
h.update("root\0" + root.kind + "\0" + root.path + "\0" + root.realPath + "\0" + String(rootStat.mode) + "\0");
|
|
@@ -11745,7 +11824,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
11745
11824
|
const hashFile = (path, label) => {
|
|
11746
11825
|
let st;
|
|
11747
11826
|
try {
|
|
11748
|
-
st =
|
|
11827
|
+
st = statSync3(path);
|
|
11749
11828
|
if (!st.isFile()) throw new Error("not a regular file");
|
|
11750
11829
|
h.update(label + "\0external-file\0" + path + "\0" + String(st.mode) + "\0");
|
|
11751
11830
|
h.update(readFileSync13(path));
|
|
@@ -11811,7 +11890,7 @@ function fingerprintRoot(root, cwd, h) {
|
|
|
11811
11890
|
} catch {
|
|
11812
11891
|
continue;
|
|
11813
11892
|
}
|
|
11814
|
-
const followed =
|
|
11893
|
+
const followed = statSync3(path);
|
|
11815
11894
|
if (followed.isFile()) {
|
|
11816
11895
|
hashFile(resolved, "@target:" + rel);
|
|
11817
11896
|
continue;
|
|
@@ -11859,7 +11938,7 @@ function measuredSnapshot(roots, probes, cwd, metrics) {
|
|
|
11859
11938
|
}
|
|
11860
11939
|
function freezeRoot(kind, path) {
|
|
11861
11940
|
try {
|
|
11862
|
-
if (!
|
|
11941
|
+
if (!statSync3(path).isDirectory()) {
|
|
11863
11942
|
throw new Error("not a directory");
|
|
11864
11943
|
}
|
|
11865
11944
|
return { kind, path: resolve5(path), realPath: realpathSync5(path) };
|
|
@@ -11908,7 +11987,7 @@ function resolveExecutable(token, cwd, env) {
|
|
|
11908
11987
|
for (const entry of (env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
11909
11988
|
const candidate = resolve5(entry, token);
|
|
11910
11989
|
try {
|
|
11911
|
-
if (existsSync12(candidate) &&
|
|
11990
|
+
if (existsSync12(candidate) && statSync3(candidate).isFile()) return realpathSync5(candidate);
|
|
11912
11991
|
} catch {
|
|
11913
11992
|
}
|
|
11914
11993
|
}
|
|
@@ -12095,7 +12174,7 @@ var init_dependency_env = __esm({
|
|
|
12095
12174
|
|
|
12096
12175
|
// src/suite-diagnostics.ts
|
|
12097
12176
|
import { spawnSync } from "node:child_process";
|
|
12098
|
-
import { mkdtempSync as mkdtempSync2, rmSync as
|
|
12177
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "node:fs";
|
|
12099
12178
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
12100
12179
|
import { join as join16 } from "node:path";
|
|
12101
12180
|
function emptySuiteDiagnostics() {
|
|
@@ -12224,7 +12303,7 @@ function runCapturedProcessSync(executable, args, opts) {
|
|
|
12224
12303
|
}
|
|
12225
12304
|
return parsed;
|
|
12226
12305
|
} finally {
|
|
12227
|
-
|
|
12306
|
+
rmSync6(stateDir, { recursive: true, force: true });
|
|
12228
12307
|
}
|
|
12229
12308
|
}
|
|
12230
12309
|
var DIAGNOSTIC_TAIL_BYTES, EMPTY_STREAM, CAPTURE_SUPERVISOR;
|
|
@@ -12414,7 +12493,7 @@ if (child) {
|
|
|
12414
12493
|
// src/verifier-backend.ts
|
|
12415
12494
|
import { createHash as createHash9, randomUUID } from "node:crypto";
|
|
12416
12495
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
12417
|
-
import { accessSync as accessSync2, constants as constants3, readFileSync as readFileSync15, realpathSync as realpathSync6, statSync as
|
|
12496
|
+
import { accessSync as accessSync2, constants as constants3, readFileSync as readFileSync15, realpathSync as realpathSync6, statSync as statSync4 } from "node:fs";
|
|
12418
12497
|
import { delimiter as delimiter2, dirname as dirname6, join as join17, resolve as resolve6 } from "node:path";
|
|
12419
12498
|
function identity() {
|
|
12420
12499
|
const uid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
@@ -12423,7 +12502,7 @@ function identity() {
|
|
|
12423
12502
|
}
|
|
12424
12503
|
function writableByCaller(path) {
|
|
12425
12504
|
try {
|
|
12426
|
-
const st =
|
|
12505
|
+
const st = statSync4(path);
|
|
12427
12506
|
const { uid, groups } = identity();
|
|
12428
12507
|
if (uid === 0) return true;
|
|
12429
12508
|
if ((st.mode & 2) !== 0) return true;
|
|
@@ -12893,7 +12972,7 @@ var init_verifier_backend = __esm({
|
|
|
12893
12972
|
// src/cli/verify.ts
|
|
12894
12973
|
import { execFileSync as execFileSync8, spawnSync as spawnSync3 } from "node:child_process";
|
|
12895
12974
|
import { createHash as createHash10 } from "node:crypto";
|
|
12896
|
-
import { chmodSync as
|
|
12975
|
+
import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync9, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, readFileSync as readFileSync16, readlinkSync as readlinkSync3, rmSync as rmSync7, symlinkSync, writeFileSync as writeFileSync9, existsSync as existsSync13 } from "node:fs";
|
|
12897
12976
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
12898
12977
|
import { dirname as dirname7, isAbsolute as isAbsolute10, join as join18, relative as relative5, resolve as resolve7, sep as sep3 } from "node:path";
|
|
12899
12978
|
function oracleAssuranceReport() {
|
|
@@ -12941,7 +13020,7 @@ function safeSymlink(target, out3, root, label) {
|
|
|
12941
13020
|
if (rootedLinkTarget(target) || !inside2(root, resolve7(dirname7(out3), target))) {
|
|
12942
13021
|
throw linkEscape(label, target);
|
|
12943
13022
|
}
|
|
12944
|
-
|
|
13023
|
+
rmSync7(out3, { force: true });
|
|
12945
13024
|
symlinkSync(target, out3);
|
|
12946
13025
|
}
|
|
12947
13026
|
function validateSymlinkGraph(target, out3, root, dependencyRoot, label) {
|
|
@@ -13032,7 +13111,7 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
13032
13111
|
links.push({ rel, out: out3, target });
|
|
13033
13112
|
} else if (st.isFile()) {
|
|
13034
13113
|
cpSync(src, out3, { dereference: true });
|
|
13035
|
-
|
|
13114
|
+
chmodSync2(out3, st.mode);
|
|
13036
13115
|
} else {
|
|
13037
13116
|
throw new Error(`${rel} is ${st.isDirectory() ? "a directory" : "a special file"} where git expects a file`);
|
|
13038
13117
|
}
|
|
@@ -13052,7 +13131,7 @@ function probeFilesystemCaseSensitivity(dir) {
|
|
|
13052
13131
|
writeFileSync9(upper, "", { flag: "wx" });
|
|
13053
13132
|
return !existsSync13(lower);
|
|
13054
13133
|
} finally {
|
|
13055
|
-
|
|
13134
|
+
rmSync7(upper, { force: true });
|
|
13056
13135
|
}
|
|
13057
13136
|
}
|
|
13058
13137
|
function foldCase(p) {
|
|
@@ -13070,7 +13149,7 @@ function caseFoldCollision(paths) {
|
|
|
13070
13149
|
}
|
|
13071
13150
|
function dropSymlink(p) {
|
|
13072
13151
|
try {
|
|
13073
|
-
if (lstatSync9(p).isSymbolicLink())
|
|
13152
|
+
if (lstatSync9(p).isSymbolicLink()) rmSync7(p, { force: true });
|
|
13074
13153
|
} catch {
|
|
13075
13154
|
}
|
|
13076
13155
|
}
|
|
@@ -13146,14 +13225,14 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensi
|
|
|
13146
13225
|
const content = execFileSync8("git", ["show", `${base}:${e.path}`], { cwd, maxBuffer: 1 << 28, env: trustedGitEnv() });
|
|
13147
13226
|
const out3 = join18(dest, e.path);
|
|
13148
13227
|
mkdirSync7(dirname7(out3), { recursive: true });
|
|
13149
|
-
|
|
13228
|
+
rmSync7(out3, { force: true });
|
|
13150
13229
|
if (e.mode === "120000") {
|
|
13151
13230
|
const target = content.toString("utf8");
|
|
13152
13231
|
safeSymlink(target, out3, dest, `the base's ${e.path}`);
|
|
13153
13232
|
restoredLinks.push({ path: e.path, out: out3, target });
|
|
13154
13233
|
} else {
|
|
13155
13234
|
writeFileSync9(out3, content);
|
|
13156
|
-
|
|
13235
|
+
chmodSync2(out3, parseInt(e.mode.slice(-4), 8) & 511);
|
|
13157
13236
|
}
|
|
13158
13237
|
restored.push(e.path);
|
|
13159
13238
|
}
|
|
@@ -13161,7 +13240,7 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensi
|
|
|
13161
13240
|
const inCopy = workingTreePaths(cwd);
|
|
13162
13241
|
for (const rel of inCopy) {
|
|
13163
13242
|
if (!isOverlay(rel) || baseProtected.has(fold(rel))) continue;
|
|
13164
|
-
|
|
13243
|
+
rmSync7(join18(dest, rel), { force: true });
|
|
13165
13244
|
removed++;
|
|
13166
13245
|
}
|
|
13167
13246
|
for (const link of restoredLinks) {
|
|
@@ -13248,7 +13327,7 @@ function runLocalSuite(dir, cmd, budgetSecs) {
|
|
|
13248
13327
|
}
|
|
13249
13328
|
return { exit: r.exit, secs, diagnostics: r.diagnostics };
|
|
13250
13329
|
} finally {
|
|
13251
|
-
|
|
13330
|
+
rmSync7(scratch, { recursive: true, force: true });
|
|
13252
13331
|
}
|
|
13253
13332
|
}
|
|
13254
13333
|
function diagnosticsJson(diagnostics, includeTail) {
|
|
@@ -13401,7 +13480,7 @@ function runVerify(opts) {
|
|
|
13401
13480
|
const visDir = join18(visRoot, "t");
|
|
13402
13481
|
const cleanup = (dirs) => {
|
|
13403
13482
|
if (opts.keep) return;
|
|
13404
|
-
for (const d of dirs)
|
|
13483
|
+
for (const d of dirs) rmSync7(d, { recursive: true, force: true });
|
|
13405
13484
|
};
|
|
13406
13485
|
filesystemCaseSensitive = (opts.probeCaseSensitivity ?? probeFilesystemCaseSensitivity)(visRoot);
|
|
13407
13486
|
if (!filesystemCaseSensitive) {
|
|
@@ -13767,8 +13846,8 @@ var init_verify = __esm({
|
|
|
13767
13846
|
|
|
13768
13847
|
// src/cli/run.ts
|
|
13769
13848
|
import { execFileSync as execFileSync9, spawn, spawnSync as spawnSync4 } from "node:child_process";
|
|
13770
|
-
import { randomBytes } from "node:crypto";
|
|
13771
|
-
import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync17, readlinkSync as readlinkSync4, realpathSync as realpathSync8, rmSync as
|
|
13849
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
13850
|
+
import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync17, readlinkSync as readlinkSync4, realpathSync as realpathSync8, rmSync as rmSync8, statSync as statSync5, writeFileSync as writeFileSync10 } from "node:fs";
|
|
13772
13851
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
13773
13852
|
import { dirname as dirname8, join as join19, resolve as resolve8 } from "node:path";
|
|
13774
13853
|
function authoritativeRunLifecyclePlatform(platform = process.platform) {
|
|
@@ -13824,7 +13903,7 @@ function trustedLinuxPython(candidates = DEFAULT_TRUSTED_PYTHON_CANDIDATES) {
|
|
|
13824
13903
|
for (const candidate of candidates) {
|
|
13825
13904
|
try {
|
|
13826
13905
|
const real = realpathSync8(candidate);
|
|
13827
|
-
const st =
|
|
13906
|
+
const st = statSync5(real);
|
|
13828
13907
|
if (!st.isFile() || writableByCaller2(real)) continue;
|
|
13829
13908
|
const probe = spawnSync4(
|
|
13830
13909
|
real,
|
|
@@ -13947,7 +14026,7 @@ function runAgentSupervised(argv, cwd, budgetSecs, linuxPythonCandidates, lifecy
|
|
|
13947
14026
|
...state.failure ? { failure: state.failure } : {}
|
|
13948
14027
|
};
|
|
13949
14028
|
} finally {
|
|
13950
|
-
|
|
14029
|
+
rmSync8(stateDir, { recursive: true, force: true });
|
|
13951
14030
|
}
|
|
13952
14031
|
}
|
|
13953
14032
|
function waitMs(ms) {
|
|
@@ -13978,7 +14057,7 @@ function supervisedObserverLog(cwd) {
|
|
|
13978
14057
|
return join19(
|
|
13979
14058
|
gd,
|
|
13980
14059
|
"tamperward",
|
|
13981
|
-
`run-observer-${process.pid}-${Date.now()}-${
|
|
14060
|
+
`run-observer-${process.pid}-${Date.now()}-${randomBytes2(4).toString("hex")}.jsonl`
|
|
13982
14061
|
);
|
|
13983
14062
|
}
|
|
13984
14063
|
function startSupervisedObserver(cwd, base, entry) {
|
|
@@ -15320,7 +15399,7 @@ import {
|
|
|
15320
15399
|
readFileSync as readFileSync19,
|
|
15321
15400
|
readdirSync as readdirSync6,
|
|
15322
15401
|
realpathSync as realpathSync9,
|
|
15323
|
-
rmSync as
|
|
15402
|
+
rmSync as rmSync9,
|
|
15324
15403
|
symlinkSync as symlinkSync2
|
|
15325
15404
|
} from "node:fs";
|
|
15326
15405
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
@@ -15518,8 +15597,8 @@ function runOneTrace(cwd, base, command, budget) {
|
|
|
15518
15597
|
accesses: rewriteTraceRoot(accesses, root)
|
|
15519
15598
|
};
|
|
15520
15599
|
} finally {
|
|
15521
|
-
|
|
15522
|
-
|
|
15600
|
+
rmSync9(root, { recursive: true, force: true });
|
|
15601
|
+
rmSync9(traceDir, { recursive: true, force: true });
|
|
15523
15602
|
}
|
|
15524
15603
|
}
|
|
15525
15604
|
function renderText2(report2) {
|
|
@@ -15703,8 +15782,7 @@ var init_trace_verify = __esm({
|
|
|
15703
15782
|
|
|
15704
15783
|
// src/cli/onboard.ts
|
|
15705
15784
|
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
15706
|
-
import {
|
|
15707
|
-
import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync20, realpathSync as realpathSync10, renameSync as renameSync3, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "node:fs";
|
|
15785
|
+
import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync20, realpathSync as realpathSync10, rmSync as rmSync10 } from "node:fs";
|
|
15708
15786
|
import { tmpdir as tmpdir7 } from "node:os";
|
|
15709
15787
|
import { join as join22, relative as relative7, resolve as resolve11, sep as sep5 } from "node:path";
|
|
15710
15788
|
import { createInterface } from "node:readline";
|
|
@@ -15773,23 +15851,14 @@ function explainVerdict(v, code2) {
|
|
|
15773
15851
|
return `Could not verify${v?.reason ? ` (${v.reason})` : ""} \u2014 TamperWard failed closed${v?.detail ? `: ${v.detail}` : "."}`;
|
|
15774
15852
|
}
|
|
15775
15853
|
}
|
|
15776
|
-
function atomicReplaceFile(path, content, mode) {
|
|
15777
|
-
const tmp = `${path}.${process.pid}.${randomBytes2(8).toString("hex")}.tmp`;
|
|
15778
|
-
try {
|
|
15779
|
-
writeFileSync11(tmp, content, { encoding: "utf8", flag: "wx", mode });
|
|
15780
|
-
renameSync3(tmp, path);
|
|
15781
|
-
} finally {
|
|
15782
|
-
rmSync9(tmp, { force: true });
|
|
15783
|
-
}
|
|
15784
|
-
}
|
|
15785
15854
|
function writeVerifyCommand(cwd, command) {
|
|
15786
15855
|
const path = join22(cwd, POLICY_FILE);
|
|
15787
|
-
const
|
|
15788
|
-
if (
|
|
15856
|
+
const kind = writeTargetKind(path);
|
|
15857
|
+
if (kind === "symlink" || kind === "irregular") {
|
|
15789
15858
|
return `${POLICY_FILE} is not a regular file; refusing to follow or replace a symlink or special file`;
|
|
15790
15859
|
}
|
|
15791
|
-
const mode =
|
|
15792
|
-
const original =
|
|
15860
|
+
const mode = existingMode(path, 420);
|
|
15861
|
+
const original = kind === "file" ? readFileSync20(path, "utf8") : null;
|
|
15793
15862
|
let next;
|
|
15794
15863
|
try {
|
|
15795
15864
|
const doc = parseDocument(original && original.trim() ? original : "version: 1\n");
|
|
@@ -15806,7 +15875,7 @@ function writeVerifyCommand(cwd, command) {
|
|
|
15806
15875
|
if (loaded2.verify?.command !== command) throw new Error("verify.command did not round-trip");
|
|
15807
15876
|
return null;
|
|
15808
15877
|
} catch (e) {
|
|
15809
|
-
if (original === null)
|
|
15878
|
+
if (original === null) rmSync10(path, { force: true });
|
|
15810
15879
|
else atomicReplaceFile(path, original, mode);
|
|
15811
15880
|
return `the updated ${POLICY_FILE} does not load (${errorMessage(e)}); restored the previous file`;
|
|
15812
15881
|
}
|
|
@@ -16206,7 +16275,7 @@ function runDemo(cwd, head, runners, out3) {
|
|
|
16206
16275
|
} finally {
|
|
16207
16276
|
git5(cwd, ["worktree", "remove", "--force", wt]);
|
|
16208
16277
|
git5(cwd, ["worktree", "prune"]);
|
|
16209
|
-
|
|
16278
|
+
rmSync10(wt, { recursive: true, force: true });
|
|
16210
16279
|
}
|
|
16211
16280
|
const after = treeFingerprint(cwd);
|
|
16212
16281
|
out3(`working tree fingerprint after: ${after.slice(0, 16)}`);
|
|
@@ -16231,6 +16300,7 @@ var init_onboard = __esm({
|
|
|
16231
16300
|
init_policy_load();
|
|
16232
16301
|
init_narrow();
|
|
16233
16302
|
init_wiring();
|
|
16303
|
+
init_safe_write();
|
|
16234
16304
|
Aborted = class extends Error {
|
|
16235
16305
|
};
|
|
16236
16306
|
SECTIONS = [
|
|
@@ -16621,7 +16691,7 @@ var init_record = __esm({
|
|
|
16621
16691
|
|
|
16622
16692
|
// src/research/run.ts
|
|
16623
16693
|
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
16624
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync22, renameSync as renameSync4, rmSync as
|
|
16694
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync22, renameSync as renameSync4, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
|
|
16625
16695
|
import { join as join23, resolve as resolve14 } from "node:path";
|
|
16626
16696
|
function git6(args, cwd) {
|
|
16627
16697
|
return execFileSync13("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
@@ -16669,12 +16739,12 @@ function resumableRecord(path, expected) {
|
|
|
16669
16739
|
}
|
|
16670
16740
|
function writeRecordAtomically(path, text) {
|
|
16671
16741
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
16672
|
-
|
|
16742
|
+
writeFileSync11(tmp, text);
|
|
16673
16743
|
renameSync4(tmp, path);
|
|
16674
16744
|
}
|
|
16675
16745
|
function freshWorkspace(ledger, task, pair, arm, sourceBase) {
|
|
16676
16746
|
const ws2 = join23(ledger, "workspaces", `${task.id}--${pair}--${arm}`);
|
|
16677
|
-
|
|
16747
|
+
rmSync11(ws2, { recursive: true, force: true });
|
|
16678
16748
|
mkdirSync8(join23(ledger, "workspaces"), { recursive: true });
|
|
16679
16749
|
try {
|
|
16680
16750
|
execFileSync13("git", ["clone", "-q", "--no-hardlinks", task.repo, ws2], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.23.
|
|
3
|
+
"version": "2.23.17",
|
|
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",
|