Repository navigation
[Security] Keep screen-recording state out of world-writable /tmp - #8374
Conversation
There was a problem hiding this comment.
Pull request overview
Moves screen-recording state and diagnostics into the per-user runtime directory to prevent /tmp path manipulation.
Changes:
- Relocates recording state, region, and debug files.
- Updates diagnostic documentation.
- Adds regression coverage for recording state placement.
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
bin/omarchy-capture-screenrecording |
Moves runtime files out of fixed /tmp paths. |
test/shell.d/screenrecording-test.sh |
Tests secure state-file placement. |
default/agents/skills/omarchy/capture.md |
Updates debug-log location. |
default/agents/skills/omarchy/contributing.md |
Updates troubleshooting guidance. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Reviewed at Tests ran on a disposable VM, not on the review machine. Pushed one commit ( That first assertion also failed on the fixed script whenever Two things left for the maintainer to decide, not defects I would change under you:
Related, and it matters for merge order: this closes #8372. #7532 adds Waiting on the maintainer. |
itz4blitz
left a comment
There was a problem hiding this comment.
The direction is right and the test design is a standout — comparing the /tmp name across the run instead of demanding absence, so a leftover from a pre-fix recording can't false-positive, is the correct call.
One substantive problem: the fallback re-opens the hole this PR exists to close.
RUNTIME_DIR="${XDG_RUNTIME_DIR:-/tmp}"
RECORDING_FILE="$RUNTIME_DIR/omarchy-screenrecord-filename"When XDG_RUNTIME_DIR is unset — a recording started from a systemd user unit without the import, a cron/at job, an ssh session before systemd --user, or a stale env after logout — the state file lands right back at the predictable /tmp/omarchy-screenrecord-filename the PR's own comment calls out: "a name in world-writable /tmp is a name any other local account can create first and then point wherever it likes." The comment and the code disagree exactly in the branch where the attack still works.
Since the stop path reads the file back and feeds the path to ffmpeg (writes beside it), mv (replaces it) and rm -f (deletes its preview), a pre-planted symlink at the fallback name is a same-user arbitrary-file clobber or delete on stop — the same primitive #8372 describes.
Suggested resolution, matching the resolution #8370 and #8429 use:
RUNTIME_DIR="${XDG_RUNTIME_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/omarchy}"…and mkdir -p "$RUNTIME_DIR" before use, or refuse to run when neither is usable. REGION_FILE carried the same :-/tmp fallback before this PR, so this is a chance to fix both rather than preserve one.
A test for the unset branch would pin it: run the start path with env -u XDG_RUNTIME_DIR and a fake HOME, then assert no omarchy-screenrecord-filename appears at a /tmp path and the state file lands under the state dir instead. Happy to contribute that if useful.
Everything else — the debug-log relocation, the docs updates, the stop-path assertions — checks out.
|
fell back to the state dir when there is no runtime dir, instead of /tmp. |
|
Re-reviewed the new commit ( The suite is red on any clean machine.
Both fixes are mutation-checked on a disposable VM, not on the review machine: reverting the Two smaller things left for you, neither of them blocking:
One thing I checked and am not reporting, because it does not hold: the fallback directory is created Related, unchanged from the last pass and still worth the maintainer's attention for merge order: this closes #8372 in full, including the debug log the issue names. #7532 hardcodes Codex's independence is not currently guaranteed, so read its agreement as agreement. On this pass it earned more than that twice: it found the Waiting on you for the two items above, then on the maintainer. |
|
@dhh @ryanrhughes ready for review |
There was a problem hiding this comment.
🔵 Needs a closer look
The fallback directory may expose sensitive debug data, and debug-log relocation needs coverage.
Review details
Suppressed comments (2)
default/agents/skills/omarchy/capture.md:37
- The implementation now falls back to the Omarchy state directory when
XDG_RUNTIME_DIRis unset, so this instruction points affected users to a nonexistent path. Mention the fallback location as well.
If recording fails to start, rerun with `OMARCHY_SCREENRECORD_DEBUG=true` to
collect a log at `$XDG_RUNTIME_DIR/omarchy-screenrecord.log` worth attaching to a bug
report.
default/agents/skills/omarchy/contributing.md:41
- The no-runtime-directory fallback writes this log to the Omarchy state directory, not this path. Include that fallback so the troubleshooting procedure remains valid in the case the code explicitly supports.
`OMARCHY_SCREENRECORD_DEBUG=true` and attach `$XDG_RUNTIME_DIR/omarchy-screenrecord.log`.
- Files reviewed: 3/5 changed files
- Comments generated: 0 new
- Review effort level: Balanced
|
|
@ErikMelton Addressed both findings in ce438cd: existing fallback directories are explicitly made 0700, and both docs include the fallback debug-log path. Directory creation also moved to recording start so it cannot block the stop path. All 26 focused assertions pass with Bash 5 compatibility on macOS; native Arch/GUI validation is still outstanding. Could you re-review the update? |
The path of the recording in flight was kept at the fixed name /tmp/omarchy-screenrecord-filename, two lines above a region file that already used the per-user runtime directory. Stopping a recording reads that file back and uses its contents as a path: ffmpeg writes a "-processed.mp4" beside it, `mv` puts that over it, `rm -f` deletes its "-preview.png", and the saved toast offers to open it. Any other local account can create that name first, since /tmp's sticky bit stops deletion but not creation, and point all of it wherever it likes. Move it, and the debug log, next to the region file under $XDG_RUNTIME_DIR.
The assertion failed on the fixed script whenever that name already existed -- a recording interrupted before the upgrade leaves one, and another local account can create one you cannot delete, which is the finding itself. Comparing the name's mtime and size across the run keeps the regression caught (the unfixed script rewrites it either way) without depending on global state no test can control. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
:-/tmp put the recording filename and region files back at the same fixed /tmp names this PR exists to leave.
The file is expected to be absent on a clean machine, but the suite runs under `set -euo pipefail`, where a command substitution that fails takes the script down with it -- so `stat` on the missing name aborted the run before the first assertion in this block. It survived review only because the mutation runs that produced it had left the name behind. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The recorder's fallback moved to the state directory; the overlay resizer
still read `${XDG_RUNTIME_DIR:-/tmp}`, so on a session without a runtime
dir the region was written to one path and read from another and the
camera anchored to the whole monitor. Same expression on both sides.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ce438cd to
9310dd0
Compare
…acom#8374) * Keep screen-recording state out of world-writable /tmp * Compare the /tmp name across the run instead of requiring it absent Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fall back to the state directory when there is no runtime dir * Let the /tmp snapshot come back empty Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Resolve the region file the same way in the resizer Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Protect recording fallback state and document its path --------- Co-authored-by: Omabot <omabot@omarchy.org> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit b44fb74)
#23) * [Security] Keep the Windows VM password out of the RDP client's argument list `launch_windows` passed the VM password to xfreerdp3 as `/p:"$WIN_PASS"`. `/proc/<pid>/cmdline` is world-readable, so every other user on the machine could read the password for as long as the session stayed open — `ps` was enough. The arguments now go in over stdin with `/args-from:stdin`, leaving only that flag in the process list. The argument list itself is unchanged, and building it as an array also fixes the unquoted `$RDP_SCALE` expansion. (cherry picked from commit fa3f607) * Switch TCP congestion control to BBR with fq pacing Cubic keeps pushing until packets drop, which stands queues up in the path on fast links. BBR paces to its estimate of bottleneck bandwidth and minimum RTT instead, cutting queueing latency while keeping throughput. fq is the qdisc BBR is built to pace through. tcp_bbr and sch_fq are modules in every kernel Omarchy ships and autoload when the sysctls are set. The migration re-applies the shipped file so new connections switch without a reboot, no-ops once the live values match, and flags a reboot if applying fails. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 9246647) * Merge pull request omacom#7807 from Adolanium/keyring-fail-loud Stop update-keyring from claiming success when key operations fail (cherry picked from commit 4941894) * [Security] Keep screen-recording state out of world-writable /tmp (omacom#8374) * Keep screen-recording state out of world-writable /tmp * Compare the /tmp name across the run instead of requiring it absent Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fall back to the state directory when there is no runtime dir * Let the /tmp snapshot come back empty Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Resolve the region file the same way in the resizer Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Protect recording fallback state and document its path --------- Co-authored-by: Omabot <omabot@omarchy.org> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit b44fb74) * Remove unsafe project bin PATH injection (omacom#11336) * Remove unsafe project bin PATH injection * Cover customized unsafe Mise paths * Revoke legacy Mise Work trust * Harden legacy Mise trust cleanup * Preserve ignored Mise Work configs * Scope Mise path cleanup to env * Accept paranoid Mise ignore marker Reported-by: infosec-us-team (cherry picked from commit a73bcbf) * Treat matching kernel headers as a base system guarantee DKMS hardware installers now assume headers are already present. Existing installs get linux-omarchy or linux-t2 headers via migration 1789444024 when those kernels are installed. The ISO package list still keeps the fork's linux/linux-ptl set until the default-kernel PR lands. Co-authored-by: Mark Groves <mark-groves@users.noreply.github.com> * Set up the Claude browser extension when choosing Claude Picking Claude as the default agent now best-effort installs the official Chromium-family extension. A cancelled or failed install still selects and launches Claude. Cursor remains the official-tarball path and is not installed through mise. Co-authored-by: Mark Groves <mark-groves@users.noreply.github.com> --------- Co-authored-by: Paolo Pellicori <pellicori.paolo@gmail.com> Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Adolanium <94890352+Adolanium@users.noreply.github.com> Co-authored-by: Taksh Kothari <takshkothari09@gmail.com> Co-authored-by: Omabot <omabot@omarchy.org> Co-authored-by: Erik Melton <meltonaerik@gmail.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Mark Groves <mark-groves@users.noreply.github.com>
A fixed name in /tmp is one any other local account can create first, and a lock file this user cannot open makes flock fail, so every stop returns quietly and the recording cannot be stopped. The per-user runtime directory is where omacom#8374 moved the rest of the recorder's state for the same reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both this branch and omacom#8374 appended tests to the end of screenrecording-test.sh. The resolution keeps both blocks, quattro's first and this branch's webcam negotiation tests after it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
quattro moved the recorder's state out of /tmp into a private runtime dir (omacom#8374). The gsr socket follows it there, and the bar indicator and menu guard resolve the same fallback, so all three name one socket and none of them falls back to a fixed /tmp name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes #8372.
Recording state, region state, and the optional recorder debug log now use the per-user runtime directory. Without
XDG_RUNTIME_DIR, they use${XDG_STATE_HOME:-$HOME/.local/state}/omarchy; the webcam resizer uses that same fallback so region anchoring remains consistent.The fallback directory is explicitly mode 0700, including when it already exists with broader permissions. This protects recording paths, geometry and recorder stderr even when XDG_STATE_HOME is traversable by other users. Directory creation and permission repair happen only on recording start, so an unusable directory does not prevent the stop path from signalling an active recorder.
Both troubleshooting documents now give the runtime and fallback debug-log locations.
Validation
All 26 assertions in
test/shell.d/screenrecording-test.shpass, including actual start-path runs with stubbed recorder/compositor commands, runtime and fallback paths, webcam region anchoring, and an existing 0755 fallback repaired to 0700. Tests ran on a local macOS copy with only Bash shebangs adjusted to the installed Bash 5 interpreter; native Arch recording and GUI validation remain outstanding. Bash syntax and git diff checks pass.