From 558b272423caf1019686965eb9fafc772a709402 Mon Sep 17 00:00:00 2001 From: Liu Minwen Date: Fri, 4 Sep 2026 18:02:50 +0800 Subject: [PATCH] fix(#3): cleanup keychain tmpdir on shell exit src/credentials/keychain.ts renderUseScript emitted a tmpdir under ${TMPDIR:-/tmp}/keychain.XXXXXX with no removal hook. Decrypted credentials persisted past the consuming shell command and were only reaped by the OS tmp reaper (or never), exposing secrets to other processes on shared hosts. Add 'trap "rm -rf "$__kc_dir" EXIT' to the generated script so the temp dir is removed as soon as the consuming shell exits, even on error or signal. Refs: Jackallink/qm-integration#3 --- src/credentials/keychain.ts | 6 +++++- test/keychain.test.ts | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/credentials/keychain.ts b/src/credentials/keychain.ts index 22b88f16d..a2d029da5 100644 --- a/src/credentials/keychain.ts +++ b/src/credentials/keychain.ts @@ -1279,7 +1279,11 @@ const FILE_ENV_POINTERS: Array<[RegExp, (abs: string) => string]> = [ export function renderUseScript(m: MaterializedCred): string { if (m.kind === "env") return m.env.map((e) => `export ${e.key}=${shq(e.value)}`).join("\n") + "\n"; - const lines = [`__kc_dir="$(mktemp -d "\${TMPDIR:-/tmp}/keychain.XXXXXX")"`, `umask 077`]; + const lines = [ + `__kc_dir="$(mktemp -d "\${TMPDIR:-/tmp}/keychain.XXXXXX")"`, + `umask 077`, + `trap 'rm -rf "$__kc_dir"' EXIT`, + ]; for (const f of m.files) { const parent = f.path.includes("/") ? f.path.replace(/\/[^/]*$/, "") : ""; if (parent) lines.push(`mkdir -p "$__kc_dir/${parent}"`); diff --git a/test/keychain.test.ts b/test/keychain.test.ts index 0df8596e2..92442659b 100644 --- a/test/keychain.test.ts +++ b/test/keychain.test.ts @@ -514,6 +514,7 @@ test("file bundles: one item per service, materialize to a /tmp script with env assert.equal(m.kind, "file"); const script = renderUseScript(m); assert.match(script, /mktemp -d/); + assert.match(script, /trap 'rm -rf "\$__kc_dir"' EXIT/); assert.match(script, /chmod 600/); assert.match(script, /export AWS_SHARED_CREDENTIALS_FILE="\$__kc_dir\/.aws\/credentials"/); assert.ok(