@seekrit/cli 0.23.3 → 0.23.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.
- package/dist/index.js +87 -44
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1976,7 +1976,7 @@ function isServiceToken(value) {
|
|
|
1976
1976
|
}
|
|
1977
1977
|
//#endregion
|
|
1978
1978
|
//#region package.json
|
|
1979
|
-
var version = "0.23.
|
|
1979
|
+
var version = "0.23.4";
|
|
1980
1980
|
//#endregion
|
|
1981
1981
|
//#region ../../packages/api-client/src/index.ts
|
|
1982
1982
|
var SeekritApiError = class extends Error {
|
|
@@ -4575,45 +4575,70 @@ async function materializeForRun(options) {
|
|
|
4575
4575
|
}
|
|
4576
4576
|
}
|
|
4577
4577
|
/**
|
|
4578
|
-
* Every live
|
|
4579
|
-
*
|
|
4580
|
-
*
|
|
4581
|
-
*
|
|
4582
|
-
*
|
|
4583
|
-
*
|
|
4578
|
+
* Every live process `seekrit run` is responsible for tearing down, from a
|
|
4579
|
+
* single `ps(1)` snapshot: each descendant of `root`, plus — when we lead our
|
|
4580
|
+
* own process group — every other member of that group.
|
|
4581
|
+
*
|
|
4582
|
+
* Two mechanisms, because each covers the other's blind spot:
|
|
4583
|
+
*
|
|
4584
|
+
* - The parent-chain walk reaches a process that left our group via
|
|
4585
|
+
* setsid(2)/setpgid(2), which no group-wide kill and no tty-generated signal
|
|
4586
|
+
* can touch. But it loses anything whose chain up to `root` has already
|
|
4587
|
+
* broken — teardown kills the middle layers first, and an orphan reparents to
|
|
4588
|
+
* init, out of reach of any walk down from `root`.
|
|
4589
|
+
* - Group membership survives exactly that: a pgid outlives the parent that
|
|
4590
|
+
* passed it down. But it misses the ones that left the group.
|
|
4591
|
+
*
|
|
4592
|
+
* The group half applies only when our pid *is* our pgid, which is the shell-job
|
|
4593
|
+
* case: the shell put this command in a new group, so every other member of it
|
|
4594
|
+
* descends from us. Nested inside a shell script or a CI runner we are not the
|
|
4595
|
+
* leader and the group holds processes we did not start, so there we use the
|
|
4596
|
+
* walk alone.
|
|
4597
|
+
*
|
|
4598
|
+
* Zombies are skipped: they are already dead and waiting to be reaped, so
|
|
4599
|
+
* counting them would stall teardown for the full grace period and then report
|
|
4600
|
+
* a kill that did nothing. Returns nothing if `ps` is unavailable; callers fall
|
|
4601
|
+
* back to signalling the immediate child.
|
|
4584
4602
|
*/
|
|
4585
|
-
function
|
|
4603
|
+
function teardownTargets(root) {
|
|
4586
4604
|
const ps = spawnSync("ps", [
|
|
4587
4605
|
"-A",
|
|
4588
4606
|
"-o",
|
|
4589
|
-
"pid=,ppid=,pgid="
|
|
4607
|
+
"pid=,ppid=,pgid=,stat="
|
|
4590
4608
|
], { encoding: "utf8" });
|
|
4591
4609
|
if (ps.status !== 0 || typeof ps.stdout !== "string") return [];
|
|
4592
4610
|
const childrenOf = /* @__PURE__ */ new Map();
|
|
4593
4611
|
const groupOf = /* @__PURE__ */ new Map();
|
|
4594
4612
|
const isId = (value) => value !== void 0 && Number.isInteger(value);
|
|
4595
4613
|
for (const line of ps.stdout.split("\n")) {
|
|
4596
|
-
const [pid, ppid, pgid] = line.trim().split(/\s+/,
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4614
|
+
const [pid, ppid, pgid, stat] = line.trim().split(/\s+/, 4);
|
|
4615
|
+
const [id, parent, group] = [
|
|
4616
|
+
pid,
|
|
4617
|
+
ppid,
|
|
4618
|
+
pgid
|
|
4619
|
+
].map(Number);
|
|
4620
|
+
if (!isId(id) || !isId(parent) || !isId(group)) continue;
|
|
4621
|
+
if (stat?.startsWith("Z")) continue;
|
|
4622
|
+
const siblings = childrenOf.get(parent);
|
|
4623
|
+
if (siblings) siblings.push(id);
|
|
4624
|
+
else childrenOf.set(parent, [id]);
|
|
4625
|
+
groupOf.set(id, group);
|
|
4626
|
+
}
|
|
4627
|
+
const found = /* @__PURE__ */ new Set();
|
|
4605
4628
|
const queue = [root];
|
|
4606
4629
|
const seen = new Set(queue);
|
|
4607
4630
|
for (let pid = queue.shift(); pid !== void 0; pid = queue.shift()) for (const kid of childrenOf.get(pid) ?? []) {
|
|
4608
4631
|
if (seen.has(kid)) continue;
|
|
4609
4632
|
seen.add(kid);
|
|
4610
|
-
found.
|
|
4611
|
-
pid: kid,
|
|
4612
|
-
escaped: ourGroup === void 0 || groupOf.get(kid) !== ourGroup
|
|
4613
|
-
});
|
|
4633
|
+
found.add(kid);
|
|
4614
4634
|
queue.push(kid);
|
|
4615
4635
|
}
|
|
4616
|
-
|
|
4636
|
+
if (groupOf.get(process.pid) === process.pid) {
|
|
4637
|
+
for (const [pid, group] of groupOf) if (group === process.pid) found.add(pid);
|
|
4638
|
+
}
|
|
4639
|
+
found.delete(process.pid);
|
|
4640
|
+
found.delete(root);
|
|
4641
|
+
return [...found];
|
|
4617
4642
|
}
|
|
4618
4643
|
/** Whether `pid` still exists — signal 0 tests reachability, delivers nothing. */
|
|
4619
4644
|
function isAlive(pid) {
|
|
@@ -4624,25 +4649,37 @@ function isAlive(pid) {
|
|
|
4624
4649
|
return false;
|
|
4625
4650
|
}
|
|
4626
4651
|
}
|
|
4627
|
-
/** How long a
|
|
4628
|
-
const
|
|
4652
|
+
/** How long a straggler gets to finish shutting down before SIGKILL. */
|
|
4653
|
+
const STRAGGLER_GRACE_MS = 2e3;
|
|
4629
4654
|
/**
|
|
4630
|
-
*
|
|
4631
|
-
*
|
|
4632
|
-
*
|
|
4633
|
-
*
|
|
4634
|
-
*
|
|
4635
|
-
*
|
|
4636
|
-
*
|
|
4655
|
+
* Once the command has exited, make sure nothing it started outlives us.
|
|
4656
|
+
*
|
|
4657
|
+
* Anything still alive here has ignored the signal we forwarded *and* lost the
|
|
4658
|
+
* parent that launched it, and we are the last process that knows its pid:
|
|
4659
|
+
* the tty won't hang it up (it is no longer in the foreground group, and the
|
|
4660
|
+
* shell has moved on), so it survives until the machine reboots. That is how
|
|
4661
|
+
* `tsx` and the server under it stacked up for days.
|
|
4662
|
+
*
|
|
4663
|
+
* So escalate rather than trust. SIGTERM first — it costs no extra time and is
|
|
4664
|
+
* a rung the SIGINT-only handlers common in dev servers don't catch — then
|
|
4665
|
+
* SIGKILL whatever is left after the grace period. Nothing is killed unless it
|
|
4666
|
+
* was asked to stop first and declined, and this runs only when we forwarded a
|
|
4667
|
+
* termination signal: a command that exits on its own having deliberately left a
|
|
4668
|
+
* daemon behind keeps working.
|
|
4637
4669
|
*/
|
|
4638
|
-
async function
|
|
4639
|
-
if (pids.size === 0) return;
|
|
4640
|
-
const deadline = Date.now() + ESCAPEE_GRACE_MS;
|
|
4670
|
+
async function reapStragglers(pids, signal) {
|
|
4641
4671
|
let live = [...pids].filter(isAlive);
|
|
4672
|
+
if (live.length === 0) return;
|
|
4673
|
+
for (const pid of live) try {
|
|
4674
|
+
process.kill(pid, "SIGTERM");
|
|
4675
|
+
} catch {}
|
|
4676
|
+
const deadline = Date.now() + STRAGGLER_GRACE_MS;
|
|
4642
4677
|
while (live.length > 0 && Date.now() < deadline) {
|
|
4643
4678
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
4644
4679
|
live = live.filter(isAlive);
|
|
4645
4680
|
}
|
|
4681
|
+
if (live.length === 0) return;
|
|
4682
|
+
console.error(`seekrit: force-killed ${live.length} leftover process(es) that ignored ${signal} and SIGTERM (${live.join(", ")})`);
|
|
4646
4683
|
for (const pid of live) try {
|
|
4647
4684
|
process.kill(pid, "SIGKILL");
|
|
4648
4685
|
} catch {}
|
|
@@ -4669,21 +4706,27 @@ program.command("run").description("run a command with decrypted secrets injecte
|
|
|
4669
4706
|
"SIGHUP",
|
|
4670
4707
|
"SIGQUIT"
|
|
4671
4708
|
];
|
|
4672
|
-
const
|
|
4709
|
+
const stragglers = /* @__PURE__ */ new Set();
|
|
4710
|
+
let forwarded;
|
|
4711
|
+
const collect = () => {
|
|
4712
|
+
if (!posix || !child.pid) return;
|
|
4713
|
+
for (const pid of teardownTargets(child.pid)) stragglers.add(pid);
|
|
4714
|
+
};
|
|
4673
4715
|
const forward = (signal) => {
|
|
4674
4716
|
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
4717
|
+
forwarded ??= signal;
|
|
4718
|
+
collect();
|
|
4675
4719
|
child.kill(signal);
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
try {
|
|
4680
|
-
process.kill(pid, signal);
|
|
4681
|
-
} catch {}
|
|
4682
|
-
}
|
|
4720
|
+
for (const pid of stragglers) try {
|
|
4721
|
+
process.kill(pid, signal);
|
|
4722
|
+
} catch {}
|
|
4683
4723
|
};
|
|
4684
4724
|
for (const signal of signals) process.on(signal, forward);
|
|
4685
4725
|
child.on("exit", async (code, signal) => {
|
|
4686
|
-
|
|
4726
|
+
if (forwarded) {
|
|
4727
|
+
collect();
|
|
4728
|
+
await reapStragglers(stragglers, forwarded);
|
|
4729
|
+
}
|
|
4687
4730
|
for (const s of signals) process.off(s, forward);
|
|
4688
4731
|
if (signal) process.kill(process.pid, signal);
|
|
4689
4732
|
else process.exit(code ?? 1);
|