tamperward 2.10.5 → 2.10.6
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 +122 -12
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -8149,9 +8149,9 @@ tamperward init: ${changed} change(s) applied. Commit them so the gate travels w
|
|
|
8149
8149
|
// src/cli/verify.ts
|
|
8150
8150
|
import { execFileSync as execFileSync8, spawnSync } from "node:child_process";
|
|
8151
8151
|
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,
|
|
8152
|
+
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
8153
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
8154
|
-
import { dirname as dirname5, join as join14, resolve as resolve4 } from "node:path";
|
|
8154
|
+
import { dirname as dirname5, isAbsolute as isAbsolute9, join as join14, relative as relative4, resolve as resolve4, sep } from "node:path";
|
|
8155
8155
|
|
|
8156
8156
|
// src/fingerprint.ts
|
|
8157
8157
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
@@ -8308,29 +8308,131 @@ function baseIsAncestorOfHead(base, cwd) {
|
|
|
8308
8308
|
const r = spawnSync("git", ["merge-base", "--is-ancestor", base, "HEAD"], { cwd, env: trustedGitEnv() });
|
|
8309
8309
|
return r.status === 0;
|
|
8310
8310
|
}
|
|
8311
|
+
function inside(root, path) {
|
|
8312
|
+
const rel = relative4(resolve4(root), resolve4(path));
|
|
8313
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute9(rel);
|
|
8314
|
+
}
|
|
8315
|
+
function linkParts(path) {
|
|
8316
|
+
const parts = sep === "\\" ? path.split(/[\\/]+/) : path.split("/");
|
|
8317
|
+
return parts.filter((part) => part !== "" && part !== ".");
|
|
8318
|
+
}
|
|
8319
|
+
function linkEscape(label, target) {
|
|
8320
|
+
return new Error(`${label} is a symlink that escapes the materialised tree (${JSON.stringify(target)})`);
|
|
8321
|
+
}
|
|
8322
|
+
function driveRelativeLinkTarget(target) {
|
|
8323
|
+
return sep === "\\" && /^[A-Za-z]:(?![\\/])/.test(target);
|
|
8324
|
+
}
|
|
8325
|
+
function rootedLinkTarget(target) {
|
|
8326
|
+
return isAbsolute9(target) || driveRelativeLinkTarget(target);
|
|
8327
|
+
}
|
|
8328
|
+
function safeSymlink(target, out2, root, label) {
|
|
8329
|
+
if (rootedLinkTarget(target) || !inside(root, resolve4(dirname5(out2), target))) {
|
|
8330
|
+
throw linkEscape(label, target);
|
|
8331
|
+
}
|
|
8332
|
+
rmSync3(out2, { force: true });
|
|
8333
|
+
symlinkSync(target, out2);
|
|
8334
|
+
}
|
|
8335
|
+
function validateSymlinkGraph(target, out2, root, dependencyRoot, label) {
|
|
8336
|
+
let domain = "tree";
|
|
8337
|
+
let stack = linkParts(relative4(root, dirname5(out2)));
|
|
8338
|
+
const pending = linkParts(target);
|
|
8339
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8340
|
+
let hops = 0;
|
|
8341
|
+
while (pending.length) {
|
|
8342
|
+
const part = pending.shift();
|
|
8343
|
+
if (part === "..") {
|
|
8344
|
+
if (!stack.length) throw linkEscape(label, target);
|
|
8345
|
+
stack.pop();
|
|
8346
|
+
continue;
|
|
8347
|
+
}
|
|
8348
|
+
stack.push(part);
|
|
8349
|
+
if (domain === "tree" && dependencyRoot && stack.length === 1 && stack[0] === "node_modules") {
|
|
8350
|
+
domain = "deps";
|
|
8351
|
+
stack = [];
|
|
8352
|
+
continue;
|
|
8353
|
+
}
|
|
8354
|
+
const base = domain === "tree" ? root : dependencyRoot;
|
|
8355
|
+
const current = join14(base, ...stack);
|
|
8356
|
+
let st;
|
|
8357
|
+
try {
|
|
8358
|
+
st = lstatSync3(current);
|
|
8359
|
+
} catch {
|
|
8360
|
+
continue;
|
|
8361
|
+
}
|
|
8362
|
+
if (!st.isSymbolicLink()) continue;
|
|
8363
|
+
if (++hops > 128) {
|
|
8364
|
+
throw new Error(`${label} contains a symlink cycle while materialising (${JSON.stringify(target)})`);
|
|
8365
|
+
}
|
|
8366
|
+
const key2 = `${domain}:${current}:${pending.join(sep)}`;
|
|
8367
|
+
if (seen.has(key2)) {
|
|
8368
|
+
throw new Error(`${label} contains a symlink cycle while materialising (${JSON.stringify(target)})`);
|
|
8369
|
+
}
|
|
8370
|
+
seen.add(key2);
|
|
8371
|
+
const next = readlinkSync3(current);
|
|
8372
|
+
stack.pop();
|
|
8373
|
+
if (driveRelativeLinkTarget(next)) {
|
|
8374
|
+
throw linkEscape(label, target);
|
|
8375
|
+
}
|
|
8376
|
+
if (isAbsolute9(next)) {
|
|
8377
|
+
if (domain === "tree" || !dependencyRoot || !inside(dependencyRoot, next)) {
|
|
8378
|
+
throw linkEscape(label, target);
|
|
8379
|
+
}
|
|
8380
|
+
stack = [];
|
|
8381
|
+
pending.unshift(...linkParts(relative4(dependencyRoot, resolve4(next))));
|
|
8382
|
+
} else {
|
|
8383
|
+
pending.unshift(...linkParts(next));
|
|
8384
|
+
}
|
|
8385
|
+
}
|
|
8386
|
+
}
|
|
8387
|
+
function rejectLinkedParent(cwd, rel) {
|
|
8388
|
+
const parts = rel.split("/").slice(0, -1);
|
|
8389
|
+
let current = cwd;
|
|
8390
|
+
for (const part of parts) {
|
|
8391
|
+
current = join14(current, part);
|
|
8392
|
+
let st;
|
|
8393
|
+
try {
|
|
8394
|
+
st = lstatSync3(current);
|
|
8395
|
+
} catch {
|
|
8396
|
+
continue;
|
|
8397
|
+
}
|
|
8398
|
+
if (st.isSymbolicLink()) {
|
|
8399
|
+
throw new Error(`${rel} has a symlinked parent directory (${relative4(cwd, current)})`);
|
|
8400
|
+
}
|
|
8401
|
+
}
|
|
8402
|
+
}
|
|
8311
8403
|
function materialize(cwd, dest) {
|
|
8312
8404
|
const listed = git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
|
|
8405
|
+
const links = [];
|
|
8313
8406
|
for (const rel of listed) {
|
|
8314
8407
|
const src = join14(cwd, rel);
|
|
8408
|
+
rejectLinkedParent(cwd, rel);
|
|
8315
8409
|
let st;
|
|
8316
8410
|
try {
|
|
8317
|
-
st =
|
|
8411
|
+
st = lstatSync3(src);
|
|
8318
8412
|
} catch {
|
|
8319
8413
|
continue;
|
|
8320
8414
|
}
|
|
8321
|
-
if (!st.isFile()) continue;
|
|
8322
8415
|
const out2 = join14(dest, rel);
|
|
8323
8416
|
mkdirSync6(dirname5(out2), { recursive: true });
|
|
8324
8417
|
dropSymlink(out2);
|
|
8325
|
-
if (
|
|
8326
|
-
|
|
8327
|
-
|
|
8418
|
+
if (st.isSymbolicLink()) {
|
|
8419
|
+
const target = readlinkSync3(src);
|
|
8420
|
+
safeSymlink(target, out2, dest, rel);
|
|
8421
|
+
links.push({ rel, out: out2, target });
|
|
8422
|
+
} else if (st.isFile()) {
|
|
8328
8423
|
cpSync(src, out2, { dereference: true });
|
|
8424
|
+
chmodSync2(out2, st.mode);
|
|
8425
|
+
} else {
|
|
8426
|
+
throw new Error(`${rel} is ${st.isDirectory() ? "a directory" : "a special file"} where git expects a file`);
|
|
8329
8427
|
}
|
|
8330
|
-
chmodSync2(out2, st.mode);
|
|
8331
8428
|
}
|
|
8332
8429
|
const nm = resolve4(cwd, "node_modules");
|
|
8333
|
-
|
|
8430
|
+
let dependencyRoot = null;
|
|
8431
|
+
if (existsSync10(nm)) dependencyRoot = realpathSync3(nm);
|
|
8432
|
+
for (const link of links) {
|
|
8433
|
+
validateSymlinkGraph(link.target, link.out, dest, dependencyRoot, link.rel);
|
|
8434
|
+
}
|
|
8435
|
+
if (dependencyRoot) symlinkSync(nm, join14(dest, "node_modules"), "dir");
|
|
8334
8436
|
}
|
|
8335
8437
|
function dropSymlink(p) {
|
|
8336
8438
|
try {
|
|
@@ -8382,6 +8484,7 @@ function overlayPristine(cwd, base, dest, policy, cmd) {
|
|
|
8382
8484
|
const verifierGlobs = policy.verify?.inputs ?? [];
|
|
8383
8485
|
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
8486
|
const restored = [];
|
|
8487
|
+
const restoredLinks = [];
|
|
8385
8488
|
const baseProtected = /* @__PURE__ */ new Set();
|
|
8386
8489
|
for (const e of entries) {
|
|
8387
8490
|
if (!isOverlay(e.path)) continue;
|
|
@@ -8394,7 +8497,9 @@ function overlayPristine(cwd, base, dest, policy, cmd) {
|
|
|
8394
8497
|
mkdirSync6(dirname5(out2), { recursive: true });
|
|
8395
8498
|
rmSync3(out2, { force: true });
|
|
8396
8499
|
if (e.mode === "120000") {
|
|
8397
|
-
|
|
8500
|
+
const target = content.toString("utf8");
|
|
8501
|
+
safeSymlink(target, out2, dest, `the base's ${e.path}`);
|
|
8502
|
+
restoredLinks.push({ path: e.path, out: out2, target });
|
|
8398
8503
|
} else {
|
|
8399
8504
|
writeFileSync6(out2, content);
|
|
8400
8505
|
chmodSync2(out2, parseInt(e.mode.slice(-4), 8) & 511);
|
|
@@ -8408,6 +8513,11 @@ function overlayPristine(cwd, base, dest, policy, cmd) {
|
|
|
8408
8513
|
rmSync3(join14(dest, rel), { force: true });
|
|
8409
8514
|
removed++;
|
|
8410
8515
|
}
|
|
8516
|
+
const nm = resolve4(cwd, "node_modules");
|
|
8517
|
+
const dependencyRoot = existsSync10(nm) ? realpathSync3(nm) : null;
|
|
8518
|
+
for (const link of restoredLinks) {
|
|
8519
|
+
validateSymlinkGraph(link.target, link.out, dest, dependencyRoot, `the base's ${link.path}`);
|
|
8520
|
+
}
|
|
8411
8521
|
return { restored, removed };
|
|
8412
8522
|
}
|
|
8413
8523
|
var SUPERVISOR = `
|
|
@@ -8666,7 +8776,7 @@ function parseVerify(args) {
|
|
|
8666
8776
|
|
|
8667
8777
|
// src/cli/run.ts
|
|
8668
8778
|
import { execFileSync as execFileSync9, spawnSync as spawnSync2 } from "node:child_process";
|
|
8669
|
-
import { readdirSync as readdirSync4, readFileSync as readFileSync15, readlinkSync as readlinkSync4, realpathSync as
|
|
8779
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync15, readlinkSync as readlinkSync4, realpathSync as realpathSync4 } from "node:fs";
|
|
8670
8780
|
import { resolve as resolve5 } from "node:path";
|
|
8671
8781
|
var out = (s) => process.stdout.write(s + "\n");
|
|
8672
8782
|
var err = (s) => process.stderr.write(s + "\n");
|
|
@@ -8684,7 +8794,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
|
8684
8794
|
const out2 = [];
|
|
8685
8795
|
let real;
|
|
8686
8796
|
try {
|
|
8687
|
-
real =
|
|
8797
|
+
real = realpathSync4(cwd);
|
|
8688
8798
|
} catch {
|
|
8689
8799
|
return out2;
|
|
8690
8800
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.10.
|
|
3
|
+
"version": "2.10.6",
|
|
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",
|