tamperward 2.13.0 → 2.13.1
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 +7 -3
- package/dist/cli/index.js +168 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -336,9 +336,13 @@ from trusted CI, or after an externally isolated agent hands off the frozen cand
|
|
|
336
336
|
|
|
337
337
|
### CLI reference
|
|
338
338
|
|
|
339
|
-
Every flag below is what the command's parser actually reads (`src/cli/
|
|
340
|
-
`parseVerify`, `parseRun`).
|
|
341
|
-
|
|
339
|
+
Every flag below is what the command's parser actually reads (`src/cli/main.ts`,
|
|
340
|
+
`parseVerify`, `parseRun`). Since **2.13.1**, user-supplied argv is validated before
|
|
341
|
+
any command-specific parser or side effect runs: unknown options, missing values,
|
|
342
|
+
invalid numeric budgets/timeouts, duplicate/conflicting `check` views, and unexpected
|
|
343
|
+
positionals fail closed with exit 2 and one deterministic `tamperward: ...` diagnostic.
|
|
344
|
+
`run` requires an explicit `--` before the wrapped command, so a typoed TamperWard
|
|
345
|
+
option can never be reinterpreted as the agent command.
|
|
342
346
|
|
|
343
347
|
| command | flags |
|
|
344
348
|
| --- | --- |
|
package/dist/cli/index.js
CHANGED
|
@@ -308,8 +308,8 @@ function inspectOnce(abs2) {
|
|
|
308
308
|
try {
|
|
309
309
|
st = lstatSync(abs2);
|
|
310
310
|
} catch (e) {
|
|
311
|
-
const
|
|
312
|
-
if (
|
|
311
|
+
const code2 = errCode(e);
|
|
312
|
+
if (code2 === "ENOENT" || code2 === "ENOTDIR") return { kind: "absent", content: null, ...NO_META, detail: "" };
|
|
313
313
|
return { kind: "unreadable", content: null, ...NO_META, detail: errText(e) };
|
|
314
314
|
}
|
|
315
315
|
if (st.isSymbolicLink() || !st.isFile()) return classify(abs2, st);
|
|
@@ -320,8 +320,8 @@ function inspectPath(abs2) {
|
|
|
320
320
|
try {
|
|
321
321
|
st = lstatSync(abs2);
|
|
322
322
|
} catch (e) {
|
|
323
|
-
const
|
|
324
|
-
if (
|
|
323
|
+
const code2 = errCode(e);
|
|
324
|
+
if (code2 === "ENOENT" || code2 === "ENOTDIR") return { kind: "absent", content: null, ...NO_META, detail: "" };
|
|
325
325
|
return { kind: "unreadable", content: null, ...NO_META, detail: errText(e) };
|
|
326
326
|
}
|
|
327
327
|
return classify(abs2, st);
|
|
@@ -334,8 +334,8 @@ function inspectResolved(abs2) {
|
|
|
334
334
|
try {
|
|
335
335
|
real = realpathSync(abs2);
|
|
336
336
|
} catch (e) {
|
|
337
|
-
const
|
|
338
|
-
if (
|
|
337
|
+
const code2 = errCode(e);
|
|
338
|
+
if (code2 === "ENOENT" || code2 === "ENOTDIR") return { kind: "absent", content: null, ...NO_META, detail: "" };
|
|
339
339
|
return { kind: "unreadable", content: null, ...NO_META, detail: errText(e) };
|
|
340
340
|
}
|
|
341
341
|
return inspectPath(real);
|
|
@@ -2669,8 +2669,8 @@ var CODE_PROBES = ["src/index.ts", "src/index.js", "lib/index.js", "index.ts", "
|
|
|
2669
2669
|
function sourceProbes(ctx) {
|
|
2670
2670
|
const files = trackedFiles(ctx);
|
|
2671
2671
|
if (!files) return CODE_PROBES;
|
|
2672
|
-
const
|
|
2673
|
-
return
|
|
2672
|
+
const code2 = files.filter((f) => langOf(f) !== null);
|
|
2673
|
+
return code2.length ? code2 : CODE_PROBES;
|
|
2674
2674
|
}
|
|
2675
2675
|
function triggerNarrowings(before, after, opts) {
|
|
2676
2676
|
const out2 = [];
|
|
@@ -6535,8 +6535,8 @@ function terminalWidth(stream = process.stdout) {
|
|
|
6535
6535
|
if (!c || c < 40) return 80;
|
|
6536
6536
|
return Math.min(c, 100);
|
|
6537
6537
|
}
|
|
6538
|
-
function paint(s,
|
|
6539
|
-
return on ?
|
|
6538
|
+
function paint(s, code2, on) {
|
|
6539
|
+
return on ? code2 + s + RESET : s;
|
|
6540
6540
|
}
|
|
6541
6541
|
function wrap(text, width) {
|
|
6542
6542
|
const out2 = [];
|
|
@@ -9317,8 +9317,8 @@ function runContainerStage(backend, dir, command, budgetSecs) {
|
|
|
9317
9317
|
const secs = Math.round((Date.now() - t0) / 1e3);
|
|
9318
9318
|
if (r.error) {
|
|
9319
9319
|
cleanupContainer(backend, name);
|
|
9320
|
-
const
|
|
9321
|
-
return
|
|
9320
|
+
const code2 = r.error.code;
|
|
9321
|
+
return code2 === "ETIMEDOUT" ? { exit: null, secs, failure: "budget", reason: "verifier stage exceeded its budget" } : { exit: null, secs, failure: "backend", reason: `Docker client failed: ${r.error.message}` };
|
|
9322
9322
|
}
|
|
9323
9323
|
if (r.status === null) {
|
|
9324
9324
|
cleanupContainer(backend, name);
|
|
@@ -9942,22 +9942,22 @@ function runVerify(opts) {
|
|
|
9942
9942
|
return 2;
|
|
9943
9943
|
}
|
|
9944
9944
|
let verdict2;
|
|
9945
|
-
let
|
|
9945
|
+
let code2;
|
|
9946
9946
|
if (visible.exit === null || pristine.exit === null) {
|
|
9947
9947
|
verdict2 = "BUDGET_EXCEEDED";
|
|
9948
|
-
|
|
9948
|
+
code2 = 2;
|
|
9949
9949
|
} else if (visible.exit === 0 && pristine.exit === 0) {
|
|
9950
9950
|
verdict2 = "VERIFIED";
|
|
9951
|
-
|
|
9951
|
+
code2 = 0;
|
|
9952
9952
|
} else if (visible.exit === 0) {
|
|
9953
9953
|
verdict2 = "MASKED_FAILURE";
|
|
9954
|
-
|
|
9954
|
+
code2 = 1;
|
|
9955
9955
|
} else {
|
|
9956
9956
|
verdict2 = "SUITE_RED";
|
|
9957
|
-
|
|
9957
|
+
code2 = 1;
|
|
9958
9958
|
}
|
|
9959
9959
|
const signedOff = verdict2 === "MASKED_FAILURE" ? oobToken("verify", oobFromEnv(), oobHeadFromEnv()) : null;
|
|
9960
|
-
if (signedOff)
|
|
9960
|
+
if (signedOff) code2 = 0;
|
|
9961
9961
|
if (opts.json) {
|
|
9962
9962
|
out2(
|
|
9963
9963
|
JSON.stringify({
|
|
@@ -10000,7 +10000,7 @@ function runVerify(opts) {
|
|
|
10000
10000
|
`(${removedAdded} protected file(s) added since ${base.slice(0, 10)} were removed from the pristine run: the pristine tree carries exactly the base's protected surface.)`
|
|
10001
10001
|
);
|
|
10002
10002
|
}
|
|
10003
|
-
return
|
|
10003
|
+
return code2;
|
|
10004
10004
|
}
|
|
10005
10005
|
function parseVerify(args) {
|
|
10006
10006
|
const o = {};
|
|
@@ -10567,17 +10567,148 @@ function parseCheck(args) {
|
|
|
10567
10567
|
else if (a === "--format") {
|
|
10568
10568
|
const v = args[++i];
|
|
10569
10569
|
if (v !== void 0 && isFormat(v)) o.format = v;
|
|
10570
|
-
else process.stderr.write(`tamperward: unknown --format "${v ?? ""}" (expected ${FORMATS.join(" | ")})
|
|
10571
|
-
`);
|
|
10572
10570
|
} else if (a === "--diff") o.diff = args[++i];
|
|
10573
10571
|
else if (a === "--cwd") o.cwd = args[++i];
|
|
10574
|
-
else {
|
|
10575
|
-
process.stderr.write(`tamperward: unknown flag "${a}"
|
|
10576
|
-
`);
|
|
10577
|
-
}
|
|
10578
10572
|
}
|
|
10579
10573
|
return o;
|
|
10580
10574
|
}
|
|
10575
|
+
function validateFlatArgs(args, grammar) {
|
|
10576
|
+
const flags = new Set(grammar.flags ?? []);
|
|
10577
|
+
const values = grammar.values ?? {};
|
|
10578
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10579
|
+
const positionals = [];
|
|
10580
|
+
for (let i = 0; i < args.length; i++) {
|
|
10581
|
+
const a = args[i];
|
|
10582
|
+
if (flags.has(a) || Object.prototype.hasOwnProperty.call(values, a)) {
|
|
10583
|
+
if (seen.has(a)) {
|
|
10584
|
+
return { error: `option "${a}" specified more than once`, seen, positionals };
|
|
10585
|
+
}
|
|
10586
|
+
seen.add(a);
|
|
10587
|
+
const rule = values[a];
|
|
10588
|
+
if (!rule) continue;
|
|
10589
|
+
const v = args[i + 1];
|
|
10590
|
+
if (v === void 0 || v.startsWith("--")) {
|
|
10591
|
+
return {
|
|
10592
|
+
error: `${a} needs a value` + (v === void 0 ? "" : ` (got the flag "${v}")`),
|
|
10593
|
+
seen,
|
|
10594
|
+
positionals
|
|
10595
|
+
};
|
|
10596
|
+
}
|
|
10597
|
+
i++;
|
|
10598
|
+
if (rule === "positive") {
|
|
10599
|
+
const n = Number(v);
|
|
10600
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
10601
|
+
return { error: `${a} needs a positive number (got "${v}")`, seen, positionals };
|
|
10602
|
+
}
|
|
10603
|
+
} else if (rule === "non-negative") {
|
|
10604
|
+
const n = Number(v);
|
|
10605
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
10606
|
+
return { error: `${a} needs a non-negative number (got "${v}")`, seen, positionals };
|
|
10607
|
+
}
|
|
10608
|
+
} else if (rule === "format" && !isFormat(v)) {
|
|
10609
|
+
return {
|
|
10610
|
+
error: `--format needs one of ${FORMATS.join(" | ")} (got "${v}")`,
|
|
10611
|
+
seen,
|
|
10612
|
+
positionals
|
|
10613
|
+
};
|
|
10614
|
+
}
|
|
10615
|
+
continue;
|
|
10616
|
+
}
|
|
10617
|
+
if (a.startsWith("-")) {
|
|
10618
|
+
return { error: `unknown option "${a}"`, seen, positionals };
|
|
10619
|
+
}
|
|
10620
|
+
if (grammar.positional === "one" && positionals.length === 0) {
|
|
10621
|
+
positionals.push(a);
|
|
10622
|
+
continue;
|
|
10623
|
+
}
|
|
10624
|
+
return { error: `unexpected argument "${a}"`, seen, positionals };
|
|
10625
|
+
}
|
|
10626
|
+
return { seen, positionals };
|
|
10627
|
+
}
|
|
10628
|
+
function validateCliArgs(cmd, args) {
|
|
10629
|
+
if (cmd === "hook" || cmd === "sweep") {
|
|
10630
|
+
if (args.length === 0) return `${cmd} requires an agent name`;
|
|
10631
|
+
if (args.length > 1) return `unexpected argument "${args[1]}"`;
|
|
10632
|
+
return void 0;
|
|
10633
|
+
}
|
|
10634
|
+
if (cmd === "run") {
|
|
10635
|
+
const delimiter3 = args.indexOf("--");
|
|
10636
|
+
if (delimiter3 < 0) return 'run requires an explicit "--" before the wrapped command';
|
|
10637
|
+
const prefix = args.slice(0, delimiter3);
|
|
10638
|
+
const command = args.slice(delimiter3 + 1);
|
|
10639
|
+
const parsed = validateFlatArgs(prefix, {
|
|
10640
|
+
flags: ["--allow-dirty", "--allow-dep-drift"],
|
|
10641
|
+
values: {
|
|
10642
|
+
"--base": "string",
|
|
10643
|
+
"--cmd": "string",
|
|
10644
|
+
"--budget": "positive",
|
|
10645
|
+
"--agent-budget": "positive",
|
|
10646
|
+
"--settle": "non-negative",
|
|
10647
|
+
"--cwd": "string"
|
|
10648
|
+
}
|
|
10649
|
+
});
|
|
10650
|
+
if (parsed.error) return parsed.error;
|
|
10651
|
+
if (command.length === 0) return 'run requires a wrapped command after "--"';
|
|
10652
|
+
return void 0;
|
|
10653
|
+
}
|
|
10654
|
+
if (cmd === "check") {
|
|
10655
|
+
const parsed = validateFlatArgs(args, {
|
|
10656
|
+
flags: ["--staged", "--worktree", "--json"],
|
|
10657
|
+
values: { "--diff": "string", "--format": "format", "--cwd": "string" }
|
|
10658
|
+
});
|
|
10659
|
+
if (parsed.error) return parsed.error;
|
|
10660
|
+
const views = ["--staged", "--worktree", "--diff"].filter((x) => parsed.seen.has(x));
|
|
10661
|
+
if (views.length > 1) return "choose exactly one of --staged, --worktree, or --diff";
|
|
10662
|
+
if (parsed.seen.has("--json") && parsed.seen.has("--format")) {
|
|
10663
|
+
return "--json cannot be combined with --format";
|
|
10664
|
+
}
|
|
10665
|
+
return void 0;
|
|
10666
|
+
}
|
|
10667
|
+
if (cmd === "allow") {
|
|
10668
|
+
const parsed = validateFlatArgs(args, {
|
|
10669
|
+
values: { "--file": "string", "--reason": "string", "--cwd": "string" },
|
|
10670
|
+
positional: "one"
|
|
10671
|
+
});
|
|
10672
|
+
if (parsed.error) return parsed.error;
|
|
10673
|
+
if (parsed.positionals.length === 0) return "allow requires a rule";
|
|
10674
|
+
return void 0;
|
|
10675
|
+
}
|
|
10676
|
+
if (cmd === "init") {
|
|
10677
|
+
return validateFlatArgs(args, {
|
|
10678
|
+
flags: ["--dry-run", "--force-workflow"],
|
|
10679
|
+
values: { "--cwd": "string" }
|
|
10680
|
+
}).error;
|
|
10681
|
+
}
|
|
10682
|
+
if (cmd === "doctor") {
|
|
10683
|
+
return validateFlatArgs(args, {
|
|
10684
|
+
flags: ["--github"],
|
|
10685
|
+
values: {
|
|
10686
|
+
"--cwd": "string",
|
|
10687
|
+
"--base": "string",
|
|
10688
|
+
"--workflow": "string",
|
|
10689
|
+
"--repo": "string",
|
|
10690
|
+
"--branch": "string"
|
|
10691
|
+
}
|
|
10692
|
+
}).error;
|
|
10693
|
+
}
|
|
10694
|
+
if (cmd === "verify") {
|
|
10695
|
+
return validateFlatArgs(args, {
|
|
10696
|
+
flags: ["--json", "--keep", "--require-ancestor"],
|
|
10697
|
+
values: {
|
|
10698
|
+
"--base": "string",
|
|
10699
|
+
"--cmd": "string",
|
|
10700
|
+
"--cwd": "string",
|
|
10701
|
+
"--budget": "positive"
|
|
10702
|
+
}
|
|
10703
|
+
}).error;
|
|
10704
|
+
}
|
|
10705
|
+
if (cmd === "watch") {
|
|
10706
|
+
return validateFlatArgs(args, {
|
|
10707
|
+
values: { "--dir": "string", "--log": "string" }
|
|
10708
|
+
}).error;
|
|
10709
|
+
}
|
|
10710
|
+
return void 0;
|
|
10711
|
+
}
|
|
10581
10712
|
function printHelp() {
|
|
10582
10713
|
process.stdout.write(`tamperward \u2014 the deterministic agent-integrity gate
|
|
10583
10714
|
|
|
@@ -10652,6 +10783,14 @@ Exit codes: 0 clean \xB7 1 a blocking finding (check), MASKED_FAILURE or SUITE_R
|
|
|
10652
10783
|
}
|
|
10653
10784
|
function main(argv2) {
|
|
10654
10785
|
const [cmd, ...rest] = argv2;
|
|
10786
|
+
if (cmd !== void 0 && cmd !== "-h" && cmd !== "--help") {
|
|
10787
|
+
const invalid = validateCliArgs(cmd, rest);
|
|
10788
|
+
if (invalid) {
|
|
10789
|
+
process.stderr.write(`tamperward: ${invalid}
|
|
10790
|
+
`);
|
|
10791
|
+
return 2;
|
|
10792
|
+
}
|
|
10793
|
+
}
|
|
10655
10794
|
switch (cmd) {
|
|
10656
10795
|
case "check":
|
|
10657
10796
|
return runCheck(parseCheck(rest));
|
|
@@ -10665,6 +10804,8 @@ function main(argv2) {
|
|
|
10665
10804
|
return runInit(parseInit(rest));
|
|
10666
10805
|
case "doctor":
|
|
10667
10806
|
return runDoctor(parseDoctor(rest));
|
|
10807
|
+
case "watch":
|
|
10808
|
+
return runWatch(rest);
|
|
10668
10809
|
case "verify":
|
|
10669
10810
|
return runVerify(parseVerify(rest));
|
|
10670
10811
|
case "run":
|
|
@@ -10694,8 +10835,5 @@ function guardedMain(argv2, stderr = process.stderr) {
|
|
|
10694
10835
|
|
|
10695
10836
|
// src/cli/index.ts
|
|
10696
10837
|
var argv = process.argv.slice(2);
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
} else {
|
|
10700
|
-
process.exit(guardedMain(argv));
|
|
10701
|
-
}
|
|
10838
|
+
var code = guardedMain(argv);
|
|
10839
|
+
if (code >= 0) process.exit(code);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.1",
|
|
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",
|