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.
Files changed (2) hide show
  1. package/dist/cli/index.js +138 -16
  2. 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) return 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 "latest";
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, rmSync as rmSync3, statSync as statSync5, symlinkSync, writeFileSync as writeFileSync6, existsSync as existsSync10 } from "node:fs";
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 = statSync5(src);
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 (lstatSync3(src).isSymbolicLink()) {
8326
- writeFileSync6(out2, readFileSync14(src));
8327
- } else {
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
- if (existsSync10(nm)) symlinkSync(nm, join14(dest, "node_modules"), "dir");
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
- symlinkSync(content.toString("utf8"), out2);
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 realpathSync3 } from "node:fs";
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 = realpathSync3(cwd);
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.5",
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",