Follow-up from #30 / #39. Filing only — not fixed there.
What
The detached initial-sync runner removes its generated script once the sync is done with it:
wait "$relayfile_initial_sync_pid" || relayfile_initial_sync_status=$?;
rm -f <script>;
echo "$relayfile_initial_sync_status" > <exit>
That cleanup is on the normal-completion path only. If the runner shell itself does not reach the rm — the process group is killed, the sandbox is torn down, the host goes away mid-sync — the script stays in the temp directory with no owner and no expiry.
Exposure class
A credential-bearing file persisting past the lifetime it was scoped to. #30 fixed the mode (0600) and added tokenIngress: 'creds-file' so no token literal need be rendered at all, but the default argv ingress still writes the credential into the script, and 0600 is a same-uid boundary — which in a sandbox is commonly shared by every process in it. The point of the removal was that the script should not outlive the sync; on an abnormal exit it outlives it indefinitely.
Lower severity than #30 (the mode fix holds, and creds-file ingress removes the literal entirely), but the lifetime guarantee is currently conditional in a way the code reads as unconditional.
Suggested fix
Any of, ideally more than one:
trap in the runner so EXIT/TERM/INT/HUP remove the script, not just the fall-through after wait. This does not cover SIGKILL.
- Sweep stale
relayfile-initial-sync.sh.* at mount start — a script whose run id has no live pid has no reader left.
- Prefer
tokenIngress: 'creds-file' wherever the daemon build honours RELAYFILE_MOUNT_CREDS_FILE, so an orphaned script carries no reusable credential in the first place. This is the durable fix; the other two narrow the window.
A test should kill the detached sync rather than releasing it, and assert on the file left on disk.
Follow-up from #30 / #39. Filing only — not fixed there.
What
The detached initial-sync runner removes its generated script once the sync is done with it:
That cleanup is on the normal-completion path only. If the runner shell itself does not reach the
rm— the process group is killed, the sandbox is torn down, the host goes away mid-sync — the script stays in the temp directory with no owner and no expiry.Exposure class
A credential-bearing file persisting past the lifetime it was scoped to. #30 fixed the mode (
0600) and addedtokenIngress: 'creds-file'so no token literal need be rendered at all, but the defaultargvingress still writes the credential into the script, and0600is a same-uid boundary — which in a sandbox is commonly shared by every process in it. The point of the removal was that the script should not outlive the sync; on an abnormal exit it outlives it indefinitely.Lower severity than #30 (the mode fix holds, and creds-file ingress removes the literal entirely), but the lifetime guarantee is currently conditional in a way the code reads as unconditional.
Suggested fix
Any of, ideally more than one:
trapin the runner soEXIT/TERM/INT/HUPremove the script, not just the fall-through afterwait. This does not coverSIGKILL.relayfile-initial-sync.sh.*at mount start — a script whose run id has no live pid has no reader left.tokenIngress: 'creds-file'wherever the daemon build honoursRELAYFILE_MOUNT_CREDS_FILE, so an orphaned script carries no reusable credential in the first place. This is the durable fix; the other two narrow the window.A test should kill the detached sync rather than releasing it, and assert on the file left on disk.