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 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/index.ts`,
340
- `parseVerify`, `parseRun`). `check` reports an unknown flag on stderr; the other
341
- commands ignore what they do not know.
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 code = errCode(e);
312
- if (code === "ENOENT" || code === "ENOTDIR") return { kind: "absent", content: null, ...NO_META, detail: "" };
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 code = errCode(e);
324
- if (code === "ENOENT" || code === "ENOTDIR") return { kind: "absent", content: null, ...NO_META, detail: "" };
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 code = errCode(e);
338
- if (code === "ENOENT" || code === "ENOTDIR") return { kind: "absent", content: null, ...NO_META, detail: "" };
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 code = files.filter((f) => langOf(f) !== null);
2673
- return code.length ? code : CODE_PROBES;
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, code, on) {
6539
- return on ? code + s + RESET : s;
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 code = r.error.code;
9321
- return code === "ETIMEDOUT" ? { exit: null, secs, failure: "budget", reason: "verifier stage exceeded its budget" } : { exit: null, secs, failure: "backend", reason: `Docker client failed: ${r.error.message}` };
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 code;
9945
+ let code2;
9946
9946
  if (visible.exit === null || pristine.exit === null) {
9947
9947
  verdict2 = "BUDGET_EXCEEDED";
9948
- code = 2;
9948
+ code2 = 2;
9949
9949
  } else if (visible.exit === 0 && pristine.exit === 0) {
9950
9950
  verdict2 = "VERIFIED";
9951
- code = 0;
9951
+ code2 = 0;
9952
9952
  } else if (visible.exit === 0) {
9953
9953
  verdict2 = "MASKED_FAILURE";
9954
- code = 1;
9954
+ code2 = 1;
9955
9955
  } else {
9956
9956
  verdict2 = "SUITE_RED";
9957
- code = 1;
9957
+ code2 = 1;
9958
9958
  }
9959
9959
  const signedOff = verdict2 === "MASKED_FAILURE" ? oobToken("verify", oobFromEnv(), oobHeadFromEnv()) : null;
9960
- if (signedOff) code = 0;
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 code;
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
- if (argv[0] === "watch") {
10698
- runWatch(argv.slice(1));
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.0",
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",