tamperward 2.10.5 → 2.10.7
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 +138 -16
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2869,21 +2869,33 @@ import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "no
|
|
|
2869
2869
|
import { homedir } from "node:os";
|
|
2870
2870
|
import { basename, dirname, isAbsolute as isAbsolute3, join as join5, posix } from "node:path";
|
|
2871
2871
|
import { fileURLToPath } from "node:url";
|
|
2872
|
+
var PLAIN_SEMVER = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
|
|
2872
2873
|
function shippedVersion() {
|
|
2873
2874
|
try {
|
|
2874
2875
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
2875
2876
|
for (const rel of ["../package.json", "../../package.json", "../../../package.json"]) {
|
|
2876
2877
|
try {
|
|
2877
2878
|
const pkg = JSON.parse(readFileSync3(join5(here, rel), "utf8"));
|
|
2878
|
-
if (pkg.name === "tamperward" && pkg.version
|
|
2879
|
+
if (pkg.name === "tamperward" && typeof pkg.version === "string" && PLAIN_SEMVER.test(pkg.version)) {
|
|
2880
|
+
return pkg.version;
|
|
2881
|
+
}
|
|
2879
2882
|
} catch {
|
|
2880
2883
|
}
|
|
2881
2884
|
}
|
|
2882
2885
|
} catch {
|
|
2883
2886
|
}
|
|
2884
|
-
return
|
|
2887
|
+
return null;
|
|
2888
|
+
}
|
|
2889
|
+
var RESOLVED_TW_VERSION = shippedVersion();
|
|
2890
|
+
var TW_VERSION = RESOLVED_TW_VERSION ?? "0.0.0-unresolved";
|
|
2891
|
+
function requireShippedVersion() {
|
|
2892
|
+
if (RESOLVED_TW_VERSION === null) {
|
|
2893
|
+
throw new Error(
|
|
2894
|
+
"cannot resolve a plain TamperWard release version from package.json; refusing to generate canonical wiring"
|
|
2895
|
+
);
|
|
2896
|
+
}
|
|
2897
|
+
return RESOLVED_TW_VERSION;
|
|
2885
2898
|
}
|
|
2886
|
-
var TW_VERSION = shippedVersion();
|
|
2887
2899
|
var PRE_TOOLS = ["Bash", "Edit", "Write", "MultiEdit", "NotebookEdit"];
|
|
2888
2900
|
var PRE_MATCHER = PRE_TOOLS.join("|");
|
|
2889
2901
|
var MARKER = "# tamperward: block agent shortcuts before they land";
|
|
@@ -2893,7 +2905,6 @@ var HOOK_CMD = `${NPX_AUTHORITY} tamperward@${TW_VERSION} hook claude${AUTHORITY
|
|
|
2893
2905
|
var SWEEP_CMD = `${NPX_AUTHORITY} tamperward@${TW_VERSION} sweep claude${AUTHORITY_FAIL_CLOSED}`;
|
|
2894
2906
|
var PRECOMMIT_CMD = `${NPX_AUTHORITY} tamperward@${TW_VERSION} check --staged`;
|
|
2895
2907
|
var OURS = /^\s*npx --yes --global --userconfig=\/dev\/null --globalconfig=\/dev\/null\/npmrc-global --registry=https:\/\/registry\.npmjs\.org\/ --node-options=' ' --script-shell= --ignore-scripts --offline=false --prefer-online tamperward(?:@(\S+))? (?:(hook claude|sweep claude) \|\| \(echo tamperward: authority failed to start >&2 && exit 2\)|(check --staged))\s*$/;
|
|
2896
|
-
var PLAIN_SEMVER = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
|
|
2897
2908
|
function compareVersions(a, b) {
|
|
2898
2909
|
if (!PLAIN_SEMVER.test(a) || !PLAIN_SEMVER.test(b)) return null;
|
|
2899
2910
|
const pa = a.split(".").map(Number);
|
|
@@ -8092,6 +8103,7 @@ function planned(item, path, plan) {
|
|
|
8092
8103
|
}
|
|
8093
8104
|
}
|
|
8094
8105
|
function planInit(cwd, opts = {}) {
|
|
8106
|
+
requireShippedVersion();
|
|
8095
8107
|
return [
|
|
8096
8108
|
planned("policy", POLICY_FILE, () => planPolicy(cwd)),
|
|
8097
8109
|
planned("agent", ".claude/settings.json", () => planClaudeHooks(cwd)),
|
|
@@ -8149,9 +8161,9 @@ tamperward init: ${changed} change(s) applied. Commit them so the gate travels w
|
|
|
8149
8161
|
// src/cli/verify.ts
|
|
8150
8162
|
import { execFileSync as execFileSync8, spawnSync } from "node:child_process";
|
|
8151
8163
|
import { createHash as createHash8 } from "node:crypto";
|
|
8152
|
-
import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync3, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, readFileSync as readFileSync14, readlinkSync as readlinkSync3,
|
|
8164
|
+
import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync3, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, readFileSync as readFileSync14, readlinkSync as readlinkSync3, realpathSync as realpathSync3, rmSync as rmSync3, symlinkSync, writeFileSync as writeFileSync6, existsSync as existsSync10 } from "node:fs";
|
|
8153
8165
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
8154
|
-
import { dirname as dirname5, join as join14, resolve as resolve4 } from "node:path";
|
|
8166
|
+
import { dirname as dirname5, isAbsolute as isAbsolute9, join as join14, relative as relative4, resolve as resolve4, sep } from "node:path";
|
|
8155
8167
|
|
|
8156
8168
|
// src/fingerprint.ts
|
|
8157
8169
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
@@ -8308,29 +8320,131 @@ function baseIsAncestorOfHead(base, cwd) {
|
|
|
8308
8320
|
const r = spawnSync("git", ["merge-base", "--is-ancestor", base, "HEAD"], { cwd, env: trustedGitEnv() });
|
|
8309
8321
|
return r.status === 0;
|
|
8310
8322
|
}
|
|
8323
|
+
function inside(root, path) {
|
|
8324
|
+
const rel = relative4(resolve4(root), resolve4(path));
|
|
8325
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute9(rel);
|
|
8326
|
+
}
|
|
8327
|
+
function linkParts(path) {
|
|
8328
|
+
const parts = sep === "\\" ? path.split(/[\\/]+/) : path.split("/");
|
|
8329
|
+
return parts.filter((part) => part !== "" && part !== ".");
|
|
8330
|
+
}
|
|
8331
|
+
function linkEscape(label, target) {
|
|
8332
|
+
return new Error(`${label} is a symlink that escapes the materialised tree (${JSON.stringify(target)})`);
|
|
8333
|
+
}
|
|
8334
|
+
function driveRelativeLinkTarget(target) {
|
|
8335
|
+
return sep === "\\" && /^[A-Za-z]:(?![\\/])/.test(target);
|
|
8336
|
+
}
|
|
8337
|
+
function rootedLinkTarget(target) {
|
|
8338
|
+
return isAbsolute9(target) || driveRelativeLinkTarget(target);
|
|
8339
|
+
}
|
|
8340
|
+
function safeSymlink(target, out2, root, label) {
|
|
8341
|
+
if (rootedLinkTarget(target) || !inside(root, resolve4(dirname5(out2), target))) {
|
|
8342
|
+
throw linkEscape(label, target);
|
|
8343
|
+
}
|
|
8344
|
+
rmSync3(out2, { force: true });
|
|
8345
|
+
symlinkSync(target, out2);
|
|
8346
|
+
}
|
|
8347
|
+
function validateSymlinkGraph(target, out2, root, dependencyRoot, label) {
|
|
8348
|
+
let domain = "tree";
|
|
8349
|
+
let stack = linkParts(relative4(root, dirname5(out2)));
|
|
8350
|
+
const pending = linkParts(target);
|
|
8351
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8352
|
+
let hops = 0;
|
|
8353
|
+
while (pending.length) {
|
|
8354
|
+
const part = pending.shift();
|
|
8355
|
+
if (part === "..") {
|
|
8356
|
+
if (!stack.length) throw linkEscape(label, target);
|
|
8357
|
+
stack.pop();
|
|
8358
|
+
continue;
|
|
8359
|
+
}
|
|
8360
|
+
stack.push(part);
|
|
8361
|
+
if (domain === "tree" && dependencyRoot && stack.length === 1 && stack[0] === "node_modules") {
|
|
8362
|
+
domain = "deps";
|
|
8363
|
+
stack = [];
|
|
8364
|
+
continue;
|
|
8365
|
+
}
|
|
8366
|
+
const base = domain === "tree" ? root : dependencyRoot;
|
|
8367
|
+
const current = join14(base, ...stack);
|
|
8368
|
+
let st;
|
|
8369
|
+
try {
|
|
8370
|
+
st = lstatSync3(current);
|
|
8371
|
+
} catch {
|
|
8372
|
+
continue;
|
|
8373
|
+
}
|
|
8374
|
+
if (!st.isSymbolicLink()) continue;
|
|
8375
|
+
if (++hops > 128) {
|
|
8376
|
+
throw new Error(`${label} contains a symlink cycle while materialising (${JSON.stringify(target)})`);
|
|
8377
|
+
}
|
|
8378
|
+
const key2 = `${domain}:${current}:${pending.join(sep)}`;
|
|
8379
|
+
if (seen.has(key2)) {
|
|
8380
|
+
throw new Error(`${label} contains a symlink cycle while materialising (${JSON.stringify(target)})`);
|
|
8381
|
+
}
|
|
8382
|
+
seen.add(key2);
|
|
8383
|
+
const next = readlinkSync3(current);
|
|
8384
|
+
stack.pop();
|
|
8385
|
+
if (driveRelativeLinkTarget(next)) {
|
|
8386
|
+
throw linkEscape(label, target);
|
|
8387
|
+
}
|
|
8388
|
+
if (isAbsolute9(next)) {
|
|
8389
|
+
if (domain === "tree" || !dependencyRoot || !inside(dependencyRoot, next)) {
|
|
8390
|
+
throw linkEscape(label, target);
|
|
8391
|
+
}
|
|
8392
|
+
stack = [];
|
|
8393
|
+
pending.unshift(...linkParts(relative4(dependencyRoot, resolve4(next))));
|
|
8394
|
+
} else {
|
|
8395
|
+
pending.unshift(...linkParts(next));
|
|
8396
|
+
}
|
|
8397
|
+
}
|
|
8398
|
+
}
|
|
8399
|
+
function rejectLinkedParent(cwd, rel) {
|
|
8400
|
+
const parts = rel.split("/").slice(0, -1);
|
|
8401
|
+
let current = cwd;
|
|
8402
|
+
for (const part of parts) {
|
|
8403
|
+
current = join14(current, part);
|
|
8404
|
+
let st;
|
|
8405
|
+
try {
|
|
8406
|
+
st = lstatSync3(current);
|
|
8407
|
+
} catch {
|
|
8408
|
+
continue;
|
|
8409
|
+
}
|
|
8410
|
+
if (st.isSymbolicLink()) {
|
|
8411
|
+
throw new Error(`${rel} has a symlinked parent directory (${relative4(cwd, current)})`);
|
|
8412
|
+
}
|
|
8413
|
+
}
|
|
8414
|
+
}
|
|
8311
8415
|
function materialize(cwd, dest) {
|
|
8312
8416
|
const listed = git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
|
|
8417
|
+
const links = [];
|
|
8313
8418
|
for (const rel of listed) {
|
|
8314
8419
|
const src = join14(cwd, rel);
|
|
8420
|
+
rejectLinkedParent(cwd, rel);
|
|
8315
8421
|
let st;
|
|
8316
8422
|
try {
|
|
8317
|
-
st =
|
|
8423
|
+
st = lstatSync3(src);
|
|
8318
8424
|
} catch {
|
|
8319
8425
|
continue;
|
|
8320
8426
|
}
|
|
8321
|
-
if (!st.isFile()) continue;
|
|
8322
8427
|
const out2 = join14(dest, rel);
|
|
8323
8428
|
mkdirSync6(dirname5(out2), { recursive: true });
|
|
8324
8429
|
dropSymlink(out2);
|
|
8325
|
-
if (
|
|
8326
|
-
|
|
8327
|
-
|
|
8430
|
+
if (st.isSymbolicLink()) {
|
|
8431
|
+
const target = readlinkSync3(src);
|
|
8432
|
+
safeSymlink(target, out2, dest, rel);
|
|
8433
|
+
links.push({ rel, out: out2, target });
|
|
8434
|
+
} else if (st.isFile()) {
|
|
8328
8435
|
cpSync(src, out2, { dereference: true });
|
|
8436
|
+
chmodSync2(out2, st.mode);
|
|
8437
|
+
} else {
|
|
8438
|
+
throw new Error(`${rel} is ${st.isDirectory() ? "a directory" : "a special file"} where git expects a file`);
|
|
8329
8439
|
}
|
|
8330
|
-
chmodSync2(out2, st.mode);
|
|
8331
8440
|
}
|
|
8332
8441
|
const nm = resolve4(cwd, "node_modules");
|
|
8333
|
-
|
|
8442
|
+
let dependencyRoot = null;
|
|
8443
|
+
if (existsSync10(nm)) dependencyRoot = realpathSync3(nm);
|
|
8444
|
+
for (const link of links) {
|
|
8445
|
+
validateSymlinkGraph(link.target, link.out, dest, dependencyRoot, link.rel);
|
|
8446
|
+
}
|
|
8447
|
+
if (dependencyRoot) symlinkSync(nm, join14(dest, "node_modules"), "dir");
|
|
8334
8448
|
}
|
|
8335
8449
|
function dropSymlink(p) {
|
|
8336
8450
|
try {
|
|
@@ -8382,6 +8496,7 @@ function overlayPristine(cwd, base, dest, policy, cmd) {
|
|
|
8382
8496
|
const verifierGlobs = policy.verify?.inputs ?? [];
|
|
8383
8497
|
const isOverlay = (p) => OVERLAY_CLASSES.some((c) => isProtected(p, policy, c)) || matchesAny(p, VERIFICATION_SURFACE) || verifierOwned.has(p) || verifierGlobs.length > 0 && matchesAny(p, verifierGlobs);
|
|
8384
8498
|
const restored = [];
|
|
8499
|
+
const restoredLinks = [];
|
|
8385
8500
|
const baseProtected = /* @__PURE__ */ new Set();
|
|
8386
8501
|
for (const e of entries) {
|
|
8387
8502
|
if (!isOverlay(e.path)) continue;
|
|
@@ -8394,7 +8509,9 @@ function overlayPristine(cwd, base, dest, policy, cmd) {
|
|
|
8394
8509
|
mkdirSync6(dirname5(out2), { recursive: true });
|
|
8395
8510
|
rmSync3(out2, { force: true });
|
|
8396
8511
|
if (e.mode === "120000") {
|
|
8397
|
-
|
|
8512
|
+
const target = content.toString("utf8");
|
|
8513
|
+
safeSymlink(target, out2, dest, `the base's ${e.path}`);
|
|
8514
|
+
restoredLinks.push({ path: e.path, out: out2, target });
|
|
8398
8515
|
} else {
|
|
8399
8516
|
writeFileSync6(out2, content);
|
|
8400
8517
|
chmodSync2(out2, parseInt(e.mode.slice(-4), 8) & 511);
|
|
@@ -8408,6 +8525,11 @@ function overlayPristine(cwd, base, dest, policy, cmd) {
|
|
|
8408
8525
|
rmSync3(join14(dest, rel), { force: true });
|
|
8409
8526
|
removed++;
|
|
8410
8527
|
}
|
|
8528
|
+
const nm = resolve4(cwd, "node_modules");
|
|
8529
|
+
const dependencyRoot = existsSync10(nm) ? realpathSync3(nm) : null;
|
|
8530
|
+
for (const link of restoredLinks) {
|
|
8531
|
+
validateSymlinkGraph(link.target, link.out, dest, dependencyRoot, `the base's ${link.path}`);
|
|
8532
|
+
}
|
|
8411
8533
|
return { restored, removed };
|
|
8412
8534
|
}
|
|
8413
8535
|
var SUPERVISOR = `
|
|
@@ -8666,7 +8788,7 @@ function parseVerify(args) {
|
|
|
8666
8788
|
|
|
8667
8789
|
// src/cli/run.ts
|
|
8668
8790
|
import { execFileSync as execFileSync9, spawnSync as spawnSync2 } from "node:child_process";
|
|
8669
|
-
import { readdirSync as readdirSync4, readFileSync as readFileSync15, readlinkSync as readlinkSync4, realpathSync as
|
|
8791
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync15, readlinkSync as readlinkSync4, realpathSync as realpathSync4 } from "node:fs";
|
|
8670
8792
|
import { resolve as resolve5 } from "node:path";
|
|
8671
8793
|
var out = (s) => process.stdout.write(s + "\n");
|
|
8672
8794
|
var err = (s) => process.stderr.write(s + "\n");
|
|
@@ -8684,7 +8806,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
|
8684
8806
|
const out2 = [];
|
|
8685
8807
|
let real;
|
|
8686
8808
|
try {
|
|
8687
|
-
real =
|
|
8809
|
+
real = realpathSync4(cwd);
|
|
8688
8810
|
} catch {
|
|
8689
8811
|
return out2;
|
|
8690
8812
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.10.
|
|
3
|
+
"version": "2.10.7",
|
|
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",
|