Skip to content

Commit 2c42cf9

Browse files
hyperpolymathclaude
andcommitted
fix(launcher-standard): move PID/log to XDG dirs (security: symlink-attack hardening)
The standard specified `/tmp/{app-name}-server.pid` and `/tmp/{app-name}-server.log` as the required runtime paths. Predictable names in a world-writable directory are a symlink-attack target on any shared host: an attacker can pre-create `/tmp/<app>-server.pid` containing their own PID, after which the launcher's `is_running()` returns true and `stop_server()` will `kill <attacker-pid>` — DoS or signal-handling abuse vector. Similar for log: clobber attacks via pre-symlinked log file, or info-disclosure if logs contain anything sensitive (the standard already warns "don't log sensitive information" but trust-but-verify). Fix: route both to XDG dirs with documented fallback ladders. - **PID** → `${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/<app>-server.pid`. `$XDG_RUNTIME_DIR` is mode 0700 and user-scoped per the XDG Base Directory spec (Linux). `$TMPDIR` covers macOS / BSDs (typically `/var/folders/.../T`, per-user). `/tmp` remains only as a last-resort fallback for hosts that set neither (rare). - **Log** → `${XDG_STATE_HOME:-$HOME/.local/state}/<app>/server.log`. Per-user, survives reboot, not world-writable. The `<app>` subdir isolates each launcher's logs. Updates to both files in the same commit per the lock-step requirement: - `launcher/launcher-standard.a2ml`: `[runtime].pid-file-pattern` and `log-file-pattern` updated with fallback ladders; `[disinteg].preserve` updated to reference the new log dir. - `docs/UX-standards/launcher-standard.adoc`: - Standard Launcher Template snippet updated with XDG paths plus `mkdir -p "$LOG_DIR"` for the state-dir - §What --disinteg removes / does not remove: paths updated - Desktop File Standard Exec= example log-arg updated - Calling Convention daemon-chain example updated - Debugging Checklist now uses $LOG_FILE / $PID_FILE variable refs - §Best Practices > Logging: lead bullet rewritten with rationale - §Best Practices > Security: new lead bullets explaining the symlink-attack vector and the XDG choice - §Compliance Checklist: "Log to predictable location (/tmp/...)" replaced with the XDG requirement Remaining `/tmp/` mentions in the prose are in forbidden-patterns text that explicitly tells readers NOT to use /tmp — intentional. Bash-expansion ladder format (`${VAR:-${VAR2:-/literal}}`) matches the shell-expansion style already used elsewhere in the a2ml (e.g. `$HOME/.local/share/applications` in `[integration.linux]`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 431adbb commit 2c42cf9

2 files changed

Lines changed: 33 additions & 15 deletions

File tree

docs/UX-standards/launcher-standard.adoc

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,16 @@ The reference implementation is provided in link:comprehensive-launcher-template
2020
----
2121
# Standardized structure for all launchers
2222
APP_NAME="MyApp" # Application name
23-
REPO_DIR="/path/to/repo" # Repository directory
23+
REPO_DIR="/path/to/repo" # Repository directory
2424
COMMAND="command to run" # Startup command
2525
URL="http://localhost:PORT" # Web URL (if applicable)
26-
PID_FILE="/tmp/app-server.pid" # Process ID tracking
27-
LOG_FILE="/tmp/app-server.log" # Log file location
26+
# PID file in XDG_RUNTIME_DIR (mode 0700, user-scoped) — falls back to
27+
# $TMPDIR (macOS) then /tmp (last resort). See §Best Practices > Security.
28+
PID_FILE="${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/${APP_NAME}-server.pid"
29+
# Log file in XDG_STATE_HOME (defaults to $HOME/.local/state per spec).
30+
LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/${APP_NAME}"
31+
LOG_FILE="${LOG_DIR}/server.log"
32+
mkdir -p "$LOG_DIR"
2833
MODE="${1:---auto}" # Default mode
2934
----
3035

@@ -281,13 +286,13 @@ system". The `--disinteg` mode is its exact inverse.
281286

282287
Everything `--integ` created, plus:
283288

284-
* The PID file (`/tmp/<app>-server.pid`)
289+
* The PID file (`$XDG_RUNTIME_DIR/<app>-server.pid`, or the resolved equivalent — see §Best Practices > Security)
285290
* Any `.bat` fallback shortcuts written when PowerShell wasn't available
286291

287292
It deliberately **does not** remove:
288293

289294
* `~/.config/<app>/` — user preferences survive reinstall
290-
* `/tmp/<app>-server.log` — logs stay for post-mortem
295+
* `$XDG_STATE_HOME/<app>/` (defaults to `$HOME/.local/state/<app>/`) — logs stay for post-mortem
291296
* The source repository at `REPO_DIR`
292297

293298
The removal instructions for those are printed after `--disinteg` so the user
@@ -316,7 +321,7 @@ feedback.
316321
[Desktop Entry]
317322
Type=Application
318323
Name=Application Name
319-
Exec=/var/mnt/eclipse/repos/.desktop-tools/keepopen.sh "AppName" "/path/to/repo" "GUI_CMD" "TUI_CMD" "/tmp/app.log"
324+
Exec=/var/mnt/eclipse/repos/.desktop-tools/keepopen.sh "AppName" "/path/to/repo" "GUI_CMD" "TUI_CMD" "$HOME/.local/state/app/server.log"
320325
Terminal=true # keepopen needs a terminal for its loud banners and shell fallback
321326
Icon=/path/to/icon.png
322327
Categories=Category;
@@ -390,7 +395,7 @@ a `tail -f LOG` after the launcher so the terminal stays open:
390395

391396
[source,bash]
392397
----
393-
"aerie-launcher.sh --auto && tail -f /tmp/aerie.log"
398+
"aerie-launcher.sh --auto && tail -f $HOME/.local/state/aerie/server.log"
394399
----
395400

396401
=== The three stages
@@ -478,10 +483,10 @@ Exec=launcher.sh --auto
478483

479484
=== Debugging Checklist
480485

481-
1. Check log file: `tail -f /tmp/app-server.log`
486+
1. Check log file: `tail -f "$LOG_FILE"` (resolves to `$XDG_STATE_HOME/<app>/server.log`)
482487
2. Verify process: `ps aux | grep app-name`
483488
3. Test URL manually: `curl -v http://localhost:PORT`
484-
4. Check PID file: `cat /tmp/app-server.pid`
489+
4. Check PID file: `cat "$PID_FILE"` (resolves to `$XDG_RUNTIME_DIR/<app>-server.pid`)
485490
5. Test browser opening: `xdg-open http://localhost:PORT`
486491
6. Verify dependencies: `command -v required-command`
487492
7. Check port availability: `ss -tlnp | grep PORT`
@@ -529,7 +534,7 @@ APP_NAME="AppName"
529534
[ ] Implement `wait_for_server()` with reasonable timeout
530535
[ ] Add proper error handling and user feedback
531536
[ ] Provide clear success/failure messages
532-
[ ] Log to predictable location (`/tmp/app-name.log`)
537+
[ ] Log to XDG state dir (`$XDG_STATE_HOME/<app>/server.log`, defaults to `$HOME/.local/state/<app>/server.log`); never use `/tmp/<app>.log`
533538
[ ] Handle browser launch failures gracefully
534539
[ ] Provide manual fallback instructions
535540
[ ] Implement `--start`, `--stop`, `--status` modes
@@ -540,7 +545,8 @@ APP_NAME="AppName"
540545
== Best Practices
541546

542547
=== Logging
543-
- Log to `/tmp/app-name.log` for easy debugging
548+
- Log to `$XDG_STATE_HOME/<app>/server.log` (defaults to `$HOME/.local/state/<app>/server.log` per XDG spec). Per-user, survives reboot, not world-writable.
549+
- Never log to `/tmp/<app>.log`. Predictable names in a world-writable dir are a symlink-attack target on shared hosts — see §Best Practices > Security.
544550
- Include timestamps for long-running processes
545551
- Rotate logs if they grow large
546552
- Provide log location in error messages
@@ -559,7 +565,9 @@ APP_NAME="AppName"
559565
- Optimize startup sequence
560566

561567
=== Security
562-
- Use predictable but unique PID file names
568+
- **PID files MUST go in `$XDG_RUNTIME_DIR`** (Linux) / `$TMPDIR` (macOS), not `/tmp`. `$XDG_RUNTIME_DIR` is mode `0700` and user-scoped per the XDG Base Directory spec; `$TMPDIR` on macOS is `/var/folders/.../T` (per-user). `/tmp` is world-writable: an attacker on a shared host can pre-create `/tmp/<app>-server.pid` containing their own PID, after which the launcher's `is_running()` returns true and `stop_server()` will `kill <attacker-pid>` — DoS or signal-handling abuse vector. The fallback ladder `${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}` exists only as a last resort for hosts that set neither (rare).
569+
- **Log files MUST go in `$XDG_STATE_HOME`** for the same reason — never `/tmp/<app>.log`.
570+
- Use predictable but unique PID file *names within the chosen dir* (not in `/tmp`).
563571
- Clean up PID files on exit
564572
- Don't log sensitive information
565573
- Validate URLs before opening

launcher/launcher-standard.a2ml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,18 @@ mode = "--auto"
5959
[runtime]
6060
# Required runtime behaviour.
6161
background = "nohup"
62-
pid-file-pattern = "/tmp/{app-name}-server.pid"
63-
log-file-pattern = "/tmp/{app-name}-server.log"
62+
# PID files live in the user's runtime-state dir — wiped on logout,
63+
# user-scoped (mode 0700 per XDG spec), no symlink-attack target.
64+
# Bash-expansion ladder:
65+
# 1. $XDG_RUNTIME_DIR — Linux per freedesktop XDG Base Directory spec
66+
# 2. $TMPDIR — macOS / BSDs (typically /var/folders/.../T, per-user)
67+
# 3. /tmp — last resort (predictable, world-writable; flagged
68+
# in §Best Practices > Security)
69+
pid-file-pattern = "${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/{app-name}-server.pid"
70+
# Logs go to XDG_STATE_HOME (per spec, defaults to $HOME/.local/state).
71+
# Per-user, survives reboot, not world-writable. The {app-name} subdir
72+
# isolates each launcher's logs.
73+
log-file-pattern = "${XDG_STATE_HOME:-$HOME/.local/state}/{app-name}/server.log"
6474
wait-for-url-timeout-seconds = 15
6575
startup-command-search = [
6676
"{repo-dir}/scripts/run.sh",
@@ -121,7 +131,7 @@ remove = [
121131
]
122132
preserve = [
123133
"$HOME/.config/{app-name}/",
124-
"/tmp/{app-name}-server.log",
134+
"${XDG_STATE_HOME:-$HOME/.local/state}/{app-name}/",
125135
"{repo-dir}",
126136
]
127137

0 commit comments

Comments
 (0)