tamperward 1.10.3 → 1.11.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/dist/cli/index.js +86 -13
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2139,18 +2139,13 @@ function snapshotProtected(cwd, policy, prev) {
|
|
|
2139
2139
|
}
|
|
2140
2140
|
if (st.isDirectory()) walk(r);
|
|
2141
2141
|
else if (st.isFile() && isProtected(r, policy)) {
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
}
|
|
2146
|
-
|
|
2147
|
-
try {
|
|
2148
|
-
hash = sha(readFileSync6(join6(cwd, r)));
|
|
2149
|
-
} catch {
|
|
2150
|
-
hash = "unreadable";
|
|
2151
|
-
}
|
|
2152
|
-
out2[r] = { hash, mode: st.mode, size: st.size, mtimeMs: st.mtimeMs };
|
|
2142
|
+
let hash = "";
|
|
2143
|
+
try {
|
|
2144
|
+
hash = sha(readFileSync6(join6(cwd, r)));
|
|
2145
|
+
} catch {
|
|
2146
|
+
hash = "unreadable";
|
|
2153
2147
|
}
|
|
2148
|
+
out2[r] = { hash, mode: st.mode, size: st.size, mtimeMs: st.mtimeMs };
|
|
2154
2149
|
}
|
|
2155
2150
|
}
|
|
2156
2151
|
};
|
|
@@ -2959,11 +2954,69 @@ function parseVerify(args) {
|
|
|
2959
2954
|
|
|
2960
2955
|
// src/cli/run.ts
|
|
2961
2956
|
import { execFileSync as execFileSync5, spawnSync as spawnSync2 } from "node:child_process";
|
|
2957
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
2958
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync11, realpathSync } from "node:fs";
|
|
2959
|
+
import { join as join10 } from "node:path";
|
|
2962
2960
|
var out = (s) => process.stdout.write(s + "\n");
|
|
2963
2961
|
var err = (s) => process.stderr.write(s + "\n");
|
|
2964
2962
|
function git3(args, cwd) {
|
|
2965
2963
|
return execFileSync5("git", args, { cwd, encoding: "utf8", maxBuffer: 1 << 28 });
|
|
2966
2964
|
}
|
|
2965
|
+
function treeFingerprint(cwd) {
|
|
2966
|
+
const files = git3(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean).sort();
|
|
2967
|
+
const h = createHash5("sha256");
|
|
2968
|
+
for (const rel of files) {
|
|
2969
|
+
h.update(rel);
|
|
2970
|
+
h.update("\0");
|
|
2971
|
+
try {
|
|
2972
|
+
h.update(readFileSync11(join10(cwd, rel)));
|
|
2973
|
+
} catch {
|
|
2974
|
+
h.update("<unreadable>");
|
|
2975
|
+
}
|
|
2976
|
+
h.update("\0");
|
|
2977
|
+
}
|
|
2978
|
+
return h.digest("hex");
|
|
2979
|
+
}
|
|
2980
|
+
function nowTicks() {
|
|
2981
|
+
try {
|
|
2982
|
+
return Math.floor(parseFloat(readFileSync11("/proc/uptime", "utf8").split(" ")[0]) * 100);
|
|
2983
|
+
} catch {
|
|
2984
|
+
return Number.POSITIVE_INFINITY;
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
2988
|
+
const out2 = [];
|
|
2989
|
+
let real;
|
|
2990
|
+
try {
|
|
2991
|
+
real = realpathSync(cwd);
|
|
2992
|
+
} catch {
|
|
2993
|
+
return out2;
|
|
2994
|
+
}
|
|
2995
|
+
let pids;
|
|
2996
|
+
try {
|
|
2997
|
+
pids = readdirSync3("/proc").filter((d) => /^\d+$/.test(d));
|
|
2998
|
+
} catch {
|
|
2999
|
+
return out2;
|
|
3000
|
+
}
|
|
3001
|
+
for (const pid of pids) {
|
|
3002
|
+
const n = Number(pid);
|
|
3003
|
+
if (n === process.pid) continue;
|
|
3004
|
+
let held;
|
|
3005
|
+
try {
|
|
3006
|
+
held = realpathSync(`/proc/${pid}/cwd`);
|
|
3007
|
+
} catch {
|
|
3008
|
+
continue;
|
|
3009
|
+
}
|
|
3010
|
+
if (held !== real && !held.startsWith(real + "/")) continue;
|
|
3011
|
+
try {
|
|
3012
|
+
const stat = readFileSync11(`/proc/${pid}/stat`, "utf8");
|
|
3013
|
+
const startTicks = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]);
|
|
3014
|
+
if (Number.isFinite(startTicks) && startTicks >= spawnedAfterTicks) out2.push(n);
|
|
3015
|
+
} catch {
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
return out2;
|
|
3019
|
+
}
|
|
2967
3020
|
function runEnvelope(opts) {
|
|
2968
3021
|
const cwd = opts.cwd ?? process.cwd();
|
|
2969
3022
|
if (!opts.argv.length) {
|
|
@@ -3002,6 +3055,7 @@ function runEnvelope(opts) {
|
|
|
3002
3055
|
}
|
|
3003
3056
|
}
|
|
3004
3057
|
out(`tamperward run \u2014 trusted base ${base.slice(0, 10)}; agent exit is untrusted.`);
|
|
3058
|
+
const spawnTicks = nowTicks();
|
|
3005
3059
|
const agent = spawnSync2(opts.argv[0], opts.argv.slice(1), { cwd, stdio: "inherit" });
|
|
3006
3060
|
const agentExit = agent.status ?? 1;
|
|
3007
3061
|
const head = git3(["rev-parse", "HEAD"], cwd).trim();
|
|
@@ -3013,6 +3067,7 @@ function runEnvelope(opts) {
|
|
|
3013
3067
|
tamperward run \u2014 agent exit ${agentExit}; HISTORY_REWRITE \u2192 ENFORCEMENT_FAILED`);
|
|
3014
3068
|
return 1;
|
|
3015
3069
|
}
|
|
3070
|
+
const fpBefore = treeFingerprint(cwd);
|
|
3016
3071
|
let diffCode = 0;
|
|
3017
3072
|
if (head !== base) {
|
|
3018
3073
|
out(`
|
|
@@ -3023,10 +3078,27 @@ tamperward run \u2014 agent exit ${agentExit}; HISTORY_REWRITE \u2192 ENFORCEMEN
|
|
|
3023
3078
|
const workCode = runCheck({ worktree: true, cwd, policyOverride: frozenPolicy, includeUntracked: true, ciLayer: true });
|
|
3024
3079
|
out("\n[envelope] pristine verification against the trusted base:");
|
|
3025
3080
|
const verifyCode = runVerify({ cwd, base, cmd: frozenCmd, budget: frozenBudget, policyOverride: frozenPolicy });
|
|
3081
|
+
if (opts.settle && opts.settle > 0) {
|
|
3082
|
+
try {
|
|
3083
|
+
execFileSync5("sleep", [String(opts.settle)]);
|
|
3084
|
+
} catch {
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
const mutatedDuringAdjudication = treeFingerprint(cwd) !== fpBefore;
|
|
3088
|
+
if (mutatedDuringAdjudication) {
|
|
3089
|
+
err("tamperward run: the working tree changed while it was being adjudicated \u2014");
|
|
3090
|
+
err("the verdict below would describe a tree that no longer exists.");
|
|
3091
|
+
}
|
|
3092
|
+
const survivors = survivorsHoldingTree(cwd, spawnTicks);
|
|
3093
|
+
if (survivors.length) {
|
|
3094
|
+
err(`tamperward run: ${survivors.length} process(es) started during this run still hold the`);
|
|
3095
|
+
err(`working tree (pid ${survivors.join(", ")}). A verdict cannot outlive the tree it describes.`);
|
|
3096
|
+
}
|
|
3097
|
+
const notQuiescent = mutatedDuringAdjudication || survivors.length > 0;
|
|
3026
3098
|
const cannot = diffCode === 2 || workCode === 2 || verifyCode === 2;
|
|
3027
|
-
const blocked = diffCode === 1 || workCode === 1 || verifyCode === 1;
|
|
3099
|
+
const blocked = diffCode === 1 || workCode === 1 || verifyCode === 1 || notQuiescent;
|
|
3028
3100
|
const enforcement = cannot ? 2 : blocked ? 1 : 0;
|
|
3029
|
-
const verdict2 = enforcement === 2 ? "CANNOT_ADJUDICATE (fail closed)" : enforcement === 1 ? "ENFORCEMENT_FAILED \u2014 the tree the runtime released does not stand" : agentExit !== 0 ? `agent exited ${agentExit} (enforcement clean)` : "GREEN MEANS GREEN";
|
|
3101
|
+
const verdict2 = enforcement === 2 ? "CANNOT_ADJUDICATE (fail closed)" : notQuiescent ? "NOT_QUIESCENT \u2014 the tree moved, or something still holds it" : enforcement === 1 ? "ENFORCEMENT_FAILED \u2014 the tree the runtime released does not stand" : agentExit !== 0 ? `agent exited ${agentExit} (enforcement clean)` : "GREEN MEANS GREEN";
|
|
3030
3102
|
out(`
|
|
3031
3103
|
tamperward run \u2014 agent exit ${agentExit}; checks diff=${diffCode} worktree=${workCode} verify=${verifyCode} \u2192 ${verdict2}`);
|
|
3032
3104
|
return enforcement !== 0 ? enforcement : agentExit;
|
|
@@ -3042,6 +3114,7 @@ function parseRun(args) {
|
|
|
3042
3114
|
else if (a === "--cmd") o.cmd = args[++i];
|
|
3043
3115
|
else if (a === "--budget") o.budget = Number(args[++i]);
|
|
3044
3116
|
else if (a === "--allow-dirty") o.allowDirty = true;
|
|
3117
|
+
else if (a === "--settle") o.settle = Number(args[++i]);
|
|
3045
3118
|
else if (a === "--cwd") o.cwd = args[++i];
|
|
3046
3119
|
else {
|
|
3047
3120
|
o.argv = args.slice(i);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.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",
|