Create the service unit directory owner-only instead of refusing it - #935
Conversation
…934) The mode-taking Directory.CreateDirectory overload applies the mode to the leaf only, so ancestors are created one level at a time: a group-writable parent is a rename away from replacing the unit directory whole. A directory that arrives shared-writable is tightened rather than refused, because the bits alone do not say another account is involved — one we can chmod is one no other account owns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
PR Summary by QodoCreate service unit directories with owner-only permissions
AI Description
Diagram
High-Level Assessment
Files changed (6)
|
Code Review by Qodo
1.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 923345912d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (var d = directory; !string.IsNullOrEmpty(d) && !Directory.Exists(d); d = Path.GetDirectoryName(d)!) | ||
| missing.Push(d); | ||
|
|
||
| while (missing.Count > 0) Directory.CreateDirectory(missing.Pop(), OwnerOnlyDir); |
There was a problem hiding this comment.
Repair owner bits after creating each directory
On Unix, the explicit 0700 mode passed to Directory.CreateDirectory is still filtered by the process umask. For example, under umask 0400, each new directory lands as 0300; the unit write can succeed because write and traverse remain, but later ListInstalled() calls cannot enumerate the unreadable unit directory and fail with an access error. The existing file path repairs its requested mode after creation, but this new directory path tests only shared-write bits, so it should likewise verify and repair every newly created directory to 0700 rather than relying on the requested creation mode.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Confirmed and fixed in 954a51f.
The mechanism is right, and the test suite was blind to it: the assertions only checked the shared-write bits, so a umask stripping the owner's bits passed. Added umask 400 and 777 cases and tightened the assertion to an exact mode — both failed, then passed once each created directory is chmod'd after creation.
400 is the sharper case, as described: 0300 is still writable, so the install reports success and ListInstalled fails afterwards. 777 lands 0000 and fails at the write instead.
The mode passed to Directory.CreateDirectory is filtered through the umask exactly like the default one, so a restrictive umask strips the owner's own bits: under 400 the directory lands 0300, which the unit still writes into and which ListInstalled then cannot enumerate — an install that reports success and a `service list` that fails. An explicit chmod is not filtered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Closes #934 — AI-2772
What & why
kcap daemon service installcreates the unit directory withDirectory.CreateDirectory, which applies0777 & ~umask, and then refuses it for being group-writable. On Debian and Ubuntupam_umaskwithUSERGROUPS_ENAB yessets umask002for a user whose primary group is their own name, so the directory lands0775and no install can succeed. The mode bits alone do not say another account is involved — a user-private group has one member, and a directory we canchmodis one no other account owns. So the directory is now created0700, a shared-writable one is tightened, and the refusal is kept for the case where the bits survive.Where to look
ServiceFiles.CreateDirectorywalks one level at a time on purpose: the mode-takingDirectory.CreateDirectoryoverload applies the mode to the leaf only, so a single call would still leave every ancestor it created at0777 & ~umask— and a writable parent is a rename away from replacing the unit directory whole. Ancestors that already exist are left alone;~/.configis not this code's to tighten.The refusal branch needs a directory the current user does not own, so it is driven by a seam rather than provoked — the only directories a test could rely on finding are shared system ones that a run as root would then really
chmod.Verification
Reproduced on a stock Ubuntu box, where the directory is created and rejected within the same second:
WriteOwnerOnly_creates_a_usable_unit_directory_under_any_umasksets umask002and000and fails onmainwith exactly the reportedInvalidOperationException. The parent-mode assertion in it also failed against a first attempt that used oneCreateDirectorycall, which is how the leaf-only behaviour above was found.Capacitor.Cli.Tests.Unit: 4098 total, 0 failed, 19 skipped.dotnet build Capacitor.slnx: 16 projects, 0 warnings.dotnet publish -c Release: no IL2026/IL3050.🤖 Generated with Claude Code