tamperward 1.10.1 → 1.10.3

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.
Files changed (2) hide show
  1. package/dist/cli/index.js +62 -16
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -272,6 +272,19 @@ function diffWorktree(opts = {}) {
272
272
  (c) => enrich(c, (p) => blobAt("HEAD", p, opts.cwd), (p) => fromDisk(p, opts.cwd))
273
273
  );
274
274
  }
275
+ function diffWorktreeWithUntracked(opts = {}) {
276
+ const tracked = diffWorktree(opts);
277
+ const others = git(["ls-files", "--others", "--exclude-standard", "-z"], opts.cwd).split("\0").filter(Boolean);
278
+ const seen = new Set(tracked.filter((c) => c.kind === "file").map((c) => c.path));
279
+ const untracked = others.filter((rel) => !seen.has(rel)).map(
280
+ (rel) => enrich(
281
+ { kind: "file", path: rel, oldPath: null, op: "add", before: null, after: null, binary: false, hunks: [] },
282
+ () => null,
283
+ (p) => fromDisk(p, opts.cwd)
284
+ )
285
+ );
286
+ return [...tracked, ...untracked];
287
+ }
275
288
 
276
289
  // src/detectors/finding.ts
277
290
  function severityOf(rule, policy, fallback = "block") {
@@ -316,7 +329,16 @@ var LITERAL = [
316
329
  { re: /(?:^|\s)--no-verify(?:\s|$)/, why: "--no-verify skips the pre-commit/pre-push hooks" },
317
330
  { re: /(?:^|\s)--no-hooks(?:\s|$)/, why: "--no-hooks disables hook execution" },
318
331
  { re: /(?:^|[\s;&|=])HUSKY=0(?:\s|$)/, why: "HUSKY=0 disables all Husky hooks" },
319
- { re: /\bHUSKY_SKIP_HOOKS=1\b/, why: "HUSKY_SKIP_HOOKS=1 skips Husky hooks" }
332
+ { re: /\bHUSKY_SKIP_HOOKS=1\b/, why: "HUSKY_SKIP_HOOKS=1 skips Husky hooks" },
333
+ // git-native hook disabling. core.hooksPath is strictly MORE general than the
334
+ // flags above: it turns off husky, lefthook, plain .git/hooks and pre-commit
335
+ // alike, and it was reachable in three spellings the literal list missed
336
+ // (-c, `git config`, and GIT_CONFIG_* env injection). (P1-9, external review.)
337
+ { re: /\bcore\.hooksPath\b/i, why: "core.hooksPath redirects git away from the installed hooks" },
338
+ { re: /\bGIT_CONFIG_KEY_\d+\s*=\s*["']?core\.hooksPath/i, why: "GIT_CONFIG_* injection sets core.hooksPath" },
339
+ { re: /(?:^|[\s;&|=])LEFTHOOK=0(?:\s|$)/, why: "LEFTHOOK=0 disables Lefthook hooks" },
340
+ { re: /\bSKIP_SIMPLE_GIT_HOOKS=1\b/, why: "SKIP_SIMPLE_GIT_HOOKS=1 skips simple-git-hooks" },
341
+ { re: /\bPRE_COMMIT_ALLOW_NO_CONFIG=1\b/, why: "PRE_COMMIT_ALLOW_NO_CONFIG=1 lets pre-commit no-op" }
320
342
  ];
321
343
  var SHORT_N = /^-[a-z]*n[a-z]*$/i;
322
344
  var noVerify = {
@@ -867,6 +889,9 @@ var CHECK = /\b(test|tests|lint|typecheck|type-check|tsc|eslint|jest|vitest|play
867
889
  var YAML_KEY = /^\s*-?\s*[A-Za-z_][\w-]*:\s*(?:$|\S)/;
868
890
  var INVOKES_TOOL = /(?:^\s*|[;&|`]\s*|\$\(\s*|\b(?:npx|yarn|pnpm|bunx?)\s+)(?:jest|vitest|eslint|tsc|playwright|pytest|tamperward)\b/;
869
891
  var INVOKES_SCRIPT = /\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|tests|lint|typecheck|type-check|coverage)\b|\b(?:make|cargo|go)\s+test\b|\bgradle\w*\s+(?:test|check)\b/;
892
+ function isActiveWorkflow(path) {
893
+ return /(^|\/)\.github\/workflows\/[^/]+\.ya?ml$/.test(path);
894
+ }
870
895
  var invokesCheck = (line) => INVOKES_TOOL.test(line) || INVOKES_SCRIPT.test(line);
871
896
  function commandCore(line) {
872
897
  return line.trim().replace(/^-\s*/, "").replace(/^(?:run|uses):\s*/, "").replace(/\s+/g, " ").trim();
@@ -897,6 +922,17 @@ var ciTampering = {
897
922
  const out2 = [];
898
923
  for (const c of changes) {
899
924
  if (c.kind !== "file") continue;
925
+ if (c.op === "rename" && c.oldPath && isActiveWorkflow(c.oldPath) && !isActiveWorkflow(c.path)) {
926
+ out2.push(
927
+ makeFinding(RULE5, policy, {
928
+ file: c.path,
929
+ message: `A CI workflow was renamed so it no longer runs (${c.oldPath} \u2192 ${c.path}).`,
930
+ evidence: `${c.oldPath} \u2192 ${c.path}`,
931
+ remediation: "Restore the path and extension. GitHub runs only *.yml/*.yaml directly under .github/workflows \u2014 renaming outside that disables the checks as surely as deleting them."
932
+ })
933
+ );
934
+ continue;
935
+ }
900
936
  if (!isProtected(c.path, policy, "ci")) continue;
901
937
  for (const l of addedLines(c)) {
902
938
  const coe = l.content.match(/^\s*-?\s*continue-on-error:\s*(.+?)\s*$/);
@@ -1247,7 +1283,7 @@ var testDeletion = {
1247
1283
  remediation: "Restoring the path. Renaming a spec out of the glob silently removes it from the suite."
1248
1284
  })
1249
1285
  );
1250
- } else if (c.op === "modify" && isTest && c.before != null && c.after != null) {
1286
+ } else if ((c.op === "modify" || c.op === "rename") && isTest && c.before != null && c.after != null) {
1251
1287
  const before = countTestBlocks(c.before);
1252
1288
  const after = countTestBlocks(c.after);
1253
1289
  if (after < before) {
@@ -1326,7 +1362,7 @@ var testContentRemoval = {
1326
1362
  }
1327
1363
  }
1328
1364
  for (const c of changes) {
1329
- if (c.kind !== "file" || c.op !== "modify" || c.before == null || c.after == null) continue;
1365
+ if (c.kind !== "file" || c.op !== "modify" && c.op !== "rename" || c.before == null || c.after == null) continue;
1330
1366
  if (!isProtected(c.path, policy, "tests")) continue;
1331
1367
  if (isProtected(c.path, policy, "snapshots")) continue;
1332
1368
  if (countTestBlocks(c.after) < countTestBlocks(c.before)) continue;
@@ -1652,8 +1688,17 @@ function wrap(text, width) {
1652
1688
  if (cur) out2.push(cur);
1653
1689
  return out2.length ? out2 : [""];
1654
1690
  }
1691
+ function stripControl(v) {
1692
+ let outStr = "";
1693
+ for (const ch of v) {
1694
+ const cp = ch.codePointAt(0) ?? 0;
1695
+ const control = cp < 32 && cp !== 9 && cp !== 10 && cp !== 13 || cp === 127 || cp >= 128 && cp <= 159;
1696
+ outStr += control ? "\uFFFD" : ch;
1697
+ }
1698
+ return outStr;
1699
+ }
1655
1700
  function clip(s, max) {
1656
- const one = s.replace(/\s+/g, " ").trim();
1701
+ const one = stripControl(s).replace(/\s+/g, " ").trim();
1657
1702
  return one.length > max ? one.slice(0, Math.max(1, max - 1)) + "\u2026" : one;
1658
1703
  }
1659
1704
  function orderFindings(findings) {
@@ -1840,7 +1885,7 @@ function report(input) {
1840
1885
 
1841
1886
  // src/cli/check.ts
1842
1887
  function check(opts) {
1843
- let policy = loadPolicy(opts.cwd);
1888
+ let policy = opts.policyOverride ?? loadPolicy(opts.cwd);
1844
1889
  const cwd = opts.cwd ?? process.cwd();
1845
1890
  let changes;
1846
1891
  let layer;
@@ -1850,8 +1895,8 @@ function check(opts) {
1850
1895
  layer = "local";
1851
1896
  view = "staged";
1852
1897
  } else if (opts.worktree) {
1853
- changes = diffWorktree({ cwd: opts.cwd });
1854
- layer = "local";
1898
+ changes = opts.includeUntracked ? diffWorktreeWithUntracked({ cwd: opts.cwd }) : diffWorktree({ cwd: opts.cwd });
1899
+ layer = opts.ciLayer ? "ci" : "local";
1855
1900
  view = "worktree";
1856
1901
  } else if (opts.diff) {
1857
1902
  const [base, head] = opts.diff.split(/\.{2,3}/);
@@ -1863,7 +1908,7 @@ function check(opts) {
1863
1908
  changes = diffRange(base, head, { cwd: opts.cwd });
1864
1909
  layer = "ci";
1865
1910
  view = "range";
1866
- policy = loadPolicyAt(mergeBaseOf(base, head, { cwd: opts.cwd }), opts.cwd) ?? defaultPolicy();
1911
+ policy = opts.policyOverride ?? loadPolicyAt(mergeBaseOf(base, head, { cwd: opts.cwd }), opts.cwd) ?? defaultPolicy();
1867
1912
  } else {
1868
1913
  process.stderr.write("tamperward: specify --staged, --worktree, or --diff <base>...<head>\n");
1869
1914
  return 2;
@@ -2820,7 +2865,7 @@ function runVerify(opts) {
2820
2865
  const out2 = (s) => void process.stdout.write(s + "\n");
2821
2866
  let policy;
2822
2867
  try {
2823
- policy = loadPolicy(cwd);
2868
+ policy = opts.policyOverride ?? loadPolicy(cwd);
2824
2869
  } catch (e) {
2825
2870
  out2(`verify: cannot load policy (${e instanceof Error ? e.message : String(e)}) \u2014 failing closed`);
2826
2871
  return 2;
@@ -2932,12 +2977,13 @@ function runEnvelope(opts) {
2932
2977
  err(`tamperward run: cannot resolve trusted base ${opts.base ?? "HEAD"} \u2014 failing closed.`);
2933
2978
  return 2;
2934
2979
  }
2980
+ let frozenPolicy;
2935
2981
  let frozenCmd;
2936
2982
  let frozenBudget;
2937
2983
  try {
2938
- const entryPolicy = loadPolicy(cwd);
2939
- frozenCmd = opts.cmd ?? entryPolicy.verify?.command;
2940
- frozenBudget = opts.budget ?? entryPolicy.verify?.budget;
2984
+ frozenPolicy = loadPolicyAt(base, cwd) ?? defaultPolicy();
2985
+ frozenCmd = opts.cmd ?? frozenPolicy.verify?.command;
2986
+ frozenBudget = opts.budget ?? frozenPolicy.verify?.budget;
2941
2987
  } catch (e) {
2942
2988
  err(`tamperward run: cannot load the entry-time policy (${e instanceof Error ? e.message : String(e)}) \u2014 failing closed.`);
2943
2989
  return 2;
@@ -2971,12 +3017,12 @@ tamperward run \u2014 agent exit ${agentExit}; HISTORY_REWRITE \u2192 ENFORCEMEN
2971
3017
  if (head !== base) {
2972
3018
  out(`
2973
3019
  [envelope] policy check over committed changes (${base.slice(0, 10)}...${head.slice(0, 10)}):`);
2974
- diffCode = runCheck({ diff: `${base}...${head}`, cwd });
3020
+ diffCode = runCheck({ diff: `${base}...${head}`, cwd, policyOverride: frozenPolicy });
2975
3021
  }
2976
- out("\n[envelope] policy check over the working tree:");
2977
- const workCode = runCheck({ worktree: true, cwd });
3022
+ out("\n[envelope] policy check over the working tree (frozen policy, untracked included, no local ledger):");
3023
+ const workCode = runCheck({ worktree: true, cwd, policyOverride: frozenPolicy, includeUntracked: true, ciLayer: true });
2978
3024
  out("\n[envelope] pristine verification against the trusted base:");
2979
- const verifyCode = runVerify({ cwd, base, cmd: frozenCmd, budget: frozenBudget });
3025
+ const verifyCode = runVerify({ cwd, base, cmd: frozenCmd, budget: frozenBudget, policyOverride: frozenPolicy });
2980
3026
  const cannot = diffCode === 2 || workCode === 2 || verifyCode === 2;
2981
3027
  const blocked = diffCode === 1 || workCode === 1 || verifyCode === 1;
2982
3028
  const enforcement = cannot ? 2 : blocked ? 1 : 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "1.10.1",
3
+ "version": "1.10.3",
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",