tamperward 2.26.0 → 2.27.0

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/README.md CHANGED
@@ -98,7 +98,7 @@ Earlier controlled experiments → **[the research series](./docs/blog/index.md)
98
98
  > Read the round as a pair — the bets before, the outcome after:
99
99
  > [How round 4 is built to be hard to fool](./docs/blog/how-round-4-is-built-to-be-hard-to-fool.md)
100
100
  > (preregistration, no numbers) →
101
- > [The prevention bet didn't replicate. No surviving tampering was certified clean.](./docs/blog/round-4-results.md)
101
+ > [The prevention bet didn't replicate. No surviving tampering was certified clean.](./docs/blog/the-prevention-bet-didnt-replicate-no-surviving-tampering-was-certified-clean.md)
102
102
  > (results). Round 4.1, a second agent runtime over the same frozen pool, is the
103
103
  > next registered step ([SPEC §9.1](./SPEC.md#91-maturity-milestones)).
104
104
 
package/dist/cli/index.js CHANGED
@@ -1090,7 +1090,7 @@ var init_trusted = __esm({
1090
1090
 
1091
1091
  // src/repo-context.ts
1092
1092
  import { execFileSync as execFileSync2 } from "node:child_process";
1093
- import { accessSync, constants as constants2, existsSync as existsSync2, statSync } from "node:fs";
1093
+ import { accessSync, constants as constants2, existsSync as existsSync2, realpathSync as realpathSync3, statSync } from "node:fs";
1094
1094
  import { isAbsolute as isAbsolute3, resolve } from "node:path";
1095
1095
  function rev(cwd, flag) {
1096
1096
  try {
@@ -1123,6 +1123,27 @@ function repoContext(cwd) {
1123
1123
  function repoRoot(cwd) {
1124
1124
  return repoContext(cwd)?.root ?? cwd;
1125
1125
  }
1126
+ function validateClaimAgainstRoot(claimedCwd, trustedRoot, base) {
1127
+ if (claimedCwd === void 0) return { ok: true, trustedRoot };
1128
+ if (typeof claimedCwd !== "string" || claimedCwd.trim() === "") return { ok: false, rejected: "malformed cwd claim" };
1129
+ const abs = isAbsolute3(claimedCwd) ? claimedCwd : resolve(base, claimedCwd);
1130
+ let realClaim;
1131
+ try {
1132
+ realClaim = realpathSync3(abs);
1133
+ } catch (e) {
1134
+ return { ok: false, rejected: `cwd claim path cannot be resolved (${abs}: ${e instanceof Error ? e.message : String(e)})` };
1135
+ }
1136
+ let realTrusted;
1137
+ try {
1138
+ realTrusted = realpathSync3(trustedRoot);
1139
+ } catch {
1140
+ realTrusted = trustedRoot;
1141
+ }
1142
+ const ctx = repoContext(realClaim);
1143
+ if (!ctx) return { ok: false, rejected: `cwd claim resolves to no repository (${realClaim})` };
1144
+ if (ctx.root !== realTrusted) return { ok: false, rejected: `cwd claim resolves to a different repository (${ctx.root} != trusted ${realTrusted})` };
1145
+ return { ok: true, trustedRoot: realTrusted };
1146
+ }
1126
1147
  function outsideRepository(cwd) {
1127
1148
  const key2 = resolve(cwd);
1128
1149
  let isDir;
@@ -11863,10 +11884,7 @@ function failClosed(kind, detail) {
11863
11884
  };
11864
11885
  return verdict([f], kind);
11865
11886
  }
11866
- function verdict(blocks2, kind) {
11867
- if (blocks2.length === 0) return { exitCode: 0, stdout: "" };
11868
- recordDenylog(blocks2);
11869
- const reason = formatDenial(blocks2);
11887
+ function denyWire(reason, kind) {
11870
11888
  const payload = kind === "PreToolUse" ? {
11871
11889
  hookSpecificOutput: {
11872
11890
  hookEventName: "PreToolUse",
@@ -11874,7 +11892,13 @@ function verdict(blocks2, kind) {
11874
11892
  permissionDecisionReason: reason
11875
11893
  }
11876
11894
  } : { decision: "block", reason };
11877
- return { exitCode: 0, stdout: JSON.stringify(payload) + "\n" };
11895
+ return JSON.stringify(payload) + "\n";
11896
+ }
11897
+ function verdict(blocks2, kind) {
11898
+ if (blocks2.length === 0) return { exitCode: 0, stdout: "" };
11899
+ recordDenylog(blocks2);
11900
+ const reason = formatDenial(blocks2);
11901
+ return { exitCode: 0, stdout: denyWire(reason, kind) };
11878
11902
  }
11879
11903
  function fileChanges(changes) {
11880
11904
  return changes.filter((c) => c.kind === "file");
@@ -12052,9 +12076,13 @@ function sanctionPredictedWrites(cwd, sessionId, policy, changes) {
12052
12076
  }
12053
12077
  if (dirty) savePtree(cwd, sessionId, tree);
12054
12078
  }
12055
- function preToolUseVerdict(input, defaultCwd) {
12079
+ function preToolUseVerdict(input, defaultCwd, trustedRoot) {
12056
12080
  try {
12057
12081
  const sessionCwd = input.cwd ?? defaultCwd ?? process.cwd();
12082
+ if (trustedRoot !== void 0) {
12083
+ const v = validateClaimAgainstRoot(input.cwd, trustedRoot, defaultCwd ?? process.cwd());
12084
+ if (!v.ok) return failClosed("PreToolUse", `repository identity claim rejected: ${v.rejected}`);
12085
+ }
12058
12086
  const cwd = repoRoot(sessionCwd);
12059
12087
  turnBaseline(cwd, input.session_id);
12060
12088
  const policy = loadPolicy(cwd);
@@ -12161,9 +12189,13 @@ function recordObserverHealth(state, reason) {
12161
12189
  } catch {
12162
12190
  }
12163
12191
  }
12164
- function stopVerdict(input, defaultCwd) {
12192
+ function stopVerdict(input, defaultCwd, trustedRoot) {
12165
12193
  if (input.stop_hook_active) return { exitCode: 0, stdout: "" };
12166
12194
  const sessionCwd = input.cwd ?? defaultCwd ?? process.cwd();
12195
+ if (trustedRoot !== void 0) {
12196
+ const v = validateClaimAgainstRoot(input.cwd, trustedRoot, defaultCwd ?? process.cwd());
12197
+ if (!v.ok) return failClosed("Stop", `repository identity claim rejected: ${v.rejected}`);
12198
+ }
12167
12199
  const ctx = repoContext(sessionCwd);
12168
12200
  if (!ctx) {
12169
12201
  const why = outsideRepository(sessionCwd);
@@ -12206,16 +12238,16 @@ function emit(r) {
12206
12238
  if (r.stdout) process.stdout.write(r.stdout);
12207
12239
  return r.exitCode;
12208
12240
  }
12209
- function preToolUseFromRaw(raw, defaultCwd) {
12241
+ function preToolUseFromRaw(raw, defaultCwd, trustedRoot) {
12210
12242
  try {
12211
- return preToolUseVerdict(parseInput(raw), defaultCwd);
12243
+ return preToolUseVerdict(parseInput(raw), defaultCwd, trustedRoot);
12212
12244
  } catch (e) {
12213
12245
  return failClosed("PreToolUse", errText2(e));
12214
12246
  }
12215
12247
  }
12216
- function stopFromRaw(raw, defaultCwd) {
12248
+ function stopFromRaw(raw, defaultCwd, trustedRoot) {
12217
12249
  try {
12218
- return stopVerdict(parseInput(raw), defaultCwd);
12250
+ return stopVerdict(parseInput(raw), defaultCwd, trustedRoot);
12219
12251
  } catch (e) {
12220
12252
  return failClosed("Stop", errText2(e));
12221
12253
  }
@@ -12361,7 +12393,7 @@ var init_ptree_cache = __esm({
12361
12393
  // src/cli/hook-service.ts
12362
12394
  import { chmodSync, existsSync as existsSync10, lstatSync as lstatSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync11, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "node:fs";
12363
12395
  import { createConnection as createConnection2, createServer } from "node:net";
12364
- import { realpathSync as realpathSync3 } from "node:fs";
12396
+ import { realpathSync as realpathSync4 } from "node:fs";
12365
12397
  import { sep } from "node:path";
12366
12398
  function readServiceState(paths) {
12367
12399
  try {
@@ -12399,7 +12431,7 @@ function listening(socket) {
12399
12431
  function within(root, cwd) {
12400
12432
  let real;
12401
12433
  try {
12402
- real = realpathSync3(cwd);
12434
+ real = realpathSync4(cwd);
12403
12435
  } catch {
12404
12436
  return false;
12405
12437
  }
@@ -12464,7 +12496,7 @@ async function startHookService(opts) {
12464
12496
  if (uid === void 0) throw new Error("no uid on this platform; refusing to start");
12465
12497
  let root;
12466
12498
  try {
12467
- root = realpathSync3(repoRoot(realpathSync3(opts.root)));
12499
+ root = realpathSync4(repoRoot(realpathSync4(opts.root)));
12468
12500
  } catch {
12469
12501
  throw new Error(`cannot resolve ${opts.root}`);
12470
12502
  }
@@ -12831,7 +12863,7 @@ var init_safe_write = __esm({
12831
12863
  // src/cli/init.ts
12832
12864
  import { execFileSync as execFileSync6 } from "node:child_process";
12833
12865
  import { createHash as createHash6 } from "node:crypto";
12834
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync12, realpathSync as realpathSync4 } from "node:fs";
12866
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync12, realpathSync as realpathSync5 } from "node:fs";
12835
12867
  import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute8, join as join14, relative as relative3, resolve as resolve4 } from "node:path";
12836
12868
  function gitConfig(cwd, key2) {
12837
12869
  try {
@@ -13409,7 +13441,7 @@ function planned(item, path, plan) {
13409
13441
  }
13410
13442
  function sameDir(a, b) {
13411
13443
  try {
13412
- return realpathSync4(a) === realpathSync4(b);
13444
+ return realpathSync5(a) === realpathSync5(b);
13413
13445
  } catch {
13414
13446
  return false;
13415
13447
  }
@@ -13665,7 +13697,7 @@ import {
13665
13697
  readdirSync as readdirSync3,
13666
13698
  readFileSync as readFileSync13,
13667
13699
  readlinkSync as readlinkSync2,
13668
- realpathSync as realpathSync5,
13700
+ realpathSync as realpathSync6,
13669
13701
  statSync as statSync3
13670
13702
  } from "node:fs";
13671
13703
  import {
@@ -13697,7 +13729,7 @@ function fingerprintRoot(root, cwd, h) {
13697
13729
  let currentReal;
13698
13730
  try {
13699
13731
  rootStat = lstatSync8(root.path);
13700
- currentReal = realpathSync5(root.path);
13732
+ currentReal = realpathSync6(root.path);
13701
13733
  } catch (e) {
13702
13734
  throw new Unattestable(
13703
13735
  root.kind + " root " + JSON.stringify(root.path) + " can no longer be resolved (" + (e instanceof Error ? e.message : String(e)) + ")"
@@ -13713,7 +13745,7 @@ function fingerprintRoot(root, cwd, h) {
13713
13745
  }
13714
13746
  h.update("root\0" + root.kind + "\0" + root.path + "\0" + root.realPath + "\0" + String(rootStat.mode) + "\0");
13715
13747
  if (rootStat.isSymbolicLink()) h.update(readlinkSync2(root.path) + "\0");
13716
- const cwdReal = realpathSync5(cwd);
13748
+ const cwdReal = realpathSync6(cwd);
13717
13749
  const visited = /* @__PURE__ */ new Set();
13718
13750
  const hashFile = (path, label) => {
13719
13751
  let st;
@@ -13730,7 +13762,7 @@ function fingerprintRoot(root, cwd, h) {
13730
13762
  }
13731
13763
  };
13732
13764
  const walk2 = (dir, namespace) => {
13733
- const realDir = realpathSync5(dir);
13765
+ const realDir = realpathSync6(dir);
13734
13766
  if (visited.has(realDir)) return;
13735
13767
  visited.add(realDir);
13736
13768
  let entries;
@@ -13780,7 +13812,7 @@ function fingerprintRoot(root, cwd, h) {
13780
13812
  h.update(target + "\0");
13781
13813
  let resolved;
13782
13814
  try {
13783
- resolved = realpathSync5(path);
13815
+ resolved = realpathSync6(path);
13784
13816
  } catch {
13785
13817
  continue;
13786
13818
  }
@@ -13835,7 +13867,7 @@ function freezeRoot(kind, path) {
13835
13867
  if (!statSync3(path).isDirectory()) {
13836
13868
  throw new Error("not a directory");
13837
13869
  }
13838
- return { kind, path: resolve5(path), realPath: realpathSync5(path) };
13870
+ return { kind, path: resolve5(path), realPath: realpathSync6(path) };
13839
13871
  } catch (e) {
13840
13872
  throw new Unattestable(
13841
13873
  kind + " root " + JSON.stringify(path) + " is not resolvable (" + (e instanceof Error ? e.message : String(e)) + ")"
@@ -13873,7 +13905,7 @@ function resolveExecutable(token, cwd, env) {
13873
13905
  if (token.includes("/") || token.includes("\\")) {
13874
13906
  const path = isAbsolute9(token) ? token : resolve5(cwd, token);
13875
13907
  try {
13876
- return existsSync12(path) ? realpathSync5(path) : null;
13908
+ return existsSync12(path) ? realpathSync6(path) : null;
13877
13909
  } catch {
13878
13910
  return null;
13879
13911
  }
@@ -13881,7 +13913,7 @@ function resolveExecutable(token, cwd, env) {
13881
13913
  for (const entry of (env.PATH ?? "").split(delimiter).filter(Boolean)) {
13882
13914
  const candidate = resolve5(entry, token);
13883
13915
  try {
13884
- if (existsSync12(candidate) && statSync3(candidate).isFile()) return realpathSync5(candidate);
13916
+ if (existsSync12(candidate) && statSync3(candidate).isFile()) return realpathSync6(candidate);
13885
13917
  } catch {
13886
13918
  }
13887
13919
  }
@@ -14387,7 +14419,7 @@ if (child) {
14387
14419
  // src/verifier-backend.ts
14388
14420
  import { createHash as createHash9, randomUUID } from "node:crypto";
14389
14421
  import { spawnSync as spawnSync2 } from "node:child_process";
14390
- import { accessSync as accessSync2, constants as constants3, readFileSync as readFileSync15, realpathSync as realpathSync6, statSync as statSync4 } from "node:fs";
14422
+ import { accessSync as accessSync2, constants as constants3, readFileSync as readFileSync15, realpathSync as realpathSync7, statSync as statSync4 } from "node:fs";
14391
14423
  import { delimiter as delimiter2, dirname as dirname6, join as join17, resolve as resolve6 } from "node:path";
14392
14424
  function identity() {
14393
14425
  const uid = typeof process.getuid === "function" ? process.getuid() : null;
@@ -14425,7 +14457,7 @@ function findExecutable(name) {
14425
14457
  const candidate = join17(raw, name);
14426
14458
  try {
14427
14459
  accessSync2(candidate, constants3.X_OK);
14428
- return realpathSync6(candidate);
14460
+ return realpathSync7(candidate);
14429
14461
  } catch {
14430
14462
  }
14431
14463
  }
@@ -14500,7 +14532,7 @@ function inspectImageMetadata(enginePath, host, image) {
14500
14532
  function engineStable(backend) {
14501
14533
  if (!backend.enginePath || !backend.engineSha256) return false;
14502
14534
  try {
14503
- if (realpathSync6(backend.enginePath) !== backend.enginePath) return false;
14535
+ if (realpathSync7(backend.enginePath) !== backend.enginePath) return false;
14504
14536
  if (replaceableByCaller(backend.enginePath)) return false;
14505
14537
  return fileSha256(backend.enginePath) === backend.engineSha256;
14506
14538
  } catch {
@@ -15741,7 +15773,7 @@ var init_verify = __esm({
15741
15773
  // src/cli/run.ts
15742
15774
  import { execFileSync as execFileSync9, spawn, spawnSync as spawnSync4 } from "node:child_process";
15743
15775
  import { randomBytes as randomBytes2 } from "node:crypto";
15744
- import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync17, readlinkSync as readlinkSync4, realpathSync as realpathSync8, rmSync as rmSync8, statSync as statSync5, writeFileSync as writeFileSync10 } from "node:fs";
15776
+ import { accessSync as accessSync3, constants as fsConstants, mkdtempSync as mkdtempSync4, readdirSync as readdirSync4, readFileSync as readFileSync17, readlinkSync as readlinkSync4, realpathSync as realpathSync9, rmSync as rmSync8, statSync as statSync5, writeFileSync as writeFileSync10 } from "node:fs";
15745
15777
  import { tmpdir as tmpdir5 } from "node:os";
15746
15778
  import { dirname as dirname8, join as join19, resolve as resolve8 } from "node:path";
15747
15779
  function authoritativeRunLifecyclePlatform(platform = process.platform) {
@@ -15796,7 +15828,7 @@ function trustedLinuxPython(candidates = DEFAULT_TRUSTED_PYTHON_CANDIDATES) {
15796
15828
  }
15797
15829
  for (const candidate of candidates) {
15798
15830
  try {
15799
- const real = realpathSync8(candidate);
15831
+ const real = realpathSync9(candidate);
15800
15832
  const st = statSync5(real);
15801
15833
  if (!st.isFile() || writableByCaller2(real)) continue;
15802
15834
  const probe = spawnSync4(
@@ -16070,7 +16102,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
16070
16102
  const out3 = [];
16071
16103
  let real;
16072
16104
  try {
16073
- real = realpathSync8(cwd);
16105
+ real = realpathSync9(cwd);
16074
16106
  } catch {
16075
16107
  return out3;
16076
16108
  }
@@ -17292,7 +17324,7 @@ import {
17292
17324
  mkdtempSync as mkdtempSync5,
17293
17325
  readFileSync as readFileSync19,
17294
17326
  readdirSync as readdirSync6,
17295
- realpathSync as realpathSync9,
17327
+ realpathSync as realpathSync10,
17296
17328
  rmSync as rmSync9,
17297
17329
  symlinkSync as symlinkSync2
17298
17330
  } from "node:fs";
@@ -17440,7 +17472,7 @@ function rewriteTraceRoot(accesses, actualRoot) {
17440
17472
  if (!inside3(actualRoot, abs)) return { ...item, path: abs };
17441
17473
  const rel = relative6(actualRoot, abs).split(sep4).join("/");
17442
17474
  try {
17443
- const real = resolve10(realpathSync9(abs));
17475
+ const real = resolve10(realpathSync10(abs));
17444
17476
  if (!inside3(actualRoot, real)) return { ...item, path: real };
17445
17477
  } catch {
17446
17478
  }
@@ -17676,7 +17708,7 @@ var init_trace_verify = __esm({
17676
17708
 
17677
17709
  // src/cli/onboard.ts
17678
17710
  import { execFileSync as execFileSync12 } from "node:child_process";
17679
- import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync20, realpathSync as realpathSync10, rmSync as rmSync10 } from "node:fs";
17711
+ import { lstatSync as lstatSync10, mkdtempSync as mkdtempSync6, readFileSync as readFileSync20, realpathSync as realpathSync11, rmSync as rmSync10 } from "node:fs";
17680
17712
  import { tmpdir as tmpdir7 } from "node:os";
17681
17713
  import { join as join22, relative as relative7, resolve as resolve11, sep as sep5 } from "node:path";
17682
17714
  import { createInterface } from "node:readline";
@@ -17818,7 +17850,7 @@ async function runOnboard(opts, io = {}) {
17818
17850
  const cwd = resolve11(rootText);
17819
17851
  let sameRoot = cwd === requestedCwd;
17820
17852
  try {
17821
- sameRoot = realpathSync10(cwd) === realpathSync10(requestedCwd);
17853
+ sameRoot = realpathSync11(cwd) === realpathSync11(requestedCwd);
17822
17854
  } catch {
17823
17855
  }
17824
17856
  if (!sameRoot) {
@@ -18156,8 +18188,8 @@ function runDemo(cwd, head, runners, out3) {
18156
18188
  let worktreeRoot;
18157
18189
  let realTarget;
18158
18190
  try {
18159
- worktreeRoot = realpathSync10(wt);
18160
- realTarget = realpathSync10(targetPath);
18191
+ worktreeRoot = realpathSync11(wt);
18192
+ realTarget = realpathSync11(targetPath);
18161
18193
  } catch {
18162
18194
  out3("Demo skipped: the selected test could not be resolved safely inside the disposable worktree.");
18163
18195
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.26.0",
3
+ "version": "2.27.0",
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",