tamperward 2.39.22 → 2.39.23

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 +81 -4
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -153,7 +153,16 @@ function resolvesToClaudeSettings(path, env = process.env) {
153
153
  if (isClaudeSettings(path, env)) return true;
154
154
  return isAbsolute(path) && isClaudeSettings(canonicalPath(path), env);
155
155
  }
156
- var PLAIN_SEMVER, RESOLVED_TW_VERSION, TW_VERSION, GIT_HEAD_RE, TW_COMMIT, PRE_TOOLS, PRE_MATCHER, MARKER, NPX_AUTHORITY, AUTHORITY_FAIL_CLOSED, HOOK_CMD, SWEEP_CMD, PRECOMMIT_CMD, OURS, INIT_SCRIPT, CLAUDE_SETTINGS;
156
+ function runtimeWiringOf(path) {
157
+ const p = path.replace(/\\/g, "/");
158
+ if (CODEX_HOOK_CONFIG.test(p)) return "codex";
159
+ if (COPILOT_HOOK_CONFIG.test(p)) return "copilot";
160
+ return null;
161
+ }
162
+ function resolvesToRuntimeWiring(path) {
163
+ return runtimeWiringOf(path) ?? (isAbsolute(path) ? runtimeWiringOf(canonicalPath(path)) : null);
164
+ }
165
+ var PLAIN_SEMVER, RESOLVED_TW_VERSION, TW_VERSION, GIT_HEAD_RE, TW_COMMIT, PRE_TOOLS, PRE_MATCHER, MARKER, NPX_AUTHORITY, AUTHORITY_FAIL_CLOSED, HOOK_CMD, SWEEP_CMD, PRECOMMIT_CMD, OURS, INIT_SCRIPT, CLAUDE_SETTINGS, CODEX_HOOK_CONFIG, COPILOT_HOOK_CONFIG;
157
166
  var init_wiring = __esm({
158
167
  "src/wiring.ts"() {
159
168
  "use strict";
@@ -174,6 +183,8 @@ var init_wiring = __esm({
174
183
  OURS = /^\s*npx --yes --global --userconfig=\/dev\/null --globalconfig=\/dev\/null\/npmrc-global --registry=https:\/\/registry\.npmjs\.org\/ --node-options=' ' --script-shell= --ignore-scripts --offline=false --prefer-online tamperward(?:@(\S+))? (?:(hook claude|sweep claude) \|\| \(echo tamperward: authority failed to start >&2 && exit 2\)|(check --staged))\s*$/;
175
184
  INIT_SCRIPT = /^#!\/bin\/sh\n# tamperward: block agent shortcuts before they land\nnpx --yes --global --userconfig=\/dev\/null --globalconfig=\/dev\/null\/npmrc-global --registry=https:\/\/registry\.npmjs\.org\/ --node-options=' ' --script-shell= --ignore-scripts --offline=false --prefer-online tamperward(?:@(\S+))? check --staged\n*$/;
176
185
  CLAUDE_SETTINGS = /(?:^|\/)(?:\.claude\/settings(?:\.local)?\.json|managed-settings\.json)$/;
186
+ CODEX_HOOK_CONFIG = /(?:^|\/)\.codex\/config\.toml$/;
187
+ COPILOT_HOOK_CONFIG = /(?:^|\/)\.github\/hooks\/tamperward\.json$/;
177
188
  }
178
189
  });
179
190
 
@@ -588,6 +599,12 @@ function defaultPolicy(version = 1) {
588
599
  "**/.tamperward.yml",
589
600
  ".claude/settings.json",
590
601
  ".claude/settings.local.json",
602
+ // The project hook wiring of the experimental runtimes: where Codex reads
603
+ // project hooks and where the Copilot CLI reads repository hooks. Each is
604
+ // the interception for its runtime; removing or weakening it before a
605
+ // protected edit is the tamper, and the gate must see the wiring change.
606
+ ".codex/config.toml",
607
+ ".github/hooks/tamperward.json",
591
608
  "**/.pre-commit-config.yaml",
592
609
  // CODEOWNERS is enforcement wiring, not documentation: it is the only
593
610
  // thing that can require a human on a change to the workflow that
@@ -2736,6 +2753,45 @@ function codeownersWeakening(before, after, extra = []) {
2736
2753
  }
2737
2754
  return out3;
2738
2755
  }
2756
+ function codexHooksTables(toml) {
2757
+ const out3 = [];
2758
+ let inHooks = false;
2759
+ for (const raw of toml.split("\n")) {
2760
+ const line = raw.trim();
2761
+ if (!line || line.startsWith("#")) continue;
2762
+ const table = line.match(TOML_TABLE);
2763
+ if (table) inHooks = isHooksTable(table[1].replace(/["']/g, ""));
2764
+ if (inHooks) out3.push(line);
2765
+ }
2766
+ return out3.join("\n");
2767
+ }
2768
+ function codexHooksWeakening(before, after) {
2769
+ const was = codexHooksTables(before);
2770
+ if (!was) return [];
2771
+ const now = codexHooksTables(after);
2772
+ if (now === was) return [];
2773
+ if (GATE.test(was) && !GATE.test(now)) return ["the tamperward hook entries were removed from the [hooks] tables \u2014 Codex no longer runs the gate"];
2774
+ return ["the [hooks] tables changed \u2014 the PreToolUse and Stop wiring Codex reads is no longer what it was"];
2775
+ }
2776
+ function hooksMember(src) {
2777
+ let doc;
2778
+ try {
2779
+ doc = JSON.parse(src);
2780
+ } catch {
2781
+ return null;
2782
+ }
2783
+ if (!isDoc(doc)) return { hooks: null };
2784
+ return { hooks: doc.hooks === void 0 ? null : JSON.stringify(doc.hooks) };
2785
+ }
2786
+ function copilotHooksWeakening(before, after) {
2787
+ const was = hooksMember(before);
2788
+ if (was === null || was.hooks === null) return [];
2789
+ const now = hooksMember(after);
2790
+ if (now === null) return ["the file no longer parses as JSON \u2014 the Copilot CLI loads no hook from it"];
2791
+ if (now.hooks === was.hooks) return [];
2792
+ if (GATE.test(was.hooks) && !GATE.test(now.hooks ?? "")) return ['the tamperward hook entries were removed from "hooks" \u2014 the Copilot CLI no longer runs the gate'];
2793
+ return ['the "hooks" entries changed \u2014 the preToolUse and agentStop wiring the Copilot CLI reads is no longer what it was'];
2794
+ }
2739
2795
  function parseDoc(src) {
2740
2796
  try {
2741
2797
  const v = yaml.parse(src);
@@ -3184,7 +3240,7 @@ function xargsWritesHook(toks, fed, policy, ctx) {
3184
3240
  if ((cmd === "sed" || cmd === "perl") && args.some((a) => /^-[A-Za-z]*i|^--in-place/.test(a))) return `xargs ${cmd} -i rewrites a protected hook named earlier in the line`;
3185
3241
  return null;
3186
3242
  }
3187
- var isComment, RUNNER, TOOL, SCRIPT, SUBCOMMAND_TOOLS, PREFIX, MODE, CWD, unq, alwaysTrue, EXIT_ZERO, EXIT_ANY, FN_DEF, SENSITIVE_ENV, assignedNames, WAIT_PID, TRANSPARENT, strip, baseIdentity, isCodeowners, GATE_CRITICAL, isDoc, isLefthook, isLefthookLocal, isPreCommitConfig, isPackageJson, GATE, str, list, redirectingEnv, off, STAGE_ALIAS, GATE_STAGES, ALL_STAGES, normStage, INSTALL_SCRIPTS, GIT_HOOKS, ownerExec, SYMBOLIC, WRAPPERS, INTERPRETERS, INLINE_FLAG, EDITORS, SCRIPT_WORD, HOOK_BASENAMES, REDIRECT_TARGET;
3243
+ var isComment, RUNNER, TOOL, SCRIPT, SUBCOMMAND_TOOLS, PREFIX, MODE, CWD, unq, alwaysTrue, EXIT_ZERO, EXIT_ANY, FN_DEF, SENSITIVE_ENV, assignedNames, WAIT_PID, TRANSPARENT, strip, baseIdentity, isCodeowners, GATE_CRITICAL, TOML_TABLE, isHooksTable, isDoc, isLefthook, isLefthookLocal, isPreCommitConfig, isPackageJson, GATE, str, list, redirectingEnv, off, STAGE_ALIAS, GATE_STAGES, ALL_STAGES, normStage, INSTALL_SCRIPTS, GIT_HOOKS, ownerExec, SYMBOLIC, WRAPPERS, INTERPRETERS, INLINE_FLAG, EDITORS, SCRIPT_WORD, HOOK_BASENAMES, REDIRECT_TARGET;
3188
3244
  var init_hook_wiring = __esm({
3189
3245
  "src/detectors/hook-wiring.ts"() {
3190
3246
  "use strict";
@@ -3244,6 +3300,8 @@ var init_hook_wiring = __esm({
3244
3300
  "/lefthook.yml",
3245
3301
  "/lefthook.yaml"
3246
3302
  ];
3303
+ TOML_TABLE = /^\s*\[\[?\s*([^\]]*?)\s*\]\]?/;
3304
+ isHooksTable = (name) => name === "hooks" || name.startsWith("hooks.");
3247
3305
  isDoc = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
3248
3306
  isLefthook = (path) => /(?:^|\/)\.?lefthook(?:-local)?\.(?:ya?ml|toml|json)$/.test(path);
3249
3307
  isLefthookLocal = (path) => /(?:^|\/)\.?lefthook-local\.(?:ya?ml|toml|json)$/.test(path);
@@ -8879,7 +8937,22 @@ function huskyRuntimeWrite(path, content, ctx) {
8879
8937
  (i) => i.identity === "tamperward check --staged" && i.state === "live" && i.pin === TW_VERSION
8880
8938
  );
8881
8939
  }
8882
- var RULE9, ownerExec2, EVENTS, isObj, declares, GATE_KEYS, HOOK_TYPES, SIBLING_KEYS, MENTIONS_GATE, FORGERY, ENV_MECHANISM, envMechanism, mentionsGate, carriesGate, CANONICAL_GATE, COMMIT_STAGES, namesGate, liveOrCanonical, HUSKY_SHIM, HUSKY_SHIM_NAMES, HUSKY_H_KNOWN, HUSKY_DEPRECATION, stripTrailingNl, isHuskyScript, isHookNote, hookTampering;
8940
+ function runtimeWiringFindings(c, wiring, policy) {
8941
+ const finding = (reason) => makeFinding(RULE9, policy, {
8942
+ file: c.path,
8943
+ message: `The ${RUNTIME_WIRING_LABEL[wiring]} hook wiring was weakened: ${reason}.`,
8944
+ evidence: reason,
8945
+ remediation: "Leave the hook entries that run tamperward as they were. Editing the wiring is editing the gate: restore it, or sign off."
8946
+ });
8947
+ if (c.after != null) {
8948
+ if (c.before == null) return [];
8949
+ const weaken = wiring === "codex" ? codexHooksWeakening : copilotHooksWeakening;
8950
+ return weaken(c.before, c.after).map(finding);
8951
+ }
8952
+ const lost = removedLines(c).find((l) => /\btamperward\b/.test(l.content));
8953
+ return lost ? [finding(`a line running tamperward was removed (\`${lost.content.trim()}\`)`)] : [];
8954
+ }
8955
+ var RULE9, ownerExec2, EVENTS, isObj, declares, GATE_KEYS, HOOK_TYPES, SIBLING_KEYS, MENTIONS_GATE, FORGERY, ENV_MECHANISM, envMechanism, mentionsGate, carriesGate, CANONICAL_GATE, COMMIT_STAGES, namesGate, liveOrCanonical, HUSKY_SHIM, HUSKY_SHIM_NAMES, HUSKY_H_KNOWN, HUSKY_DEPRECATION, stripTrailingNl, isHuskyScript, isHookNote, RUNTIME_WIRING_LABEL, hookTampering;
8883
8956
  var init_hook_tampering = __esm({
8884
8957
  "src/detectors/hook-tampering.ts"() {
8885
8958
  "use strict";
@@ -8959,6 +9032,7 @@ They WILL FAIL in v10.0.0
8959
9032
  stripTrailingNl = (s) => s.endsWith("\n") ? s.slice(0, -1) : s;
8960
9033
  isHuskyScript = (path) => /(?:^|\/)\.husky\/[^/]+$/.test(path);
8961
9034
  isHookNote = (path) => !/(?:^|\/)\.husky\/_\//.test(path) && /\.(?:md|markdown|txt)$|(?:^|\/)\.git(?:ignore|attributes)$/i.test(path);
9035
+ RUNTIME_WIRING_LABEL = { codex: "Codex", copilot: "Copilot CLI" };
8962
9036
  hookTampering = {
8963
9037
  id: RULE9,
8964
9038
  surface: ["file", "command"],
@@ -8967,8 +9041,9 @@ They WILL FAIL in v10.0.0
8967
9041
  const out3 = [];
8968
9042
  for (const c of changes) {
8969
9043
  let semantic;
9044
+ let wiring;
8970
9045
  if (c.kind === "file") {
8971
- const targetsHook = isProtected(c.path, policy, "hooks") || (c.oldPath ? isProtected(c.oldPath, policy, "hooks") : false) || resolvesToClaudeSettings(c.path);
9046
+ const targetsHook = isProtected(c.path, policy, "hooks") || (c.oldPath ? isProtected(c.oldPath, policy, "hooks") : false) || resolvesToClaudeSettings(c.path) || resolvesToRuntimeWiring(c.path) !== null;
8972
9047
  if (!targetsHook) {
8973
9048
  if (isPackageJson(c.path) && c.before != null && c.after != null) {
8974
9049
  for (const reason of packageJsonWeakening(c.before, c.after)) {
@@ -9015,6 +9090,8 @@ They WILL FAIL in v10.0.0
9015
9090
  } else if ((c.path.endsWith(POLICY_FILE) || resolvesToClaudeSettings(c.path)) && (semantic = c.path.endsWith(POLICY_FILE) ? policyFindings(c, policy, ctx) : claudeSettingsFindings(c, policy)) !== null) {
9016
9091
  out3.push(...semantic);
9017
9092
  } else if (c.op === "rename") {
9093
+ } else if ((wiring = resolvesToRuntimeWiring(c.path)) !== null) {
9094
+ out3.push(...runtimeWiringFindings(c, wiring, policy));
9018
9095
  } else if (isCodeowners(c.path)) {
9019
9096
  if (c.before != null && c.after != null) {
9020
9097
  for (const reason of codeownersWeakening(c.before, c.after, [...repoGateFiles(ctx), c.path])) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.39.22",
3
+ "version": "2.39.23",
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",