What was measured
Hypatia code-scanning alerts 82 and 83 fired on the phase-2 fixture
crates/launcher-common/tests/fixtures/metadata_block/minted-2026-09-23_stapeln-launcher-deed.sh
lines 56–57:
PID_FILE="/tmp/stapeln-server.pid"
LOG_FILE="/tmp/stapeln-server.log"
CodeRabbit/Hypatia anchored this to the fixture, but the fixture is not the defect —
it is a faithful record of what mint emits. Traced to source:
crates/launcher-common/src/template.rs:108 — .unwrap_or_else(|| format!("/tmp/{}-server.pid", config.project.name))
crates/launcher-common/src/template.rs:113 — .unwrap_or_else(|| format!("/tmp/{}-server.log", config.project.name))
templates/launcher.sh.tera:86-87 merely interpolates {{ pid_file }} / {{ log_file }};
it emits no literal /tmp/. So this is a Rust default, and it reaches every launcher
minted from a config that does not set pid_file/log_file explicitly — not just this fixture.
Why it matters
The path is both world-writable and fully predictable from the project name. The
generated launcher then acts on that file's contents:
templates/launcher.sh.tera:195 — kill -0 "$(cat "$PID_FILE")"
:226 — log "Already running (PID $(cat "$PID_FILE"))"
:200 — rm -f "$PID_FILE"
so an unprivileged local user can pre-create or symlink /tmp/<name>-server.pid before
first run and influence what the launcher signals or deletes. This is the ordinary
/tmp predictable-name hazard; mktemp-style unpredictability or an XDG-scoped
per-user directory removes it.
Pre-existing, not a phase-2 regression
The legacy fixture minted-2026-09-22_stapeln-launcher.sh carries the same two /tmp/
lines. Phase 2 did not introduce this; the new fixture merely made an existing default visible
to the scanner for the first time. Not a blocker for #46 — filed per the standing rule that
a new scanner finding becomes an issue with acceptance criteria.
Acceptance criteria
- The default pid/log location is not a predictable path in a world-writable directory.
The natural target is $XDG_RUNTIME_DIR (falling back to $XDG_STATE_HOME, then
~/.local/state) for the PID file and $XDG_STATE_HOME for the log — per-user, not
world-writable, and stable across restarts, which mktemp deliberately is not.
- An explicit
pid_file / log_file in the config still wins, unchanged, including ~
expansion via expand_home (integration.rs:298-299).
- The generated launcher creates the directory if absent, with mode
0700, before writing.
- A test asserts the emitted
PID_FILE=/LOG_FILE= lines for a config that sets neither
value, so the default is pinned by a committed artefact rather than by the transform.
⚠ Do not assert it by recomputing the same format! in the test — an equality whose right
side is derived from the left cannot fail when both move.
- Both fixtures are re-minted, and Hypatia alerts 82 and 83 close as fixed rather than
being dismissed.
- A note in the config docs states where the default now lands and how to override it.
Out of scope
The three Shellcheck findings on the same generated file — filed separately.
🤖 Generated with Claude Code
https://claude.ai/code/session_01WPSJ7fBhVAMcpSffCBWUDo
What was measured
Hypatia code-scanning alerts 82 and 83 fired on the phase-2 fixture
crates/launcher-common/tests/fixtures/metadata_block/minted-2026-09-23_stapeln-launcher-deed.shlines 56–57:
CodeRabbit/Hypatia anchored this to the fixture, but the fixture is not the defect —
it is a faithful record of what
mintemits. Traced to source:crates/launcher-common/src/template.rs:108—.unwrap_or_else(|| format!("/tmp/{}-server.pid", config.project.name))crates/launcher-common/src/template.rs:113—.unwrap_or_else(|| format!("/tmp/{}-server.log", config.project.name))templates/launcher.sh.tera:86-87merely interpolates{{ pid_file }}/{{ log_file }};it emits no literal
/tmp/. So this is a Rust default, and it reaches every launcherminted from a config that does not set
pid_file/log_fileexplicitly — not just this fixture.Why it matters
The path is both world-writable and fully predictable from the project name. The
generated launcher then acts on that file's contents:
templates/launcher.sh.tera:195—kill -0 "$(cat "$PID_FILE")":226—log "Already running (PID $(cat "$PID_FILE"))":200—rm -f "$PID_FILE"so an unprivileged local user can pre-create or symlink
/tmp/<name>-server.pidbeforefirst run and influence what the launcher signals or deletes. This is the ordinary
/tmppredictable-name hazard;mktemp-style unpredictability or an XDG-scopedper-user directory removes it.
Pre-existing, not a phase-2 regression
The legacy fixture
minted-2026-09-22_stapeln-launcher.shcarries the same two/tmp/lines. Phase 2 did not introduce this; the new fixture merely made an existing default visible
to the scanner for the first time. Not a blocker for #46 — filed per the standing rule that
a new scanner finding becomes an issue with acceptance criteria.
Acceptance criteria
The natural target is
$XDG_RUNTIME_DIR(falling back to$XDG_STATE_HOME, then~/.local/state) for the PID file and$XDG_STATE_HOMEfor the log — per-user, notworld-writable, and stable across restarts, which
mktempdeliberately is not.pid_file/log_filein the config still wins, unchanged, including~expansion via
expand_home(integration.rs:298-299).0700, before writing.PID_FILE=/LOG_FILE=lines for a config that sets neithervalue, so the default is pinned by a committed artefact rather than by the transform.
⚠ Do not assert it by recomputing the same
format!in the test — an equality whose rightside is derived from the left cannot fail when both move.
being dismissed.
Out of scope
The three Shellcheck findings on the same generated file — filed separately.
🤖 Generated with Claude Code
https://claude.ai/code/session_01WPSJ7fBhVAMcpSffCBWUDo