fix(doctor): case-insensitive safe-bin trusted dir matching on macOS/Windows#42131
fix(doctor): case-insensitive safe-bin trusted dir matching on macOS/Windows#42131hkochar wants to merge 1 commit intoopenclaw:mainfrom
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7d0288852c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| extraDirs: ["/Users/Dev/Custom/bin"], | ||
| }); | ||
| // Trusted dir should be stored lowercased on case-insensitive FS | ||
| expect(dirs.has("/users/dev/custom/bin")).toBe(true); |
There was a problem hiding this comment.
Normalize Windows test expectations with path.resolve
This assertion hardcodes a POSIX-style path, but on win32 the trusted-dir set stores path.resolve(...).toLowerCase() values with Windows separators (for example \\users\\dev\\custom\\bin), so dirs.has("/users/dev/custom/bin") is always false and the test fails on Windows. The checks-windows matrix in .github/workflows/ci.yml runs pnpm test (lines 384-416), so this introduces a deterministic CI failure there.
Useful? React with 👍 / 👎.
| // macOS (APFS) and Windows (NTFS) use case-insensitive filesystems. | ||
| // normalizeConfiguredSafeBins() lowercases bin paths, so trusted dirs | ||
| // must also be lowercased for the Set membership check to match. | ||
| const CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32"; |
There was a problem hiding this comment.
Detect case sensitivity before lowercasing trust paths
Using process.platform to force case-insensitive matching on all macOS paths is unsafe because APFS can be configured as case-sensitive; on those systems, lowercasing collapses distinct directories into the same key, so binaries outside the explicitly trusted path can be incorrectly treated as trusted (and writable-dir checks can miss real directories). This should be gated by actual filesystem behavior rather than platform alone.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR fixes a real bug on macOS (APFS) and Windows (NTFS) where case-insensitive filesystem handling was broken. The issue: The fix is minimal and correct:
The fix is logically sound and well-tested. No logic bugs or functional issues identified. Confidence Score: 5/5
Last reviewed commit: 7d02888 |
|
Addressed both review comments: P1 (Windows paths): Fixed in e8b0655 — tests now use P2 (Case-sensitive APFS): Valid point in theory — APFS can be configured as case-sensitive. In practice this is extremely rare (macOS default is case-insensitive, and Disk Utility warns against it for compatibility). The existing |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e8b0655341
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // macOS (APFS) and Windows (NTFS) use case-insensitive filesystems. | ||
| // normalizeConfiguredSafeBins() lowercases bin paths, so trusted dirs | ||
| // must also be lowercased for the Set membership check to match. | ||
| const CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32"; |
There was a problem hiding this comment.
Detect filesystem case sensitivity before lowercasing trust paths
Lowercasing trusted directories based only on process.platform breaks trust boundaries on macOS volumes formatted as case-sensitive APFS: distinct paths like /opt/Tools/bin and /opt/tools/bin collapse to the same key, so isTrustedSafeBinPath can treat binaries from an untrusted sibling directory as trusted (and listWritableExplicitTrustedSafeBinDirs can miss real directories when fs.statSync is run on the lowercased, non-existent path). This is introduced by forcing case-insensitive normalization for all darwin hosts instead of detecting actual filesystem behavior.
Useful? React with 👍 / 👎.
|
P2→P1 (Case-sensitive APFS): Addressed in 8ea4f62. Replaced the static function detectCaseInsensitiveFs(): boolean {
const tmpDir = os.tmpdir();
const original = fs.statSync(tmpDir);
const flipped = fs.statSync(tmpDir.toUpperCase());
return original.ino === flipped.ino && original.dev === flipped.dev;
}This correctly handles case-sensitive APFS volumes on macOS — if the FS is case-sensitive, no lowercasing occurs. Falls back to the platform heuristic if the probe fails. Tests updated to use the same probe for expectations. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8ea4f628be
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const resolved = path.resolve(trimmed); | ||
| return CASE_INSENSITIVE_FS ? resolved.toLowerCase() : resolved; |
There was a problem hiding this comment.
Determine case sensitivity per trusted path, not tmpdir
normalizeTrustedDir applies lowercasing based on the single CASE_INSENSITIVE_FS probe from os.tmpdir(), so when the temp volume is case-insensitive but a configured safeBinTrustedDirs entry lives on a case-sensitive mount (for example a case-sensitive APFS volume under /Volumes), distinct directories that differ only by case collapse to the same trust key. In that setup, isTrustedSafeBinPath can incorrectly accept binaries from an untrusted sibling path (and writable-dir checks can miss the intended directory), which weakens the trust boundary this check is meant to enforce.
Useful? React with 👍 / 👎.
|
Re: per-path case sensitivity detection — acknowledged. A per-path probe (stat the directory itself with flipped case) would handle cross-volume mounts with mixed case sensitivity. However this adds a Happy to add per-path probing if maintainers want it, but I'd suggest landing the tmpdir probe first as a significant improvement over the status quo (no case handling at all) and iterating if the edge case comes up in practice. |
|
This pull request has been automatically marked as stale due to inactivity. |
|
Codex review: needs maintainer review before merge. Summary Reproducibility: yes. by source inspection: current main lowercases configured safeBins, preserves trusted-dir case, and then exact-matches the resolved executable directory. I did not run a live macOS or Windows doctor reproduction in this read-only review. Real behavior proof Next step before merge Security Review detailsBest possible solution: Land this path-local safe-bin trust fix after exact-head CI is clean, keeping the comparison logic in src/infra/exec-safe-bin-trust.ts and preserving case-sensitive directory separation. Do we have a high-confidence way to reproduce the issue? Yes, by source inspection: current main lowercases configured safeBins, preserves trusted-dir case, and then exact-matches the resolved executable directory. I did not run a live macOS or Windows doctor reproduction in this read-only review. Is this the best way to solve the issue? Yes: the PR fixes the comparison at the trust helper with path-local filesystem probing, avoids a broad platform/tmpdir heuristic, and keeps writable-dir diagnostics on original paths. The remaining blocker is CI/branch validation, not a better code direction. What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against f1ced1961a2f. Re-review progress:
|
8ea4f62 to
af4a016
Compare
af4a016 to
61ce081
Compare
|
Prepared maintainer update at Changes made:
Verification:
Fresh GitHub checks are queued on the prepared head; I did not merge while those are pending. @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
61ce081 to
290418a
Compare
|
Updated prepared head to Follow-up from CI: after the final base rebase, Real behavior proof now passes on the PR body. Fresh CI is running; I am not merging while checks are pending/failing. |
Summary
normalizeConfiguredSafeBins()can lowercase configured safe-bin paths, while trusted safe-bin directory comparisons were exact-case. On case-insensitive filesystems such as default macOS APFS and typical Windows NTFS, that can produce false "outside trusted safe-bin dirs" doctor/runtime warnings even whensafeBinTrustedDirsis configured correctly.The maintainer update keeps the fix path-local: trusted-dir comparison keys are case-folded only when probing the actual compared path shows the filesystem treats that path case-insensitively. Case-sensitive paths keep exact matching, so case-distinct sibling directories do not inherit trust.
Changes
src/infra/exec-safe-bin-trust.ts: normalize trusted-dir comparison keys with path-local case-sensitivity probing instead of a global temp-dir/platform probe.src/infra/exec-safe-bin-trust.ts: keep writable trusted-dir diagnostics reporting the configured resolved path rather than the folded comparison key.src/infra/exec-safe-bin-trust.test.ts: add regression coverage for case-insensitive matching and case-sensitive sibling separation.CHANGELOG.md: add the required fix entry.Reproduction
openclaw doctoror the runtime policy path.Set.has()comparison can miss because one side is folded and the other preserves case.Real behavior proof
290418af5a6516477df180c0526993fb412e87b5.isTrustedSafeBinPath.Test plan
pnpm test src/infra/exec-safe-bin-trust.test.ts src/infra/exec-safe-bin-runtime-policy.test.ts src/commands/doctor-config-flow.safe-bins.test.tspassed before the final base refresh: 33 tests.pnpm test:changedpassed before the final base refresh: 346 files / 3454 tests.pnpm exec oxfmt --check CHANGELOG.md src/infra/exec-safe-bin-trust.ts src/infra/exec-safe-bin-trust.test.tspassed after the final rebase.290418af5a6516477df180c0526993fb412e87b5.