tamperward 2.11.0 → 2.11.2
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 +13 -3
- package/dist/cli/index.js +84 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -210,8 +210,11 @@ pinned Tamperward version, and label permissions restricted to trusted humans.
|
|
|
210
210
|
The default local verifier is checkpointed same-host execution, not OS isolation.
|
|
211
211
|
An optional digest-pinned container backend isolates final verification with no network,
|
|
212
212
|
host dependency tree, HOME, temp, credential or socket sharing. Tamperward still is not a
|
|
213
|
-
semantic-correctness oracle
|
|
214
|
-
|
|
213
|
+
semantic-correctness oracle: verifier output reports `oracle_assurance: suite-exit-only`
|
|
214
|
+
because candidate source still executes inside the configured suite process and can
|
|
215
|
+
terminate or interpose on that in-process oracle. `isolated-container` therefore means
|
|
216
|
+
execution-domain isolation, not semantic/oracle isolation. `tamperward run` also does
|
|
217
|
+
not pretend that a same-identity host agent is isolated from the Docker daemon.
|
|
215
218
|
|
|
216
219
|
Full assumptions and residual risks: [SPEC.md](./SPEC.md),
|
|
217
220
|
[SECURITY-ENVELOPE.md](./SECURITY-ENVELOPE.md), and the
|
|
@@ -259,7 +262,14 @@ verify:
|
|
|
259
262
|
local Docker daemon; Tamperward never pulls during adjudication. The materialised
|
|
260
263
|
candidate/pristine tree is mounted read-only, the image owns runtime/dependencies,
|
|
261
264
|
network is disabled, HOME/tmp are private, and optional suite output belongs in
|
|
262
|
-
`$TAMPERWARD_OUTPUT_DIR` (`/workspace-out`).
|
|
265
|
+
`$TAMPERWARD_OUTPUT_DIR` (`/workspace-out`). The isolated verifier also applies a
|
|
266
|
+
fixed host-protection envelope: **2 GiB memory, no additional swap, 2 CPUs and 256
|
|
267
|
+
PIDs**, in addition to the configured wall-clock budget. Those limits are included in
|
|
268
|
+
the JSON backend report. Docker-confirmed memory OOM is `CANNOT_VERIFY` with
|
|
269
|
+
`VERIFIER_RESOURCE_EXHAUSTED`, never a suite failure; an exit such as 137 without
|
|
270
|
+
`OOMKilled=true` remains the suite's own exit. v2.11.2 deliberately has no environment
|
|
271
|
+
variable tuning knob for these ceilings because candidate-controlled CI/env must not
|
|
272
|
+
weaken verifier containment. Images that declare Dockerfile
|
|
263
273
|
`VOLUME` paths are refused: Docker mounts those paths writable even with
|
|
264
274
|
`--read-only`, which would undermine the immutable verifier-image boundary. Image
|
|
265
275
|
`ENTRYPOINT` is also overridden; the pinned image supplies the runtime/dependencies,
|
package/dist/cli/index.js
CHANGED
|
@@ -8581,6 +8581,13 @@ import { createHash as createHash9, randomUUID } from "node:crypto";
|
|
|
8581
8581
|
import { spawnSync } from "node:child_process";
|
|
8582
8582
|
import { accessSync, constants as constants2, readFileSync as readFileSync14, realpathSync as realpathSync4, statSync as statSync5 } from "node:fs";
|
|
8583
8583
|
import { delimiter as delimiter2, dirname as dirname6, join as join14, resolve as resolve5 } from "node:path";
|
|
8584
|
+
var DEFAULT_CONTAINER_RESOURCE_LIMITS = Object.freeze({
|
|
8585
|
+
memoryBytes: 2 * 1024 * 1024 * 1024,
|
|
8586
|
+
// Docker interprets memory-swap == memory as no additional swap allowance.
|
|
8587
|
+
memorySwapBytes: 2 * 1024 * 1024 * 1024,
|
|
8588
|
+
cpus: 2,
|
|
8589
|
+
pids: 256
|
|
8590
|
+
});
|
|
8584
8591
|
var DIGEST_IMAGE = /^(?!-)[^\s@]+@sha256:[0-9a-f]{64}$/i;
|
|
8585
8592
|
var DEFAULT_DOCKER_HOST = "unix:///var/run/docker.sock";
|
|
8586
8593
|
function identity() {
|
|
@@ -8808,7 +8815,8 @@ function prepareVerifierBackend(verify) {
|
|
|
8808
8815
|
engine: "docker",
|
|
8809
8816
|
enginePath,
|
|
8810
8817
|
engineSha256: fileSha256(enginePath),
|
|
8811
|
-
daemonHost
|
|
8818
|
+
daemonHost,
|
|
8819
|
+
resources: { ...DEFAULT_CONTAINER_RESOURCE_LIMITS }
|
|
8812
8820
|
};
|
|
8813
8821
|
}
|
|
8814
8822
|
function verifierBackendReport(backend) {
|
|
@@ -8818,6 +8826,14 @@ function verifierBackendReport(backend) {
|
|
|
8818
8826
|
available: backend.available,
|
|
8819
8827
|
...backend.image ? { image: backend.image } : {},
|
|
8820
8828
|
...backend.engine ? { engine: backend.engine } : {},
|
|
8829
|
+
...backend.kind === "container" ? {
|
|
8830
|
+
resources: {
|
|
8831
|
+
memory_bytes: (backend.resources ?? DEFAULT_CONTAINER_RESOURCE_LIMITS).memoryBytes,
|
|
8832
|
+
memory_swap_bytes: (backend.resources ?? DEFAULT_CONTAINER_RESOURCE_LIMITS).memorySwapBytes,
|
|
8833
|
+
cpus: (backend.resources ?? DEFAULT_CONTAINER_RESOURCE_LIMITS).cpus,
|
|
8834
|
+
pids: (backend.resources ?? DEFAULT_CONTAINER_RESOURCE_LIMITS).pids
|
|
8835
|
+
}
|
|
8836
|
+
} : {},
|
|
8821
8837
|
...backend.reason ? { reason: backend.reason } : {}
|
|
8822
8838
|
};
|
|
8823
8839
|
}
|
|
@@ -8829,7 +8845,8 @@ function verifierBackendSummary(backend) {
|
|
|
8829
8845
|
if (!backend.available) {
|
|
8830
8846
|
return `container (isolated; unavailable: ${backend.reason ?? "unknown reason"}; image ${identity2})`;
|
|
8831
8847
|
}
|
|
8832
|
-
|
|
8848
|
+
const resources = backend.resources ?? DEFAULT_CONTAINER_RESOURCE_LIMITS;
|
|
8849
|
+
return `container (isolated via Docker; image ${identity2}; ${Math.round(resources.memoryBytes / (1024 * 1024))} MiB memory, no extra swap, ${resources.cpus} CPUs, ${resources.pids} PIDs)`;
|
|
8833
8850
|
}
|
|
8834
8851
|
function cleanupContainer(backend, name) {
|
|
8835
8852
|
if (!backend.enginePath || !backend.daemonHost || !engineStable(backend)) return;
|
|
@@ -8844,6 +8861,7 @@ function cleanupContainer(backend, name) {
|
|
|
8844
8861
|
}
|
|
8845
8862
|
}
|
|
8846
8863
|
function containerRunArgs(input) {
|
|
8864
|
+
const resources = input.resources ?? DEFAULT_CONTAINER_RESOURCE_LIMITS;
|
|
8847
8865
|
return [
|
|
8848
8866
|
"run",
|
|
8849
8867
|
"--name",
|
|
@@ -8860,8 +8878,14 @@ function containerRunArgs(input) {
|
|
|
8860
8878
|
"ALL",
|
|
8861
8879
|
"--security-opt",
|
|
8862
8880
|
"no-new-privileges",
|
|
8881
|
+
"--memory",
|
|
8882
|
+
String(resources.memoryBytes),
|
|
8883
|
+
"--memory-swap",
|
|
8884
|
+
String(resources.memorySwapBytes),
|
|
8885
|
+
"--cpus",
|
|
8886
|
+
String(resources.cpus),
|
|
8863
8887
|
"--pids-limit",
|
|
8864
|
-
|
|
8888
|
+
String(resources.pids),
|
|
8865
8889
|
"--user",
|
|
8866
8890
|
`${input.uid}:${input.gid}`,
|
|
8867
8891
|
// Frozen candidate/pristine input is immutable in the verifier. A suite
|
|
@@ -8895,6 +8919,26 @@ function containerRunArgs(input) {
|
|
|
8895
8919
|
input.command
|
|
8896
8920
|
];
|
|
8897
8921
|
}
|
|
8922
|
+
function containerStateResult(state, secs) {
|
|
8923
|
+
if (state?.OOMKilled) {
|
|
8924
|
+
return {
|
|
8925
|
+
exit: null,
|
|
8926
|
+
secs,
|
|
8927
|
+
failure: "resource",
|
|
8928
|
+
resource: "memory",
|
|
8929
|
+
reason: "verifier container exceeded its memory limit and was OOM-killed"
|
|
8930
|
+
};
|
|
8931
|
+
}
|
|
8932
|
+
if (!state || state.Status !== "exited" || typeof state.ExitCode !== "number" || (state.Error ?? "") !== "") {
|
|
8933
|
+
return {
|
|
8934
|
+
exit: null,
|
|
8935
|
+
secs,
|
|
8936
|
+
failure: "backend",
|
|
8937
|
+
reason: state?.Error ? `verifier container runtime error: ${state.Error}` : "Docker did not provide a trustworthy exited-container state"
|
|
8938
|
+
};
|
|
8939
|
+
}
|
|
8940
|
+
return { exit: state.ExitCode, secs };
|
|
8941
|
+
}
|
|
8898
8942
|
function runContainerStage(backend, dir, command, budgetSecs) {
|
|
8899
8943
|
const t0 = Date.now();
|
|
8900
8944
|
if (backend.kind !== "container" || !backend.available || !backend.image || !backend.enginePath || !backend.engineSha256 || !backend.daemonHost || !engineStable(backend)) {
|
|
@@ -8914,7 +8958,8 @@ function runContainerStage(backend, dir, command, budgetSecs) {
|
|
|
8914
8958
|
workspace: resolve5(dir),
|
|
8915
8959
|
command,
|
|
8916
8960
|
uid,
|
|
8917
|
-
gid
|
|
8961
|
+
gid,
|
|
8962
|
+
resources: backend.resources ?? DEFAULT_CONTAINER_RESOURCE_LIMITS
|
|
8918
8963
|
});
|
|
8919
8964
|
try {
|
|
8920
8965
|
const r = spawnSync(
|
|
@@ -8970,15 +9015,7 @@ function runContainerStage(backend, dir, command, budgetSecs) {
|
|
|
8970
9015
|
}
|
|
8971
9016
|
}
|
|
8972
9017
|
cleanupContainer(backend, name);
|
|
8973
|
-
|
|
8974
|
-
return {
|
|
8975
|
-
exit: null,
|
|
8976
|
-
secs,
|
|
8977
|
-
failure: "backend",
|
|
8978
|
-
reason: state?.OOMKilled ? "verifier container was OOM-killed" : state?.Error ? `verifier container runtime error: ${state.Error}` : "Docker did not provide a trustworthy exited-container state"
|
|
8979
|
-
};
|
|
8980
|
-
}
|
|
8981
|
-
return { exit: state.ExitCode, secs };
|
|
9018
|
+
return containerStateResult(state, secs);
|
|
8982
9019
|
} catch (e) {
|
|
8983
9020
|
cleanupContainer(backend, name);
|
|
8984
9021
|
return {
|
|
@@ -8991,6 +9028,14 @@ function runContainerStage(backend, dir, command, budgetSecs) {
|
|
|
8991
9028
|
}
|
|
8992
9029
|
|
|
8993
9030
|
// src/cli/verify.ts
|
|
9031
|
+
function oracleAssuranceReport() {
|
|
9032
|
+
return {
|
|
9033
|
+
level: "suite-exit-only",
|
|
9034
|
+
semantic_isolation: false,
|
|
9035
|
+
candidate_code_executes_in_oracle_process: true,
|
|
9036
|
+
limitation: "candidate source executes inside the configured suite process and may terminate or interpose on that in-process oracle; isolated-container is execution-domain isolation, not semantic/oracle isolation"
|
|
9037
|
+
};
|
|
9038
|
+
}
|
|
8994
9039
|
var OVERLAY_CLASSES = ["tests", "snapshots", "config"];
|
|
8995
9040
|
var VERIFICATION_SURFACE = [
|
|
8996
9041
|
// Python / pytest — read from the rootdir, and conftest at any depth
|
|
@@ -9398,7 +9443,8 @@ function runVerify(opts) {
|
|
|
9398
9443
|
out2(JSON.stringify({
|
|
9399
9444
|
verdict: "CANNOT_VERIFY",
|
|
9400
9445
|
reason: "VERIFIER_BACKEND_UNAVAILABLE",
|
|
9401
|
-
verifier_backend: backendReport()
|
|
9446
|
+
verifier_backend: backendReport(),
|
|
9447
|
+
oracle_assurance: oracleAssuranceReport()
|
|
9402
9448
|
}));
|
|
9403
9449
|
} else {
|
|
9404
9450
|
out2("verify: isolated verifier backend is unavailable \u2014 failing closed");
|
|
@@ -9419,7 +9465,8 @@ function runVerify(opts) {
|
|
|
9419
9465
|
verdict: "CANNOT_VERIFY",
|
|
9420
9466
|
reason: "DEPENDENCY_ENVIRONMENT_UNATTESTABLE",
|
|
9421
9467
|
verifier_backend: backendReport(),
|
|
9422
|
-
dependency_environment: dependencyReport()
|
|
9468
|
+
dependency_environment: dependencyReport(),
|
|
9469
|
+
oracle_assurance: oracleAssuranceReport()
|
|
9423
9470
|
}));
|
|
9424
9471
|
} else {
|
|
9425
9472
|
out2("verify: dependency environment is not attestable \u2014 failing closed");
|
|
@@ -9461,18 +9508,23 @@ function runVerify(opts) {
|
|
|
9461
9508
|
return 2;
|
|
9462
9509
|
}
|
|
9463
9510
|
const visible = runStage(visDir);
|
|
9464
|
-
if (visible.failure === "backend") {
|
|
9511
|
+
if (visible.failure === "backend" || visible.failure === "resource") {
|
|
9465
9512
|
cleanup([visRoot]);
|
|
9513
|
+
const exhausted = visible.failure === "resource";
|
|
9466
9514
|
if (opts.json) {
|
|
9467
9515
|
out2(JSON.stringify({
|
|
9468
9516
|
verdict: "CANNOT_VERIFY",
|
|
9469
|
-
reason: "VERIFIER_BACKEND_RUNTIME_FAILURE",
|
|
9517
|
+
reason: exhausted ? "VERIFIER_RESOURCE_EXHAUSTED" : "VERIFIER_BACKEND_RUNTIME_FAILURE",
|
|
9470
9518
|
stage: "visible",
|
|
9519
|
+
...visible.resource ? { resource: visible.resource } : {},
|
|
9471
9520
|
detail: visible.reason,
|
|
9472
|
-
verifier_backend: backendReport()
|
|
9521
|
+
verifier_backend: backendReport(),
|
|
9522
|
+
oracle_assurance: oracleAssuranceReport()
|
|
9473
9523
|
}));
|
|
9474
9524
|
} else {
|
|
9475
|
-
out2(
|
|
9525
|
+
out2(
|
|
9526
|
+
exhausted ? "verify: isolated verifier resource envelope was exhausted during the visible stage \u2014 failing closed" : "verify: isolated verifier backend failed while running the visible stage \u2014 failing closed"
|
|
9527
|
+
);
|
|
9476
9528
|
if (visible.reason) out2("verify: " + visible.reason);
|
|
9477
9529
|
}
|
|
9478
9530
|
return 2;
|
|
@@ -9512,18 +9564,23 @@ function runVerify(opts) {
|
|
|
9512
9564
|
}
|
|
9513
9565
|
const overlayBefore = overlayDigest(priDir, restored);
|
|
9514
9566
|
const pristine = runStage(priDir);
|
|
9515
|
-
if (pristine.failure === "backend") {
|
|
9567
|
+
if (pristine.failure === "backend" || pristine.failure === "resource") {
|
|
9516
9568
|
cleanup([visRoot, priRoot]);
|
|
9569
|
+
const exhausted = pristine.failure === "resource";
|
|
9517
9570
|
if (opts.json) {
|
|
9518
9571
|
out2(JSON.stringify({
|
|
9519
9572
|
verdict: "CANNOT_VERIFY",
|
|
9520
|
-
reason: "VERIFIER_BACKEND_RUNTIME_FAILURE",
|
|
9573
|
+
reason: exhausted ? "VERIFIER_RESOURCE_EXHAUSTED" : "VERIFIER_BACKEND_RUNTIME_FAILURE",
|
|
9521
9574
|
stage: "pristine",
|
|
9575
|
+
...pristine.resource ? { resource: pristine.resource } : {},
|
|
9522
9576
|
detail: pristine.reason,
|
|
9523
|
-
verifier_backend: backendReport()
|
|
9577
|
+
verifier_backend: backendReport(),
|
|
9578
|
+
oracle_assurance: oracleAssuranceReport()
|
|
9524
9579
|
}));
|
|
9525
9580
|
} else {
|
|
9526
|
-
out2(
|
|
9581
|
+
out2(
|
|
9582
|
+
exhausted ? "verify: isolated verifier resource envelope was exhausted during the pristine stage \u2014 failing closed" : "verify: isolated verifier backend failed while running the pristine stage \u2014 failing closed"
|
|
9583
|
+
);
|
|
9527
9584
|
if (pristine.reason) out2("verify: " + pristine.reason);
|
|
9528
9585
|
}
|
|
9529
9586
|
return 2;
|
|
@@ -9572,6 +9629,7 @@ function runVerify(opts) {
|
|
|
9572
9629
|
added_protected_removed: removedAdded,
|
|
9573
9630
|
verifier_backend: backendReport(),
|
|
9574
9631
|
dependency_environment: dependencyReport(),
|
|
9632
|
+
oracle_assurance: oracleAssuranceReport(),
|
|
9575
9633
|
...signedOff ? { oob_signoff: signedOff } : {},
|
|
9576
9634
|
...opts.keep ? { visible_dir: visDir, pristine_dir: priDir } : {}
|
|
9577
9635
|
})
|
|
@@ -9585,6 +9643,9 @@ function runVerify(opts) {
|
|
|
9585
9643
|
};
|
|
9586
9644
|
out2(`tamperward verify \u2014 ${lines[verdict2]}`);
|
|
9587
9645
|
out2(`verifier backend: ${verifierBackendSummary(verifierBackend)}`);
|
|
9646
|
+
out2(
|
|
9647
|
+
"oracle assurance: suite-exit-only (candidate source executes inside the suite process; execution-domain isolation is not semantic/oracle isolation)"
|
|
9648
|
+
);
|
|
9588
9649
|
out2(
|
|
9589
9650
|
dependencyEnvironment ? `dependency environment: ${dependencyEnvironmentSummary(dependencyEnvironment)}` + (opts.allowDepDrift && dependencyEnvironment.status === "unattestable" ? " (operator override)" : "") : `dependency environment: verifier-owned by ${verifierBackend.image ?? "isolated image"}`
|
|
9590
9651
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.2",
|
|
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",
|