tamperward 2.30.3 → 2.30.4

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 +73 -41
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -14882,7 +14882,7 @@ function runCapturedProcessSync(executable, args, opts) {
14882
14882
  rmSync6(stateDir, { recursive: true, force: true });
14883
14883
  }
14884
14884
  }
14885
- var DIAGNOSTIC_TAIL_BYTES, EMPTY_STREAM, CAPTURE_SUPERVISOR;
14885
+ var DIAGNOSTIC_TAIL_BYTES, EMPTY_STREAM, CLEANUP_HELPERS_SRC, CAPTURE_SUPERVISOR;
14886
14886
  var init_suite_diagnostics = __esm({
14887
14887
  "src/suite-diagnostics.ts"() {
14888
14888
  "use strict";
@@ -14894,40 +14894,32 @@ var init_suite_diagnostics = __esm({
14894
14894
  truncated: false,
14895
14895
  tail: ""
14896
14896
  };
14897
- CAPTURE_SUPERVISOR = String.raw`
14898
- const cp = require('node:child_process');
14899
- const fs = require('node:fs');
14900
-
14901
- const [configFile] = process.argv.slice(1);
14902
- const cfg = JSON.parse(fs.readFileSync(configFile, 'utf8'));
14903
- const CAP = Number(cfg.captureBytes);
14904
-
14905
- const fresh = () => ({ total: 0, tail: Buffer.alloc(0) });
14906
- const stdout = fresh();
14907
- const stderr = fresh();
14908
-
14909
- // Linux ordinary-exit hardening (#371): process-group ownership alone misses a
14910
- // descendant that calls setsid(). Track the real descendant tree while the
14911
- // suite is alive so a reparented session escape is still known when the main
14912
- // child exits. This supplements, rather than replaces, group termination.
14913
- const trackedDescendants = new Set();
14897
+ CLEANUP_HELPERS_SRC = String.raw`
14898
+ function readStat(pid) {
14899
+ try {
14900
+ const stat = fs.readFileSync('/proc/' + pid + '/stat', 'utf8');
14901
+ const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
14902
+ const ppid = Number(fields[1]);
14903
+ const starttime = fields[19];
14904
+ if (!Number.isFinite(ppid) || starttime === undefined) return null;
14905
+ return { ppid: ppid, starttime: starttime };
14906
+ } catch (e) { return null; }
14907
+ }
14914
14908
 
14915
14909
  function linuxDescendants(rootPid) {
14916
14910
  if (process.platform !== 'linux' || !rootPid) return [];
14917
14911
  const byParent = new Map();
14912
+ const startById = new Map();
14918
14913
  let names = [];
14919
- try { names = fs.readdirSync('/proc').filter((x) => /^\d+$/.test(x)); } catch { return []; }
14914
+ try { names = fs.readdirSync('/proc').filter((x) => /^\d+$/.test(x)); } catch (e) { return []; }
14920
14915
  for (const name of names) {
14921
14916
  const pid = Number(name);
14922
- try {
14923
- const stat = fs.readFileSync('/proc/' + name + '/stat', 'utf8');
14924
- const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
14925
- const ppid = Number(fields[1]);
14926
- if (!Number.isFinite(ppid)) continue;
14927
- const kids = byParent.get(ppid) || [];
14928
- kids.push(pid);
14929
- byParent.set(ppid, kids);
14930
- } catch {}
14917
+ const st = readStat(pid);
14918
+ if (!st) continue;
14919
+ startById.set(pid, st.starttime);
14920
+ const kids = byParent.get(st.ppid) || [];
14921
+ kids.push(pid);
14922
+ byParent.set(st.ppid, kids);
14931
14923
  }
14932
14924
  const out = [];
14933
14925
  const seen = new Set([rootPid]);
@@ -14937,16 +14929,56 @@ function linuxDescendants(rootPid) {
14937
14929
  for (const pid of (byParent.get(parent) || [])) {
14938
14930
  if (seen.has(pid)) continue;
14939
14931
  seen.add(pid);
14940
- out.push(pid);
14932
+ out.push({ pid: pid, starttime: startById.get(pid) });
14941
14933
  stack.push(pid);
14942
14934
  }
14943
14935
  }
14944
14936
  return out;
14945
14937
  }
14946
14938
 
14939
+ function killableDescendants(tracked) {
14940
+ const out = [];
14941
+ for (const entry of tracked) {
14942
+ const pid = entry[0];
14943
+ const starttime = entry[1];
14944
+ const st = readStat(pid);
14945
+ if (st && st.starttime === starttime) out.push(pid);
14946
+ }
14947
+ return out;
14948
+ }
14949
+
14950
+ function cleanupPlan(child, cfg, platform, killable) {
14951
+ const live = !!child && child.exitCode === null && child.signalCode === null;
14952
+ return {
14953
+ signalGroup: !!(live && cfg.detached && platform !== 'win32'),
14954
+ signalMainChild: live,
14955
+ descendants: platform === 'linux' ? killable() : [],
14956
+ };
14957
+ }
14958
+ `;
14959
+ CAPTURE_SUPERVISOR = String.raw`
14960
+ const cp = require('node:child_process');
14961
+ const fs = require('node:fs');
14962
+
14963
+ const [configFile] = process.argv.slice(1);
14964
+ const cfg = JSON.parse(fs.readFileSync(configFile, 'utf8'));
14965
+ const CAP = Number(cfg.captureBytes);
14966
+
14967
+ const fresh = () => ({ total: 0, tail: Buffer.alloc(0) });
14968
+ const stdout = fresh();
14969
+ const stderr = fresh();
14970
+
14971
+ // Linux ordinary-exit hardening (#371): process-group ownership alone misses a
14972
+ // descendant that calls setsid(). Track the real descendant tree while the
14973
+ // suite is alive so a reparented session escape is still known when the main
14974
+ // child exits. This supplements, rather than replaces, group termination.
14975
+ // pid -> the /proc start-time observed for that pid, so cleanup can re-check
14976
+ // identity before signalling (a bare pid alone can be reused — #545).
14977
+ const trackedDescendants = new Map();
14978
+ ${CLEANUP_HELPERS_SRC}
14947
14979
  function trackDescendants() {
14948
14980
  if (!child || !child.pid || process.platform !== 'linux') return;
14949
- for (const pid of linuxDescendants(child.pid)) trackedDescendants.add(pid);
14981
+ for (const d of linuxDescendants(child.pid)) trackedDescendants.set(d.pid, d.starttime);
14950
14982
  }
14951
14983
 
14952
14984
  function append(state, chunk) {
@@ -14981,17 +15013,17 @@ let drainTimer = null;
14981
15013
  function killOwned() {
14982
15014
  if (!child || !child.pid) return;
14983
15015
  trackDescendants();
14984
- if (cfg.detached && process.platform !== 'win32') {
14985
- try { process.kill(-child.pid, 'SIGKILL'); } catch {}
14986
- }
14987
- try { child.kill('SIGKILL'); } catch {}
14988
- if (process.platform === 'linux') {
14989
- // Deepest-first is friendlier to short-lived process trees. PIDs are still
14990
- // rechecked by kill(2); races with natural exit are harmless.
14991
- for (const pid of Array.from(trackedDescendants).reverse()) {
14992
- try { process.kill(pid, 'SIGKILL'); } catch {}
14993
- }
14994
- }
15016
+ // The main child and its detached group are signalled only while the child is
15017
+ // still reported live: after its exit event the pid can be reused, and both
15018
+ // child.kill() and kill(-pid) resolve the current bearer of that number (#545).
15019
+ // Descendants are the independently identity-checked set (deepest-first, which
15020
+ // is friendlier to short-lived process trees).
15021
+ const plan = cleanupPlan(child, cfg, process.platform, function () {
15022
+ return killableDescendants(Array.from(trackedDescendants).reverse());
15023
+ });
15024
+ if (plan.signalGroup) { try { process.kill(-child.pid, 'SIGKILL'); } catch {} }
15025
+ if (plan.signalMainChild) { try { child.kill('SIGKILL'); } catch {} }
15026
+ for (const pid of plan.descendants) { try { process.kill(pid, 'SIGKILL'); } catch {} }
14995
15027
  }
14996
15028
 
14997
15029
  function finish(extra) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.30.3",
3
+ "version": "2.30.4",
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",