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 +64 -8
- package/package.json +1 -1
- package/schemas/verify-v1.schema.json +6 -1
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 =
|
|
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
|
|
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 =
|
|
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
|
+
"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
|
},
|