tamperward 2.23.3 → 2.23.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 CHANGED
@@ -11926,7 +11926,7 @@ var init_verifier_backend = __esm({
11926
11926
  // src/cli/verify.ts
11927
11927
  import { execFileSync as execFileSync8, spawnSync as spawnSync3 } from "node:child_process";
11928
11928
  import { createHash as createHash10 } from "node:crypto";
11929
- import { chmodSync as chmodSync3, cpSync, lstatSync as lstatSync9, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, readFileSync as readFileSync16, readlinkSync as readlinkSync3, rmSync as rmSync5, symlinkSync, writeFileSync as writeFileSync9 } from "node:fs";
11929
+ import { chmodSync as chmodSync3, cpSync, lstatSync as lstatSync9, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync3, readFileSync as readFileSync16, readlinkSync as readlinkSync3, rmSync as rmSync5, symlinkSync, writeFileSync as writeFileSync9, existsSync as existsSync12 } from "node:fs";
11930
11930
  import { tmpdir as tmpdir4 } from "node:os";
11931
11931
  import { dirname as dirname7, isAbsolute as isAbsolute10, join as join18, relative as relative5, resolve as resolve6, sep as sep3 } from "node:path";
11932
11932
  function oracleAssuranceReport() {
@@ -12045,7 +12045,7 @@ function rejectLinkedParent(cwd, rel) {
12045
12045
  }
12046
12046
  }
12047
12047
  function materialize(cwd, dest, dependencyRoot) {
12048
- const listed = git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
12048
+ const listed = workingTreePaths(cwd);
12049
12049
  const links = [];
12050
12050
  for (const rel of listed) {
12051
12051
  const src = join18(cwd, rel);
@@ -12075,6 +12075,32 @@ function materialize(cwd, dest, dependencyRoot) {
12075
12075
  }
12076
12076
  if (dependencyRoot) symlinkSync(dependencyRoot, join18(dest, "node_modules"), "dir");
12077
12077
  }
12078
+ function workingTreePaths(cwd) {
12079
+ return git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
12080
+ }
12081
+ function probeFilesystemCaseSensitivity(dir) {
12082
+ const upper = join18(dir, ".tw-case-probe-A");
12083
+ const lower = join18(dir, ".tw-case-probe-a");
12084
+ try {
12085
+ writeFileSync9(upper, "", { flag: "wx" });
12086
+ return !existsSync12(lower);
12087
+ } finally {
12088
+ rmSync5(upper, { force: true });
12089
+ }
12090
+ }
12091
+ function foldCase(p) {
12092
+ return p.toLowerCase();
12093
+ }
12094
+ function caseFoldCollision(paths) {
12095
+ const seen = /* @__PURE__ */ new Map();
12096
+ for (const p of paths) {
12097
+ const key2 = foldCase(p);
12098
+ const prior = seen.get(key2);
12099
+ if (prior !== void 0 && prior !== p) return [prior, p];
12100
+ if (prior === void 0) seen.set(key2, p);
12101
+ }
12102
+ return null;
12103
+ }
12078
12104
  function dropSymlink(p) {
12079
12105
  try {
12080
12106
  if (lstatSync9(p).isSymbolicLink()) rmSync5(p, { force: true });
@@ -12129,21 +12155,24 @@ function overlayDigest(dest, paths) {
12129
12155
  }
12130
12156
  return h.digest("hex");
12131
12157
  }
12132
- function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot) {
12158
+ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot, caseSensitive) {
12133
12159
  const entries = baseEntries(base, cwd);
12134
12160
  const atBase = entries.map((e) => e.path);
12161
+ const fold = caseSensitive ? (p) => p : foldCase;
12135
12162
  const verifierGlobs = policy.verify?.inputs ?? [];
12136
12163
  const coveredBaseInputs = verifierCoveredInputs(cmd, atBase, policy);
12137
- const isOverlay = (p) => coveredBaseInputs.has(p) || // Candidate-added files do not exist in atBase and therefore cannot appear
12164
+ const isOverlayExact = (p) => coveredBaseInputs.has(p) || // Candidate-added files do not exist in atBase and therefore cannot appear
12138
12165
  // in the precomputed set. They still must be removed when they land on a
12139
12166
  // protected class or the built-in verification surface.
12140
12167
  OVERLAY_CLASSES.some((category) => isProtected(p, policy, category)) || matchesAny(p, VERIFICATION_SURFACE) || verifierGlobs.length > 0 && matchesAny(p, verifierGlobs);
12168
+ const coveredFolded = new Set(caseSensitive ? [] : [...coveredBaseInputs].map(foldCase));
12169
+ const isOverlay = caseSensitive ? isOverlayExact : (p) => isOverlayExact(p) || isOverlayExact(foldCase(p)) || coveredFolded.has(foldCase(p));
12141
12170
  const restored = [];
12142
12171
  const restoredLinks = [];
12143
12172
  const baseProtected = /* @__PURE__ */ new Set();
12144
12173
  for (const e of entries) {
12145
12174
  if (!isOverlay(e.path)) continue;
12146
- baseProtected.add(e.path);
12175
+ baseProtected.add(fold(e.path));
12147
12176
  if (e.type === "commit") {
12148
12177
  throw new Error(`the base's ${e.path} is a submodule; the pristine copy cannot reproduce it`);
12149
12178
  }
@@ -12162,9 +12191,9 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot) {
12162
12191
  restored.push(e.path);
12163
12192
  }
12164
12193
  let removed = 0;
12165
- const inCopy = git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
12194
+ const inCopy = workingTreePaths(cwd);
12166
12195
  for (const rel of inCopy) {
12167
- if (!isOverlay(rel) || baseProtected.has(rel)) continue;
12196
+ if (!isOverlay(rel) || baseProtected.has(fold(rel))) continue;
12168
12197
  rmSync5(join18(dest, rel), { force: true });
12169
12198
  removed++;
12170
12199
  }
@@ -12285,6 +12314,8 @@ function runVerify(opts) {
12285
12314
  const cwd = opts.cwd ?? process.cwd();
12286
12315
  const out3 = opts.silent ? (_s) => {
12287
12316
  } : (s) => void process.stdout.write(s + "\n");
12317
+ let filesystemCaseSensitive;
12318
+ const caseReport = () => filesystemCaseSensitive === void 0 ? {} : { filesystem_case_sensitive: filesystemCaseSensitive };
12288
12319
  const cannotVerify = (reason, detail, extra = {}) => {
12289
12320
  opts.onVerdict?.({ verdict: "CANNOT_VERIFY", reason, ...detail ? { detail } : {} });
12290
12321
  if (opts.json) {
@@ -12293,6 +12324,7 @@ function runVerify(opts) {
12293
12324
  verdict: "CANNOT_VERIFY",
12294
12325
  reason,
12295
12326
  ...detail ? { detail } : {},
12327
+ ...caseReport(),
12296
12328
  ...extra
12297
12329
  }));
12298
12330
  } else if (detail) {
@@ -12404,6 +12436,26 @@ function runVerify(opts) {
12404
12436
  if (opts.keep) return;
12405
12437
  for (const d of dirs) rmSync5(d, { recursive: true, force: true });
12406
12438
  };
12439
+ filesystemCaseSensitive = (opts.probeCaseSensitivity ?? probeFilesystemCaseSensitivity)(visRoot);
12440
+ if (!filesystemCaseSensitive) {
12441
+ const present = (rel) => {
12442
+ try {
12443
+ lstatSync9(join18(cwd, rel));
12444
+ return true;
12445
+ } catch {
12446
+ return false;
12447
+ }
12448
+ };
12449
+ const collision = caseFoldCollision(workingTreePaths(cwd).filter(present)) ?? caseFoldCollision(baseEntries(base, cwd).map((e) => e.path));
12450
+ if (collision) {
12451
+ cleanup([visRoot]);
12452
+ return cannotVerify(
12453
+ "PATH_CASE_COLLISION",
12454
+ `${collision[0]} and ${collision[1]} are distinct paths that the case-insensitive filesystem the copies are materialised on cannot tell apart; the copy could not be the tree under verification`,
12455
+ { verifier_backend: backendReport(), dependency_environment: dependencyReport(), oracle_assurance: oracleAssuranceReport() }
12456
+ );
12457
+ }
12458
+ }
12407
12459
  const protectedIgnored = (rel) => isProtected(rel, policy);
12408
12460
  const treeBefore = treeFingerprint(cwd, protectedIgnored);
12409
12461
  const dependencyAtEntry = dependencyEnvironment ? reuseDependencyEnvironmentAttestation(
@@ -12441,6 +12493,7 @@ function runVerify(opts) {
12441
12493
  verdict: "CANNOT_VERIFY",
12442
12494
  reason: exhausted ? "VERIFIER_RESOURCE_EXHAUSTED" : "VERIFIER_BACKEND_RUNTIME_FAILURE",
12443
12495
  stage: "visible",
12496
+ ...caseReport(),
12444
12497
  ...visible.resource ? { resource: visible.resource } : {},
12445
12498
  detail: visible.reason,
12446
12499
  diagnostics: diagnosticsJson(visible.diagnostics, true),
@@ -12486,7 +12539,8 @@ function runVerify(opts) {
12486
12539
  priDir,
12487
12540
  policy,
12488
12541
  cmd,
12489
- frozenNodeModules
12542
+ frozenNodeModules,
12543
+ filesystemCaseSensitive
12490
12544
  ));
12491
12545
  } catch (e) {
12492
12546
  cleanup([visRoot, priRoot]);
@@ -12508,6 +12562,7 @@ function runVerify(opts) {
12508
12562
  verdict: "CANNOT_VERIFY",
12509
12563
  reason: exhausted ? "VERIFIER_RESOURCE_EXHAUSTED" : "VERIFIER_BACKEND_RUNTIME_FAILURE",
12510
12564
  stage: "pristine",
12565
+ ...caseReport(),
12511
12566
  ...pristine.resource ? { resource: pristine.resource } : {},
12512
12567
  detail: pristine.reason,
12513
12568
  diagnostics: diagnosticsJson(pristine.diagnostics, true),
@@ -12572,6 +12627,7 @@ function runVerify(opts) {
12572
12627
  pristine: stageJson(pristine),
12573
12628
  protected_restored: restored.length,
12574
12629
  added_protected_removed: removedAdded,
12630
+ filesystem_case_sensitive: filesystemCaseSensitive,
12575
12631
  verifier_backend: backendReport(),
12576
12632
  dependency_environment: dependencyReport(),
12577
12633
  oracle_assurance: oracleAssuranceReport(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.23.3",
3
+ "version": "2.23.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",
@@ -35,7 +35,8 @@
35
35
  "VERIFIER_BACKEND_RUNTIME_FAILURE",
36
36
  "VERIFIER_RESOURCE_EXHAUSTED",
37
37
  "WORKTREE_CHANGED",
38
- "PRISTINE_INTEGRITY_CHANGED"
38
+ "PRISTINE_INTEGRITY_CHANGED",
39
+ "PATH_CASE_COLLISION"
39
40
  ],
40
41
  "description": "Machine reason for CANNOT_VERIFY. `stage` names the suite execution in flight, where one was."
41
42
  },
@@ -78,6 +79,10 @@
78
79
  "type": "integer",
79
80
  "minimum": 0
80
81
  },
82
+ "filesystem_case_sensitive": {
83
+ "type": "boolean",
84
+ "description": "Whether the filesystem the copies are materialised on distinguishes path case (probed once per verify). When false, overlay membership and the base-protected set are case-folded and a fold collision between two in-tree paths is CANNOT_VERIFY / PATH_CASE_COLLISION. Present once the probe has run."
85
+ },
81
86
  "verifier_backend": {
82
87
  "$ref": "#/$defs/backend"
83
88
  },