tamperward 2.15.2 → 2.16.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 +12 -0
- package/dist/cli/index.js +485 -109
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -369,6 +369,18 @@ restored `"test": "sh scripts/test.sh"` will happily call a script nothing
|
|
|
369
369
|
restored. It bounds the class rather than closing it — see
|
|
370
370
|
[the threat model](./docs/THREAT-MODEL-pristine-run.md).
|
|
371
371
|
|
|
372
|
+
From **2.16.0**, verifier suite output is diagnostic evidence instead of discarded
|
|
373
|
+
noise. Both visible and pristine stages continuously drain stdout/stderr through a
|
|
374
|
+
trusted supervisor, retain only the final **16 KiB per stream**, and count the total
|
|
375
|
+
bytes observed. Structured output exposes this under
|
|
376
|
+
`visible.diagnostics` / `pristine.diagnostics` with
|
|
377
|
+
`captured_bytes`, `retained_bytes`, `truncated`, and a bounded `tail` on a
|
|
378
|
+
failed stage. Default human output remains quiet on success. On failure, retained
|
|
379
|
+
diagnostics are rendered with every line prefixed and terminal/control characters
|
|
380
|
+
escaped, so candidate output cannot become ANSI control traffic or a GitHub
|
|
381
|
+
`::command::`. These bytes are evidence produced by candidate code; they never own
|
|
382
|
+
the verifier verdict.
|
|
383
|
+
|
|
372
384
|
The four primitives:
|
|
373
385
|
|
|
374
386
|
```bash
|
package/dist/cli/index.js
CHANGED
|
@@ -7085,13 +7085,14 @@ function watchTree(dir, cb, onState, onError) {
|
|
|
7085
7085
|
}
|
|
7086
7086
|
}
|
|
7087
7087
|
const watchers = /* @__PURE__ */ new Map();
|
|
7088
|
+
let closed = false;
|
|
7088
7089
|
onState("fallback", 0);
|
|
7089
7090
|
const addDir = (rel) => {
|
|
7090
|
-
if (watchers.has(rel) || SKIP2.test(rel + "/")) return;
|
|
7091
|
+
if (closed || watchers.has(rel) || SKIP2.test(rel + "/")) return;
|
|
7091
7092
|
let w;
|
|
7092
7093
|
try {
|
|
7093
7094
|
w = watch(rel ? join11(dir, rel) : dir, (kind, fname) => {
|
|
7094
|
-
if (!fname) return;
|
|
7095
|
+
if (closed || !fname) return;
|
|
7095
7096
|
const child = rel ? `${rel}/${String(fname)}` : String(fname);
|
|
7096
7097
|
cb(kind, child);
|
|
7097
7098
|
try {
|
|
@@ -7129,9 +7130,14 @@ function watchTree(dir, cb, onState, onError) {
|
|
|
7129
7130
|
}
|
|
7130
7131
|
};
|
|
7131
7132
|
walk2("");
|
|
7132
|
-
return {
|
|
7133
|
-
|
|
7134
|
-
|
|
7133
|
+
return {
|
|
7134
|
+
close: () => {
|
|
7135
|
+
if (closed) return;
|
|
7136
|
+
closed = true;
|
|
7137
|
+
for (const w of watchers.values()) w.close();
|
|
7138
|
+
watchers.clear();
|
|
7139
|
+
}
|
|
7140
|
+
};
|
|
7135
7141
|
}
|
|
7136
7142
|
function startWatcher(dir, log, policy) {
|
|
7137
7143
|
try {
|
|
@@ -7215,8 +7221,11 @@ function startWatcher(dir, log, policy) {
|
|
|
7215
7221
|
},
|
|
7216
7222
|
(detail) => degrade(detail)
|
|
7217
7223
|
);
|
|
7224
|
+
let closed = false;
|
|
7218
7225
|
return {
|
|
7219
7226
|
close: () => {
|
|
7227
|
+
if (closed) return;
|
|
7228
|
+
closed = true;
|
|
7220
7229
|
tree.close();
|
|
7221
7230
|
health.state = "stopped";
|
|
7222
7231
|
health.stopped_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9152,11 +9161,11 @@ function runDoctor(opts = {}) {
|
|
|
9152
9161
|
}
|
|
9153
9162
|
|
|
9154
9163
|
// src/cli/verify.ts
|
|
9155
|
-
import { execFileSync as execFileSync9, spawnSync as
|
|
9164
|
+
import { execFileSync as execFileSync9, spawnSync as spawnSync3 } from "node:child_process";
|
|
9156
9165
|
import { createHash as createHash10 } from "node:crypto";
|
|
9157
|
-
import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync5, mkdirSync as mkdirSync6, mkdtempSync as
|
|
9158
|
-
import { tmpdir as
|
|
9159
|
-
import { dirname as dirname7, isAbsolute as isAbsolute10, join as
|
|
9166
|
+
import { chmodSync as chmodSync2, cpSync, lstatSync as lstatSync5, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync3, readFileSync as readFileSync16, readlinkSync as readlinkSync3, rmSync as rmSync4, symlinkSync, writeFileSync as writeFileSync8 } from "node:fs";
|
|
9167
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
9168
|
+
import { dirname as dirname7, isAbsolute as isAbsolute10, join as join17, relative as relative5, resolve as resolve7, sep as sep2 } from "node:path";
|
|
9160
9169
|
|
|
9161
9170
|
// src/fingerprint.ts
|
|
9162
9171
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
@@ -9539,9 +9548,321 @@ function dependencyEnvironmentSummary(descriptor) {
|
|
|
9539
9548
|
|
|
9540
9549
|
// src/verifier-backend.ts
|
|
9541
9550
|
import { createHash as createHash9, randomUUID } from "node:crypto";
|
|
9551
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
9552
|
+
import { accessSync, constants as constants2, readFileSync as readFileSync15, realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
|
|
9553
|
+
import { delimiter as delimiter2, dirname as dirname6, join as join16, resolve as resolve6 } from "node:path";
|
|
9554
|
+
|
|
9555
|
+
// src/suite-diagnostics.ts
|
|
9542
9556
|
import { spawnSync } from "node:child_process";
|
|
9543
|
-
import {
|
|
9544
|
-
import {
|
|
9557
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "node:fs";
|
|
9558
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
9559
|
+
import { join as join15 } from "node:path";
|
|
9560
|
+
var DIAGNOSTIC_TAIL_BYTES = 16 * 1024;
|
|
9561
|
+
var EMPTY_STREAM = {
|
|
9562
|
+
captured_bytes: 0,
|
|
9563
|
+
retained_bytes: 0,
|
|
9564
|
+
truncated: false,
|
|
9565
|
+
tail: ""
|
|
9566
|
+
};
|
|
9567
|
+
function emptySuiteDiagnostics() {
|
|
9568
|
+
return {
|
|
9569
|
+
stdout: { ...EMPTY_STREAM },
|
|
9570
|
+
stderr: { ...EMPTY_STREAM }
|
|
9571
|
+
};
|
|
9572
|
+
}
|
|
9573
|
+
function decodeUtf8Tail(tail) {
|
|
9574
|
+
let start = 0;
|
|
9575
|
+
while (start < tail.length && (tail[start] & 192) === 128) start++;
|
|
9576
|
+
return tail.subarray(start).toString("utf8");
|
|
9577
|
+
}
|
|
9578
|
+
function streamFromRaw(raw) {
|
|
9579
|
+
let tail = Buffer.alloc(0);
|
|
9580
|
+
try {
|
|
9581
|
+
tail = Buffer.from(raw?.tail_b64 ?? "", "base64");
|
|
9582
|
+
} catch {
|
|
9583
|
+
tail = Buffer.alloc(0);
|
|
9584
|
+
}
|
|
9585
|
+
const captured = Number.isFinite(raw?.captured_bytes) ? Math.max(0, Number(raw?.captured_bytes)) : tail.length;
|
|
9586
|
+
return {
|
|
9587
|
+
captured_bytes: captured,
|
|
9588
|
+
retained_bytes: tail.length,
|
|
9589
|
+
truncated: captured > tail.length,
|
|
9590
|
+
tail: decodeUtf8Tail(tail)
|
|
9591
|
+
};
|
|
9592
|
+
}
|
|
9593
|
+
function scrubDiagnosticText(input) {
|
|
9594
|
+
let out2 = "";
|
|
9595
|
+
for (const ch of input) {
|
|
9596
|
+
const cp = ch.codePointAt(0);
|
|
9597
|
+
if (ch === "\n" || ch === " ") {
|
|
9598
|
+
out2 += ch;
|
|
9599
|
+
} else if (cp === 13) {
|
|
9600
|
+
out2 += "\\r";
|
|
9601
|
+
} else if (cp < 32 || cp >= 127 && cp <= 159) {
|
|
9602
|
+
out2 += cp <= 255 ? `\\x${cp.toString(16).padStart(2, "0")}` : `\\u{${cp.toString(16)}}`;
|
|
9603
|
+
} else {
|
|
9604
|
+
out2 += ch;
|
|
9605
|
+
}
|
|
9606
|
+
}
|
|
9607
|
+
return out2;
|
|
9608
|
+
}
|
|
9609
|
+
function diagnosticLines(stage, diagnostics) {
|
|
9610
|
+
const lines = [];
|
|
9611
|
+
for (const streamName of ["stdout", "stderr"]) {
|
|
9612
|
+
const stream = diagnostics[streamName];
|
|
9613
|
+
if (stream.captured_bytes === 0) continue;
|
|
9614
|
+
const suffix = stream.truncated ? ` \u2014 tail ${stream.retained_bytes}/${stream.captured_bytes} bytes retained` : ` \u2014 ${stream.captured_bytes} bytes`;
|
|
9615
|
+
lines.push(`${stage} suite ${streamName}${suffix}:`);
|
|
9616
|
+
const scrubbed = scrubDiagnosticText(stream.tail);
|
|
9617
|
+
const body = scrubbed.split("\n");
|
|
9618
|
+
if (body.at(-1) === "") body.pop();
|
|
9619
|
+
for (const line of body) lines.push(` | ${line}`);
|
|
9620
|
+
}
|
|
9621
|
+
return lines;
|
|
9622
|
+
}
|
|
9623
|
+
var CAPTURE_SUPERVISOR = String.raw`
|
|
9624
|
+
const cp = require('node:child_process');
|
|
9625
|
+
const fs = require('node:fs');
|
|
9626
|
+
|
|
9627
|
+
const [configFile] = process.argv.slice(1);
|
|
9628
|
+
const cfg = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
9629
|
+
const CAP = Number(cfg.captureBytes);
|
|
9630
|
+
|
|
9631
|
+
const fresh = () => ({ total: 0, tail: Buffer.alloc(0) });
|
|
9632
|
+
const stdout = fresh();
|
|
9633
|
+
const stderr = fresh();
|
|
9634
|
+
|
|
9635
|
+
// Linux ordinary-exit hardening (#371): process-group ownership alone misses a
|
|
9636
|
+
// descendant that calls setsid(). Track the real descendant tree while the
|
|
9637
|
+
// suite is alive so a reparented session escape is still known when the main
|
|
9638
|
+
// child exits. This supplements, rather than replaces, group termination.
|
|
9639
|
+
const trackedDescendants = new Set();
|
|
9640
|
+
|
|
9641
|
+
function linuxDescendants(rootPid) {
|
|
9642
|
+
if (process.platform !== 'linux' || !rootPid) return [];
|
|
9643
|
+
const byParent = new Map();
|
|
9644
|
+
let names = [];
|
|
9645
|
+
try { names = fs.readdirSync('/proc').filter((x) => /^\d+$/.test(x)); } catch { return []; }
|
|
9646
|
+
for (const name of names) {
|
|
9647
|
+
const pid = Number(name);
|
|
9648
|
+
try {
|
|
9649
|
+
const stat = fs.readFileSync('/proc/' + name + '/stat', 'utf8');
|
|
9650
|
+
const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
|
|
9651
|
+
const ppid = Number(fields[1]);
|
|
9652
|
+
if (!Number.isFinite(ppid)) continue;
|
|
9653
|
+
const kids = byParent.get(ppid) || [];
|
|
9654
|
+
kids.push(pid);
|
|
9655
|
+
byParent.set(ppid, kids);
|
|
9656
|
+
} catch {}
|
|
9657
|
+
}
|
|
9658
|
+
const out = [];
|
|
9659
|
+
const seen = new Set([rootPid]);
|
|
9660
|
+
const stack = [rootPid];
|
|
9661
|
+
while (stack.length) {
|
|
9662
|
+
const parent = stack.pop();
|
|
9663
|
+
for (const pid of (byParent.get(parent) || [])) {
|
|
9664
|
+
if (seen.has(pid)) continue;
|
|
9665
|
+
seen.add(pid);
|
|
9666
|
+
out.push(pid);
|
|
9667
|
+
stack.push(pid);
|
|
9668
|
+
}
|
|
9669
|
+
}
|
|
9670
|
+
return out;
|
|
9671
|
+
}
|
|
9672
|
+
|
|
9673
|
+
function trackDescendants() {
|
|
9674
|
+
if (!child || !child.pid || process.platform !== 'linux') return;
|
|
9675
|
+
for (const pid of linuxDescendants(child.pid)) trackedDescendants.add(pid);
|
|
9676
|
+
}
|
|
9677
|
+
|
|
9678
|
+
function append(state, chunk) {
|
|
9679
|
+
const b = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
9680
|
+
state.total += b.length;
|
|
9681
|
+
if (b.length >= CAP) {
|
|
9682
|
+
state.tail = Buffer.from(b.subarray(b.length - CAP));
|
|
9683
|
+
return;
|
|
9684
|
+
}
|
|
9685
|
+
if (state.tail.length + b.length <= CAP) {
|
|
9686
|
+
state.tail = Buffer.concat([state.tail, b]);
|
|
9687
|
+
return;
|
|
9688
|
+
}
|
|
9689
|
+
const keep = CAP - b.length;
|
|
9690
|
+
state.tail = Buffer.concat([state.tail.subarray(state.tail.length - keep), b]);
|
|
9691
|
+
}
|
|
9692
|
+
|
|
9693
|
+
function diag(state) {
|
|
9694
|
+
return {
|
|
9695
|
+
captured_bytes: state.total,
|
|
9696
|
+
tail_b64: state.tail.toString('base64'),
|
|
9697
|
+
};
|
|
9698
|
+
}
|
|
9699
|
+
|
|
9700
|
+
let child;
|
|
9701
|
+
let done = false;
|
|
9702
|
+
let timedOut = false;
|
|
9703
|
+
let exitInfo = { exit: null, signal: null };
|
|
9704
|
+
let descendantTracker = null;
|
|
9705
|
+
|
|
9706
|
+
function killOwned() {
|
|
9707
|
+
if (!child || !child.pid) return;
|
|
9708
|
+
trackDescendants();
|
|
9709
|
+
if (cfg.detached && process.platform !== 'win32') {
|
|
9710
|
+
try { process.kill(-child.pid, 'SIGKILL'); } catch {}
|
|
9711
|
+
}
|
|
9712
|
+
try { child.kill('SIGKILL'); } catch {}
|
|
9713
|
+
if (process.platform === 'linux') {
|
|
9714
|
+
// Deepest-first is friendlier to short-lived process trees. PIDs are still
|
|
9715
|
+
// rechecked by kill(2); races with natural exit are harmless.
|
|
9716
|
+
for (const pid of Array.from(trackedDescendants).reverse()) {
|
|
9717
|
+
try { process.kill(pid, 'SIGKILL'); } catch {}
|
|
9718
|
+
}
|
|
9719
|
+
}
|
|
9720
|
+
}
|
|
9721
|
+
|
|
9722
|
+
function finish(extra) {
|
|
9723
|
+
if (done) return;
|
|
9724
|
+
done = true;
|
|
9725
|
+
if (descendantTracker) clearInterval(descendantTracker);
|
|
9726
|
+
try {
|
|
9727
|
+
// stdout is reserved for the trusted supervisor result. Candidate suite
|
|
9728
|
+
// stdout/stderr are separate pipes and are never inherited here.
|
|
9729
|
+
process.stdout.write(JSON.stringify({
|
|
9730
|
+
...exitInfo,
|
|
9731
|
+
timedOut,
|
|
9732
|
+
...extra,
|
|
9733
|
+
stdout: diag(stdout),
|
|
9734
|
+
stderr: diag(stderr),
|
|
9735
|
+
}));
|
|
9736
|
+
} catch {}
|
|
9737
|
+
process.exit(0);
|
|
9738
|
+
}
|
|
9739
|
+
|
|
9740
|
+
try {
|
|
9741
|
+
child = cp.spawn(cfg.executable, cfg.args, {
|
|
9742
|
+
cwd: cfg.cwd,
|
|
9743
|
+
env: process.env,
|
|
9744
|
+
detached: Boolean(cfg.detached) && process.platform !== 'win32',
|
|
9745
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
9746
|
+
});
|
|
9747
|
+
} catch (e) {
|
|
9748
|
+
finish({ error: String(e) });
|
|
9749
|
+
}
|
|
9750
|
+
|
|
9751
|
+
if (child) {
|
|
9752
|
+
if (process.platform === 'linux') {
|
|
9753
|
+
trackDescendants();
|
|
9754
|
+
descendantTracker = setInterval(trackDescendants, 5);
|
|
9755
|
+
descendantTracker.unref();
|
|
9756
|
+
}
|
|
9757
|
+
child.stdout.on('data', (b) => append(stdout, b));
|
|
9758
|
+
child.stderr.on('data', (b) => append(stderr, b));
|
|
9759
|
+
|
|
9760
|
+
const timer = setTimeout(() => {
|
|
9761
|
+
timedOut = true;
|
|
9762
|
+
killOwned();
|
|
9763
|
+
// Backstop if a platform fails to emit close after the forced kill.
|
|
9764
|
+
setTimeout(() => finish({}), 1000).unref();
|
|
9765
|
+
}, Number(cfg.timeoutMs));
|
|
9766
|
+
timer.unref();
|
|
9767
|
+
|
|
9768
|
+
child.on('error', (e) => {
|
|
9769
|
+
clearTimeout(timer);
|
|
9770
|
+
killOwned();
|
|
9771
|
+
finish({ error: String(e) });
|
|
9772
|
+
});
|
|
9773
|
+
|
|
9774
|
+
child.on('exit', (code, signal) => {
|
|
9775
|
+
exitInfo = {
|
|
9776
|
+
exit: code == null ? null : Number(code),
|
|
9777
|
+
signal: signal == null ? null : String(signal),
|
|
9778
|
+
};
|
|
9779
|
+
if (cfg.killGroupOnFinish) killOwned();
|
|
9780
|
+
});
|
|
9781
|
+
|
|
9782
|
+
// close fires after the stdio pipes close, so the retained tails include all
|
|
9783
|
+
// output that was drained before owned descendants were terminated.
|
|
9784
|
+
child.on('close', (code, signal) => {
|
|
9785
|
+
clearTimeout(timer);
|
|
9786
|
+
if (exitInfo.exit === null && code != null) exitInfo.exit = Number(code);
|
|
9787
|
+
if (exitInfo.signal === null && signal != null) exitInfo.signal = String(signal);
|
|
9788
|
+
finish({});
|
|
9789
|
+
});
|
|
9790
|
+
}
|
|
9791
|
+
`;
|
|
9792
|
+
function parseCapturedSupervisorResult(stdoutText) {
|
|
9793
|
+
let raw;
|
|
9794
|
+
try {
|
|
9795
|
+
raw = JSON.parse(stdoutText);
|
|
9796
|
+
} catch {
|
|
9797
|
+
return null;
|
|
9798
|
+
}
|
|
9799
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
9800
|
+
const diagnostics = {
|
|
9801
|
+
stdout: streamFromRaw(raw.stdout),
|
|
9802
|
+
stderr: streamFromRaw(raw.stderr)
|
|
9803
|
+
};
|
|
9804
|
+
return {
|
|
9805
|
+
exit: typeof raw.exit === "number" ? raw.exit : null,
|
|
9806
|
+
signal: typeof raw.signal === "string" ? raw.signal : null,
|
|
9807
|
+
timedOut: Boolean(raw.timedOut),
|
|
9808
|
+
...typeof raw.error === "string" && raw.error ? { error: raw.error } : {},
|
|
9809
|
+
diagnostics
|
|
9810
|
+
};
|
|
9811
|
+
}
|
|
9812
|
+
function runCapturedProcessSync(executable, args, opts) {
|
|
9813
|
+
const stateDir = mkdtempSync2(join15(tmpdir2(), "tw-suite-capture-"));
|
|
9814
|
+
const configFile = join15(stateDir, "config.json");
|
|
9815
|
+
const backstop = opts.backstopMs ?? 3e4;
|
|
9816
|
+
try {
|
|
9817
|
+
writeFileSync7(
|
|
9818
|
+
configFile,
|
|
9819
|
+
JSON.stringify({
|
|
9820
|
+
executable,
|
|
9821
|
+
args,
|
|
9822
|
+
cwd: opts.cwd,
|
|
9823
|
+
timeoutMs: opts.timeoutMs,
|
|
9824
|
+
detached: Boolean(opts.detached),
|
|
9825
|
+
killGroupOnFinish: Boolean(opts.killGroupOnFinish),
|
|
9826
|
+
captureBytes: DIAGNOSTIC_TAIL_BYTES
|
|
9827
|
+
}),
|
|
9828
|
+
{ mode: 384 }
|
|
9829
|
+
);
|
|
9830
|
+
const supervisor = spawnSync(
|
|
9831
|
+
process.execPath,
|
|
9832
|
+
["-e", CAPTURE_SUPERVISOR, configFile],
|
|
9833
|
+
{
|
|
9834
|
+
cwd: opts.cwd,
|
|
9835
|
+
env: opts.env,
|
|
9836
|
+
encoding: "utf8",
|
|
9837
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
9838
|
+
// The trusted result is at most two 16 KiB tails plus small metadata.
|
|
9839
|
+
// A candidate that somehow writes into the supervisor's stdout through
|
|
9840
|
+
// same-UID /proc can only overflow/corrupt this bounded channel and make
|
|
9841
|
+
// parsing fail closed; its suite streams are never inherited here.
|
|
9842
|
+
maxBuffer: 256 * 1024,
|
|
9843
|
+
timeout: opts.timeoutMs + backstop,
|
|
9844
|
+
killSignal: "SIGKILL"
|
|
9845
|
+
}
|
|
9846
|
+
);
|
|
9847
|
+
const parsed = parseCapturedSupervisorResult(supervisor.stdout ?? "");
|
|
9848
|
+
const diagnostics = parsed?.diagnostics ?? emptySuiteDiagnostics();
|
|
9849
|
+
if (!parsed) {
|
|
9850
|
+
const supervisorTimedOut = Boolean(supervisor.error) && supervisor.error.code === "ETIMEDOUT";
|
|
9851
|
+
return {
|
|
9852
|
+
exit: null,
|
|
9853
|
+
signal: supervisor.signal ? String(supervisor.signal) : null,
|
|
9854
|
+
timedOut: supervisorTimedOut,
|
|
9855
|
+
error: supervisor.error?.message ?? "suite capture supervisor did not produce a result",
|
|
9856
|
+
diagnostics
|
|
9857
|
+
};
|
|
9858
|
+
}
|
|
9859
|
+
return parsed;
|
|
9860
|
+
} finally {
|
|
9861
|
+
rmSync3(stateDir, { recursive: true, force: true });
|
|
9862
|
+
}
|
|
9863
|
+
}
|
|
9864
|
+
|
|
9865
|
+
// src/verifier-backend.ts
|
|
9545
9866
|
var DEFAULT_CONTAINER_RESOURCE_LIMITS = Object.freeze({
|
|
9546
9867
|
memoryBytes: 2 * 1024 * 1024 * 1024,
|
|
9547
9868
|
// Docker interprets memory-swap == memory as no additional swap allowance.
|
|
@@ -9584,7 +9905,7 @@ function replaceableByCaller(path) {
|
|
|
9584
9905
|
function findExecutable(name) {
|
|
9585
9906
|
for (const raw of (process.env.PATH ?? "").split(delimiter2)) {
|
|
9586
9907
|
if (!raw) continue;
|
|
9587
|
-
const candidate =
|
|
9908
|
+
const candidate = join16(raw, name);
|
|
9588
9909
|
try {
|
|
9589
9910
|
accessSync(candidate, constants2.X_OK);
|
|
9590
9911
|
return realpathSync4(candidate);
|
|
@@ -9594,7 +9915,7 @@ function findExecutable(name) {
|
|
|
9594
9915
|
return null;
|
|
9595
9916
|
}
|
|
9596
9917
|
function fileSha256(path) {
|
|
9597
|
-
return createHash9("sha256").update(
|
|
9918
|
+
return createHash9("sha256").update(readFileSync15(path)).digest("hex");
|
|
9598
9919
|
}
|
|
9599
9920
|
function engineClientEnv() {
|
|
9600
9921
|
return {
|
|
@@ -9609,7 +9930,7 @@ function dockerArgs(host, args) {
|
|
|
9609
9930
|
}
|
|
9610
9931
|
function dockerAvailable(enginePath, host) {
|
|
9611
9932
|
try {
|
|
9612
|
-
const r =
|
|
9933
|
+
const r = spawnSync2(enginePath, dockerArgs(host, ["version"]), {
|
|
9613
9934
|
stdio: "ignore",
|
|
9614
9935
|
timeout: 5e3,
|
|
9615
9936
|
killSignal: "SIGKILL",
|
|
@@ -9637,7 +9958,7 @@ function declaredImageVolumePaths(metadata) {
|
|
|
9637
9958
|
}
|
|
9638
9959
|
function inspectImageMetadata(enginePath, host, image) {
|
|
9639
9960
|
try {
|
|
9640
|
-
const r =
|
|
9961
|
+
const r = spawnSync2(enginePath, dockerArgs(host, ["image", "inspect", image]), {
|
|
9641
9962
|
encoding: "utf8",
|
|
9642
9963
|
stdio: ["ignore", "pipe", "ignore"],
|
|
9643
9964
|
timeout: 1e4,
|
|
@@ -9812,7 +10133,7 @@ function verifierBackendSummary(backend) {
|
|
|
9812
10133
|
function cleanupContainer(backend, name) {
|
|
9813
10134
|
if (!backend.enginePath || !backend.daemonHost || !engineStable(backend)) return;
|
|
9814
10135
|
try {
|
|
9815
|
-
|
|
10136
|
+
spawnSync2(backend.enginePath, dockerArgs(backend.daemonHost, ["rm", "-f", "-v", name]), {
|
|
9816
10137
|
stdio: "ignore",
|
|
9817
10138
|
timeout: 1e4,
|
|
9818
10139
|
killSignal: "SIGKILL",
|
|
@@ -9923,29 +10244,45 @@ function runContainerStage(backend, dir, command, budgetSecs) {
|
|
|
9923
10244
|
resources: backend.resources ?? DEFAULT_CONTAINER_RESOURCE_LIMITS
|
|
9924
10245
|
});
|
|
9925
10246
|
try {
|
|
9926
|
-
const r =
|
|
10247
|
+
const r = runCapturedProcessSync(
|
|
9927
10248
|
backend.enginePath,
|
|
9928
10249
|
dockerArgs(backend.daemonHost, args),
|
|
9929
10250
|
{
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
10251
|
+
cwd: dirname6(backend.enginePath),
|
|
10252
|
+
env: engineClientEnv(),
|
|
10253
|
+
timeoutMs: budgetSecs * 1e3,
|
|
10254
|
+
backstopMs: 3e4
|
|
9934
10255
|
}
|
|
9935
10256
|
);
|
|
9936
10257
|
const secs = Math.round((Date.now() - t0) / 1e3);
|
|
10258
|
+
if (r.timedOut) {
|
|
10259
|
+
cleanupContainer(backend, name);
|
|
10260
|
+
return {
|
|
10261
|
+
exit: null,
|
|
10262
|
+
secs,
|
|
10263
|
+
failure: "budget",
|
|
10264
|
+
reason: "verifier stage exceeded its budget",
|
|
10265
|
+
diagnostics: r.diagnostics
|
|
10266
|
+
};
|
|
10267
|
+
}
|
|
9937
10268
|
if (r.error) {
|
|
9938
10269
|
cleanupContainer(backend, name);
|
|
9939
|
-
|
|
9940
|
-
|
|
10270
|
+
return {
|
|
10271
|
+
exit: null,
|
|
10272
|
+
secs,
|
|
10273
|
+
failure: "backend",
|
|
10274
|
+
reason: `Docker client failed: ${r.error}`,
|
|
10275
|
+
diagnostics: r.diagnostics
|
|
10276
|
+
};
|
|
9941
10277
|
}
|
|
9942
|
-
if (r.
|
|
10278
|
+
if (r.exit === null) {
|
|
9943
10279
|
cleanupContainer(backend, name);
|
|
9944
10280
|
return {
|
|
9945
10281
|
exit: null,
|
|
9946
10282
|
secs,
|
|
9947
10283
|
failure: "backend",
|
|
9948
|
-
reason: `Docker client did not return a status${r.signal ? ` (signal ${r.signal})` : ""}
|
|
10284
|
+
reason: `Docker client did not return a status${r.signal ? ` (signal ${r.signal})` : ""}`,
|
|
10285
|
+
diagnostics: r.diagnostics
|
|
9949
10286
|
};
|
|
9950
10287
|
}
|
|
9951
10288
|
if (!engineStable(backend)) {
|
|
@@ -9953,10 +10290,11 @@ function runContainerStage(backend, dir, command, budgetSecs) {
|
|
|
9953
10290
|
exit: null,
|
|
9954
10291
|
secs,
|
|
9955
10292
|
failure: "backend",
|
|
9956
|
-
reason: "Docker engine identity changed before verifier result inspection"
|
|
10293
|
+
reason: "Docker engine identity changed before verifier result inspection",
|
|
10294
|
+
diagnostics: r.diagnostics
|
|
9957
10295
|
};
|
|
9958
10296
|
}
|
|
9959
|
-
const inspected =
|
|
10297
|
+
const inspected = spawnSync2(
|
|
9960
10298
|
backend.enginePath,
|
|
9961
10299
|
dockerArgs(backend.daemonHost, ["inspect", "--format", "{{json .State}}", name]),
|
|
9962
10300
|
{
|
|
@@ -9976,7 +10314,10 @@ function runContainerStage(backend, dir, command, budgetSecs) {
|
|
|
9976
10314
|
}
|
|
9977
10315
|
}
|
|
9978
10316
|
cleanupContainer(backend, name);
|
|
9979
|
-
return
|
|
10317
|
+
return {
|
|
10318
|
+
...containerStateResult(state, secs),
|
|
10319
|
+
diagnostics: r.diagnostics
|
|
10320
|
+
};
|
|
9980
10321
|
} catch (e) {
|
|
9981
10322
|
cleanupContainer(backend, name);
|
|
9982
10323
|
return {
|
|
@@ -10081,7 +10422,7 @@ function resolveBase(base, cwd) {
|
|
|
10081
10422
|
}
|
|
10082
10423
|
}
|
|
10083
10424
|
function baseIsAncestorOfHead(base, cwd) {
|
|
10084
|
-
const r =
|
|
10425
|
+
const r = spawnSync3("git", ["merge-base", "--is-ancestor", base, "HEAD"], { cwd, env: trustedGitEnv() });
|
|
10085
10426
|
return r.status === 0;
|
|
10086
10427
|
}
|
|
10087
10428
|
function inside2(root, path) {
|
|
@@ -10105,7 +10446,7 @@ function safeSymlink(target, out2, root, label) {
|
|
|
10105
10446
|
if (rootedLinkTarget(target) || !inside2(root, resolve7(dirname7(out2), target))) {
|
|
10106
10447
|
throw linkEscape(label, target);
|
|
10107
10448
|
}
|
|
10108
|
-
|
|
10449
|
+
rmSync4(out2, { force: true });
|
|
10109
10450
|
symlinkSync(target, out2);
|
|
10110
10451
|
}
|
|
10111
10452
|
function validateSymlinkGraph(target, out2, root, dependencyRoot, label) {
|
|
@@ -10128,7 +10469,7 @@ function validateSymlinkGraph(target, out2, root, dependencyRoot, label) {
|
|
|
10128
10469
|
continue;
|
|
10129
10470
|
}
|
|
10130
10471
|
const base = domain === "tree" ? root : dependencyRoot;
|
|
10131
|
-
const current =
|
|
10472
|
+
const current = join17(base, ...stack);
|
|
10132
10473
|
let st;
|
|
10133
10474
|
try {
|
|
10134
10475
|
st = lstatSync5(current);
|
|
@@ -10164,7 +10505,7 @@ function rejectLinkedParent(cwd, rel) {
|
|
|
10164
10505
|
const parts = rel.split("/").slice(0, -1);
|
|
10165
10506
|
let current = cwd;
|
|
10166
10507
|
for (const part of parts) {
|
|
10167
|
-
current =
|
|
10508
|
+
current = join17(current, part);
|
|
10168
10509
|
let st;
|
|
10169
10510
|
try {
|
|
10170
10511
|
st = lstatSync5(current);
|
|
@@ -10180,7 +10521,7 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
10180
10521
|
const listed = git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
|
|
10181
10522
|
const links = [];
|
|
10182
10523
|
for (const rel of listed) {
|
|
10183
|
-
const src =
|
|
10524
|
+
const src = join17(cwd, rel);
|
|
10184
10525
|
rejectLinkedParent(cwd, rel);
|
|
10185
10526
|
let st;
|
|
10186
10527
|
try {
|
|
@@ -10188,7 +10529,7 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
10188
10529
|
} catch {
|
|
10189
10530
|
continue;
|
|
10190
10531
|
}
|
|
10191
|
-
const out2 =
|
|
10532
|
+
const out2 = join17(dest, rel);
|
|
10192
10533
|
mkdirSync6(dirname7(out2), { recursive: true });
|
|
10193
10534
|
dropSymlink(out2);
|
|
10194
10535
|
if (st.isSymbolicLink()) {
|
|
@@ -10205,11 +10546,11 @@ function materialize(cwd, dest, dependencyRoot) {
|
|
|
10205
10546
|
for (const link of links) {
|
|
10206
10547
|
validateSymlinkGraph(link.target, link.out, dest, dependencyRoot, link.rel);
|
|
10207
10548
|
}
|
|
10208
|
-
if (dependencyRoot) symlinkSync(dependencyRoot,
|
|
10549
|
+
if (dependencyRoot) symlinkSync(dependencyRoot, join17(dest, "node_modules"), "dir");
|
|
10209
10550
|
}
|
|
10210
10551
|
function dropSymlink(p) {
|
|
10211
10552
|
try {
|
|
10212
|
-
if (lstatSync5(p).isSymbolicLink())
|
|
10553
|
+
if (lstatSync5(p).isSymbolicLink()) rmSync4(p, { force: true });
|
|
10213
10554
|
} catch {
|
|
10214
10555
|
}
|
|
10215
10556
|
}
|
|
@@ -10238,11 +10579,11 @@ function overlayDigest(dest, paths) {
|
|
|
10238
10579
|
for (const rel of [...paths].sort()) {
|
|
10239
10580
|
h.update(rel);
|
|
10240
10581
|
h.update("\0");
|
|
10241
|
-
const p =
|
|
10582
|
+
const p = join17(dest, rel);
|
|
10242
10583
|
try {
|
|
10243
10584
|
const st = lstatSync5(p);
|
|
10244
10585
|
h.update(String(st.mode));
|
|
10245
|
-
h.update(st.isSymbolicLink() ? readlinkSync3(p) :
|
|
10586
|
+
h.update(st.isSymbolicLink() ? readlinkSync3(p) : readFileSync16(p));
|
|
10246
10587
|
} catch {
|
|
10247
10588
|
h.update("<gone>");
|
|
10248
10589
|
}
|
|
@@ -10266,15 +10607,15 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot) {
|
|
|
10266
10607
|
throw new Error(`the base's ${e.path} is a submodule; the pristine copy cannot reproduce it`);
|
|
10267
10608
|
}
|
|
10268
10609
|
const content = execFileSync9("git", ["show", `${base}:${e.path}`], { cwd, maxBuffer: 1 << 28, env: trustedGitEnv() });
|
|
10269
|
-
const out2 =
|
|
10610
|
+
const out2 = join17(dest, e.path);
|
|
10270
10611
|
mkdirSync6(dirname7(out2), { recursive: true });
|
|
10271
|
-
|
|
10612
|
+
rmSync4(out2, { force: true });
|
|
10272
10613
|
if (e.mode === "120000") {
|
|
10273
10614
|
const target = content.toString("utf8");
|
|
10274
10615
|
safeSymlink(target, out2, dest, `the base's ${e.path}`);
|
|
10275
10616
|
restoredLinks.push({ path: e.path, out: out2, target });
|
|
10276
10617
|
} else {
|
|
10277
|
-
|
|
10618
|
+
writeFileSync8(out2, content);
|
|
10278
10619
|
chmodSync2(out2, parseInt(e.mode.slice(-4), 8) & 511);
|
|
10279
10620
|
}
|
|
10280
10621
|
restored.push(e.path);
|
|
@@ -10283,7 +10624,7 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot) {
|
|
|
10283
10624
|
const inCopy = git2(["ls-files", "-z", "--cached", "--others", "--exclude-standard"], cwd).split("\0").filter(Boolean);
|
|
10284
10625
|
for (const rel of inCopy) {
|
|
10285
10626
|
if (!isOverlay(rel) || baseProtected.has(rel)) continue;
|
|
10286
|
-
|
|
10627
|
+
rmSync4(join17(dest, rel), { force: true });
|
|
10287
10628
|
removed++;
|
|
10288
10629
|
}
|
|
10289
10630
|
for (const link of restoredLinks) {
|
|
@@ -10291,27 +10632,6 @@ function overlayPristine(cwd, base, dest, policy, cmd, dependencyRoot) {
|
|
|
10291
10632
|
}
|
|
10292
10633
|
return { restored, removed };
|
|
10293
10634
|
}
|
|
10294
|
-
var SUPERVISOR = `
|
|
10295
|
-
const cp = require('node:child_process');
|
|
10296
|
-
const fs = require('node:fs');
|
|
10297
|
-
const [cmd, budgetMs, outFile] = process.argv.slice(1);
|
|
10298
|
-
const child = cp.spawn('sh', ['-c', cmd], { stdio: 'ignore', detached: process.platform !== 'win32' });
|
|
10299
|
-
const killGroup = () => {
|
|
10300
|
-
try { process.kill(-child.pid, 'SIGKILL'); } catch {}
|
|
10301
|
-
try { child.kill('SIGKILL'); } catch {}
|
|
10302
|
-
};
|
|
10303
|
-
let done = false;
|
|
10304
|
-
const finish = (r) => {
|
|
10305
|
-
if (done) return;
|
|
10306
|
-
done = true;
|
|
10307
|
-
killGroup();
|
|
10308
|
-
fs.writeFileSync(outFile, JSON.stringify(r));
|
|
10309
|
-
process.exit(0);
|
|
10310
|
-
};
|
|
10311
|
-
const timer = setTimeout(() => finish({ timedOut: true }), Number(budgetMs));
|
|
10312
|
-
child.on('error', (e) => { clearTimeout(timer); finish({ error: String(e) }); });
|
|
10313
|
-
child.on('exit', (code, signal) => { clearTimeout(timer); finish({ exit: code, signal }); });
|
|
10314
|
-
`;
|
|
10315
10635
|
var DROPPED_ENV = /* @__PURE__ */ new Set([
|
|
10316
10636
|
"NODE_OPTIONS",
|
|
10317
10637
|
"NODE_PATH",
|
|
@@ -10328,9 +10648,9 @@ function suiteEnv(scratch) {
|
|
|
10328
10648
|
if (DROPPED_ENV.has(k) || PINNED_NPM.test(k)) continue;
|
|
10329
10649
|
env[k] = v;
|
|
10330
10650
|
}
|
|
10331
|
-
const userRc =
|
|
10332
|
-
const globalRc =
|
|
10333
|
-
for (const f of [userRc, globalRc])
|
|
10651
|
+
const userRc = join17(scratch, "npmrc-user");
|
|
10652
|
+
const globalRc = join17(scratch, "npmrc-global");
|
|
10653
|
+
for (const f of [userRc, globalRc]) writeFileSync8(f, "", { mode: 292 });
|
|
10334
10654
|
env.npm_config_userconfig = userRc;
|
|
10335
10655
|
env.npm_config_globalconfig = globalRc;
|
|
10336
10656
|
env.npm_config_node_options = " ";
|
|
@@ -10339,31 +10659,75 @@ function suiteEnv(scratch) {
|
|
|
10339
10659
|
}
|
|
10340
10660
|
function runLocalSuite(dir, cmd, budgetSecs) {
|
|
10341
10661
|
const t0 = Date.now();
|
|
10342
|
-
const
|
|
10343
|
-
const outFile = join16(outDir, "result.json");
|
|
10662
|
+
const scratch = mkdtempSync3(join17(tmpdir3(), "tw-verify-run-"));
|
|
10344
10663
|
try {
|
|
10345
|
-
|
|
10664
|
+
const r = runCapturedProcessSync("sh", ["-c", cmd], {
|
|
10346
10665
|
cwd: dir,
|
|
10347
|
-
env: suiteEnv(
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
|
|
10351
|
-
|
|
10666
|
+
env: suiteEnv(scratch),
|
|
10667
|
+
timeoutMs: budgetSecs * 1e3,
|
|
10668
|
+
detached: process.platform !== "win32",
|
|
10669
|
+
killGroupOnFinish: true,
|
|
10670
|
+
backstopMs: 3e4
|
|
10352
10671
|
});
|
|
10353
10672
|
const secs = Math.round((Date.now() - t0) / 1e3);
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10673
|
+
if (r.timedOut) {
|
|
10674
|
+
return {
|
|
10675
|
+
exit: null,
|
|
10676
|
+
secs,
|
|
10677
|
+
failure: "budget",
|
|
10678
|
+
reason: "verifier stage exceeded its budget",
|
|
10679
|
+
diagnostics: r.diagnostics
|
|
10680
|
+
};
|
|
10359
10681
|
}
|
|
10360
|
-
if (r.
|
|
10361
|
-
|
|
10362
|
-
|
|
10682
|
+
if (r.error) {
|
|
10683
|
+
return {
|
|
10684
|
+
exit: null,
|
|
10685
|
+
secs,
|
|
10686
|
+
failure: "backend",
|
|
10687
|
+
reason: r.error,
|
|
10688
|
+
diagnostics: r.diagnostics
|
|
10689
|
+
};
|
|
10690
|
+
}
|
|
10691
|
+
if (r.exit === null) {
|
|
10692
|
+
return {
|
|
10693
|
+
exit: null,
|
|
10694
|
+
secs,
|
|
10695
|
+
failure: "backend",
|
|
10696
|
+
reason: `verifier suite did not return an exit code${r.signal ? ` (signal ${r.signal})` : ""}`,
|
|
10697
|
+
diagnostics: r.diagnostics
|
|
10698
|
+
};
|
|
10699
|
+
}
|
|
10700
|
+
return { exit: r.exit, secs, diagnostics: r.diagnostics };
|
|
10363
10701
|
} finally {
|
|
10364
|
-
|
|
10702
|
+
rmSync4(scratch, { recursive: true, force: true });
|
|
10365
10703
|
}
|
|
10366
10704
|
}
|
|
10705
|
+
function diagnosticsJson(diagnostics, includeTail) {
|
|
10706
|
+
if (!diagnostics) return void 0;
|
|
10707
|
+
const stream = (d) => ({
|
|
10708
|
+
captured_bytes: d.captured_bytes,
|
|
10709
|
+
retained_bytes: d.retained_bytes,
|
|
10710
|
+
truncated: d.truncated,
|
|
10711
|
+
...includeTail && d.captured_bytes > 0 ? { tail: d.tail } : {}
|
|
10712
|
+
});
|
|
10713
|
+
return {
|
|
10714
|
+
stdout: stream(diagnostics.stdout),
|
|
10715
|
+
stderr: stream(diagnostics.stderr)
|
|
10716
|
+
};
|
|
10717
|
+
}
|
|
10718
|
+
function stageJson(result) {
|
|
10719
|
+
const includeTail = result.exit !== 0 || result.failure !== void 0;
|
|
10720
|
+
return {
|
|
10721
|
+
exit: result.exit,
|
|
10722
|
+
secs: result.secs,
|
|
10723
|
+
...result.reason ? { reason: result.reason } : {},
|
|
10724
|
+
...result.diagnostics ? { diagnostics: diagnosticsJson(result.diagnostics, includeTail) } : {}
|
|
10725
|
+
};
|
|
10726
|
+
}
|
|
10727
|
+
function renderStageDiagnostics(out2, stage, result) {
|
|
10728
|
+
if (!result.diagnostics) return;
|
|
10729
|
+
for (const line of diagnosticLines(stage, result.diagnostics)) out2(line);
|
|
10730
|
+
}
|
|
10367
10731
|
function runVerify(opts) {
|
|
10368
10732
|
const cwd = opts.cwd ?? process.cwd();
|
|
10369
10733
|
const out2 = (s) => void process.stdout.write(s + "\n");
|
|
@@ -10445,11 +10809,11 @@ function runVerify(opts) {
|
|
|
10445
10809
|
out2(`verify: cannot resolve base rev "${opts.base ?? "HEAD"}" \u2014 failing closed`);
|
|
10446
10810
|
return 2;
|
|
10447
10811
|
}
|
|
10448
|
-
const visRoot =
|
|
10449
|
-
const visDir =
|
|
10812
|
+
const visRoot = mkdtempSync3(join17(tmpdir3(), "tw-verify-vis-"));
|
|
10813
|
+
const visDir = join17(visRoot, "t");
|
|
10450
10814
|
const cleanup = (dirs) => {
|
|
10451
10815
|
if (opts.keep) return;
|
|
10452
|
-
for (const d of dirs)
|
|
10816
|
+
for (const d of dirs) rmSync4(d, { recursive: true, force: true });
|
|
10453
10817
|
};
|
|
10454
10818
|
const protectedIgnored = (rel) => isProtected(rel, policy);
|
|
10455
10819
|
const treeBefore = treeFingerprint(cwd, protectedIgnored);
|
|
@@ -10479,14 +10843,16 @@ function runVerify(opts) {
|
|
|
10479
10843
|
stage: "visible",
|
|
10480
10844
|
...visible.resource ? { resource: visible.resource } : {},
|
|
10481
10845
|
detail: visible.reason,
|
|
10846
|
+
diagnostics: diagnosticsJson(visible.diagnostics, true),
|
|
10482
10847
|
verifier_backend: backendReport(),
|
|
10483
10848
|
oracle_assurance: oracleAssuranceReport()
|
|
10484
10849
|
}));
|
|
10485
10850
|
} else {
|
|
10486
10851
|
out2(
|
|
10487
|
-
exhausted ? "verify: isolated verifier resource envelope was exhausted during the visible stage \u2014 failing closed" : "verify:
|
|
10852
|
+
exhausted ? "verify: isolated verifier resource envelope was exhausted during the visible stage \u2014 failing closed" : "verify: verifier execution backend failed while running the visible stage \u2014 failing closed"
|
|
10488
10853
|
);
|
|
10489
10854
|
if (visible.reason) out2("verify: " + visible.reason);
|
|
10855
|
+
renderStageDiagnostics(out2, "visible", visible);
|
|
10490
10856
|
}
|
|
10491
10857
|
return 2;
|
|
10492
10858
|
}
|
|
@@ -10503,8 +10869,8 @@ function runVerify(opts) {
|
|
|
10503
10869
|
out2((dependencyAfterVisible.reason ?? "dependency identity changed") + ". Failing closed, not open.");
|
|
10504
10870
|
return 2;
|
|
10505
10871
|
}
|
|
10506
|
-
const priRoot =
|
|
10507
|
-
const priDir =
|
|
10872
|
+
const priRoot = mkdtempSync3(join17(tmpdir3(), "tw-verify-pri-"));
|
|
10873
|
+
const priDir = join17(priRoot, "t");
|
|
10508
10874
|
let restored = [];
|
|
10509
10875
|
let removedAdded = 0;
|
|
10510
10876
|
try {
|
|
@@ -10535,14 +10901,16 @@ function runVerify(opts) {
|
|
|
10535
10901
|
stage: "pristine",
|
|
10536
10902
|
...pristine.resource ? { resource: pristine.resource } : {},
|
|
10537
10903
|
detail: pristine.reason,
|
|
10904
|
+
diagnostics: diagnosticsJson(pristine.diagnostics, true),
|
|
10538
10905
|
verifier_backend: backendReport(),
|
|
10539
10906
|
oracle_assurance: oracleAssuranceReport()
|
|
10540
10907
|
}));
|
|
10541
10908
|
} else {
|
|
10542
10909
|
out2(
|
|
10543
|
-
exhausted ? "verify: isolated verifier resource envelope was exhausted during the pristine stage \u2014 failing closed" : "verify:
|
|
10910
|
+
exhausted ? "verify: isolated verifier resource envelope was exhausted during the pristine stage \u2014 failing closed" : "verify: verifier execution backend failed while running the pristine stage \u2014 failing closed"
|
|
10544
10911
|
);
|
|
10545
10912
|
if (pristine.reason) out2("verify: " + pristine.reason);
|
|
10913
|
+
renderStageDiagnostics(out2, "pristine", pristine);
|
|
10546
10914
|
}
|
|
10547
10915
|
return 2;
|
|
10548
10916
|
}
|
|
@@ -10584,8 +10952,8 @@ function runVerify(opts) {
|
|
|
10584
10952
|
base,
|
|
10585
10953
|
command: cmd,
|
|
10586
10954
|
budget_secs: budget,
|
|
10587
|
-
visible:
|
|
10588
|
-
pristine:
|
|
10955
|
+
visible: stageJson(visible),
|
|
10956
|
+
pristine: stageJson(pristine),
|
|
10589
10957
|
protected_restored: restored.length,
|
|
10590
10958
|
added_protected_removed: removedAdded,
|
|
10591
10959
|
verifier_backend: backendReport(),
|
|
@@ -10610,6 +10978,14 @@ function runVerify(opts) {
|
|
|
10610
10978
|
out2(
|
|
10611
10979
|
dependencyEnvironment ? `dependency environment: ${dependencyEnvironmentSummary(dependencyEnvironment)}` + (opts.allowDepDrift && dependencyEnvironment.status === "unattestable" ? " (operator override)" : "") : `dependency environment: verifier-owned by ${verifierBackend.image ?? "isolated image"}`
|
|
10612
10980
|
);
|
|
10981
|
+
if (verdict2 !== "VERIFIED") {
|
|
10982
|
+
if (visible.exit !== 0 || visible.failure !== void 0) {
|
|
10983
|
+
renderStageDiagnostics(out2, "visible", visible);
|
|
10984
|
+
}
|
|
10985
|
+
if (pristine.exit !== 0 || pristine.failure !== void 0) {
|
|
10986
|
+
renderStageDiagnostics(out2, "pristine", pristine);
|
|
10987
|
+
}
|
|
10988
|
+
}
|
|
10613
10989
|
if (signedOff)
|
|
10614
10990
|
out2(
|
|
10615
10991
|
`masked failure cleared by out-of-band approval (tamperward:allow:${signedOff}): a reviewer accepted that the original suite no longer applies to this change. Exit 0.`
|
|
@@ -10654,11 +11030,11 @@ function parseVerify(args) {
|
|
|
10654
11030
|
}
|
|
10655
11031
|
|
|
10656
11032
|
// src/cli/run.ts
|
|
10657
|
-
import { execFileSync as execFileSync10, spawn, spawnSync as
|
|
11033
|
+
import { execFileSync as execFileSync10, spawn, spawnSync as spawnSync4 } from "node:child_process";
|
|
10658
11034
|
import { randomBytes } from "node:crypto";
|
|
10659
|
-
import { mkdtempSync as
|
|
10660
|
-
import { tmpdir as
|
|
10661
|
-
import { join as
|
|
11035
|
+
import { mkdtempSync as mkdtempSync4, readdirSync as readdirSync5, readFileSync as readFileSync17, readlinkSync as readlinkSync4, realpathSync as realpathSync6, rmSync as rmSync5 } from "node:fs";
|
|
11036
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
11037
|
+
import { join as join18, resolve as resolve8 } from "node:path";
|
|
10662
11038
|
var out = (s) => process.stdout.write(s + "\n");
|
|
10663
11039
|
var err2 = (s) => process.stderr.write(s + "\n");
|
|
10664
11040
|
var AGENT_SUPERVISOR = String.raw`
|
|
@@ -10778,7 +11154,7 @@ function linuxDescendantPids(rootPid) {
|
|
|
10778
11154
|
for (const name of names) {
|
|
10779
11155
|
const pid = Number(name);
|
|
10780
11156
|
try {
|
|
10781
|
-
const stat =
|
|
11157
|
+
const stat = readFileSync17(`/proc/${name}/stat`, "utf8");
|
|
10782
11158
|
const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
10783
11159
|
const ppid = Number(fields[1]);
|
|
10784
11160
|
if (!Number.isFinite(ppid)) continue;
|
|
@@ -10806,7 +11182,7 @@ function killAgentTree(pid) {
|
|
|
10806
11182
|
if (!Number.isInteger(pid) || pid <= 1) return;
|
|
10807
11183
|
if (process.platform === "win32") {
|
|
10808
11184
|
try {
|
|
10809
|
-
|
|
11185
|
+
spawnSync4("taskkill", ["/pid", String(pid), "/t", "/f"], { stdio: "ignore", timeout: 5e3 });
|
|
10810
11186
|
} catch {
|
|
10811
11187
|
}
|
|
10812
11188
|
return;
|
|
@@ -10828,11 +11204,11 @@ function killAgentTree(pid) {
|
|
|
10828
11204
|
}
|
|
10829
11205
|
}
|
|
10830
11206
|
function runAgentWithBudget(argv2, cwd, budgetSecs) {
|
|
10831
|
-
const stateDir =
|
|
10832
|
-
const resultFile =
|
|
11207
|
+
const stateDir = mkdtempSync4(join18(tmpdir4(), "tw-agent-supervisor-"));
|
|
11208
|
+
const resultFile = join18(stateDir, "result.json");
|
|
10833
11209
|
let supervisorTimedOut = false;
|
|
10834
11210
|
try {
|
|
10835
|
-
const supervisor =
|
|
11211
|
+
const supervisor = spawnSync4(
|
|
10836
11212
|
process.execPath,
|
|
10837
11213
|
["-e", AGENT_SUPERVISOR, resultFile, String(budgetSecs * 1e3), ...argv2],
|
|
10838
11214
|
{
|
|
@@ -10847,7 +11223,7 @@ function runAgentWithBudget(argv2, cwd, budgetSecs) {
|
|
|
10847
11223
|
supervisorTimedOut = Boolean(supervisor.error) && supervisor.error.code === "ETIMEDOUT";
|
|
10848
11224
|
let state = {};
|
|
10849
11225
|
try {
|
|
10850
|
-
state = JSON.parse(
|
|
11226
|
+
state = JSON.parse(readFileSync17(resultFile, "utf8"));
|
|
10851
11227
|
} catch {
|
|
10852
11228
|
}
|
|
10853
11229
|
if (supervisorTimedOut || state.timedOut) {
|
|
@@ -10874,7 +11250,7 @@ function runAgentWithBudget(argv2, cwd, budgetSecs) {
|
|
|
10874
11250
|
failure: state.failure ?? "agent supervisor did not produce a final status"
|
|
10875
11251
|
};
|
|
10876
11252
|
} finally {
|
|
10877
|
-
|
|
11253
|
+
rmSync5(stateDir, { recursive: true, force: true });
|
|
10878
11254
|
}
|
|
10879
11255
|
}
|
|
10880
11256
|
var waitCell = new Int32Array(new SharedArrayBuffer(4));
|
|
@@ -10891,8 +11267,8 @@ function pidAlive2(pid) {
|
|
|
10891
11267
|
}
|
|
10892
11268
|
}
|
|
10893
11269
|
function supervisedObserverLog(cwd) {
|
|
10894
|
-
const gd = gitDir(cwd) ??
|
|
10895
|
-
return
|
|
11270
|
+
const gd = gitDir(cwd) ?? join18(cwd, ".git");
|
|
11271
|
+
return join18(
|
|
10896
11272
|
gd,
|
|
10897
11273
|
"tamperward",
|
|
10898
11274
|
`run-observer-${process.pid}-${Date.now()}-${randomBytes(4).toString("hex")}.jsonl`
|
|
@@ -11009,7 +11385,7 @@ function git3(args, cwd) {
|
|
|
11009
11385
|
}
|
|
11010
11386
|
function nowTicks() {
|
|
11011
11387
|
try {
|
|
11012
|
-
return Math.floor(parseFloat(
|
|
11388
|
+
return Math.floor(parseFloat(readFileSync17("/proc/uptime", "utf8").split(" ")[0]) * 100);
|
|
11013
11389
|
} catch {
|
|
11014
11390
|
return Number.POSITIVE_INFINITY;
|
|
11015
11391
|
}
|
|
@@ -11032,7 +11408,7 @@ function survivorsHoldingTree(cwd, spawnedAfterTicks) {
|
|
|
11032
11408
|
const n = Number(pid);
|
|
11033
11409
|
if (n === process.pid) continue;
|
|
11034
11410
|
try {
|
|
11035
|
-
const stat =
|
|
11411
|
+
const stat = readFileSync17(`/proc/${pid}/stat`, "utf8");
|
|
11036
11412
|
const startTicks = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]);
|
|
11037
11413
|
if (!Number.isFinite(startTicks) || startTicks < spawnedAfterTicks) continue;
|
|
11038
11414
|
} catch {
|
|
@@ -11137,7 +11513,7 @@ function runEnvelope(opts) {
|
|
|
11137
11513
|
const rewritesBefore = objectRewriteState(cwd);
|
|
11138
11514
|
const spawnTicks = nowTicks();
|
|
11139
11515
|
const agentRun = opts.agentBudget === void 0 ? (() => {
|
|
11140
|
-
const agent =
|
|
11516
|
+
const agent = spawnSync4(opts.argv[0], opts.argv.slice(1), { cwd, stdio: "inherit" });
|
|
11141
11517
|
return {
|
|
11142
11518
|
exit: agent.status ?? 1,
|
|
11143
11519
|
timedOut: false,
|
|
@@ -11177,7 +11553,7 @@ tamperward run \u2014 agent exit ${agentExit}; DEPENDENCY_DRIFT \u2192 ENFORCEME
|
|
|
11177
11553
|
err2("(--allow-dep-drift: proceeding anyway, on the operator's judgement.)");
|
|
11178
11554
|
}
|
|
11179
11555
|
const head = git3(["rev-parse", "HEAD"], cwd).trim();
|
|
11180
|
-
const isAncestor =
|
|
11556
|
+
const isAncestor = spawnSync4("git", ["merge-base", "--is-ancestor", base, head], { cwd, env: trustedGitEnv() });
|
|
11181
11557
|
if (isAncestor.status !== 0) {
|
|
11182
11558
|
err2(`tamperward run: HEAD ${head.slice(0, 10)} is not a descendant of the trusted base ${base.slice(0, 10)} \u2014`);
|
|
11183
11559
|
err2("the agent rewrote history out from under the anchor. That is the finding.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.16.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",
|