Skip to content

fix(permissions): sandbox-symlink-fail-closed (Linux dangling-symlink fail-open) - #102

Merged
ThatRendle merged 1 commit into
mainfrom
change/sandbox-symlink-fail-closed
Jul 14, 2026
Merged

fix(permissions): sandbox-symlink-fail-closed (Linux dangling-symlink fail-open)#102
ThatRendle merged 1 commit into
mainfrom
change/sandbox-symlink-fail-closed

Conversation

@ThatRendle

Copy link
Copy Markdown
Contributor

Why

SandboxContainmentChecker.FollowLinkToTarget (core/Dmon.Core/Permissions/) decides whether a sandbox-mode write/edit/delete target is contained within the session's assets/<session_id>/ subtree. It is written to fail closed on a broken/dangling symlink, relying solely on catch (IOException).

That intent is defeated on Linux: File.ResolveLinkTarget(returnFinalTarget: true) throws on macOS/Windows for a dangling link (catch fires → null → reject) but returns the non-existent target on Linux without throwing. So a dangling in-sandbox symlink resolved to a non-null, non-existent in-root path, the checker treated it as contained, and the write was auto-allowed when it should be rejected — a fail-open in the sandbox containment boundary.

This is the Dmon.Core twin of the tools-layer RealPathResolver bug already fixed in ac2e315 (#96), which explicitly left this file as out-of-scope follow-up. It is the last outstanding item from the repo-audit-2026-07-06 follow-up batch.

What changed

  • FollowLinkToTarget: added an explicit existence guard after the relative-path re-anchoring block — if (final is not null && !Path.Exists(final)) return null; — so a resolved-but-non-existent target fails closed on every platform. Corrected the backwards platform comment. No changes to ResolveRealPath / ResolveExistingAncestor / IsSymlink / IsContained (they already treat null as reject).
  • Tests (Dmon.Core.Tests): dangling leaf inside sandbox → not contained; dangling ancestor inside → not contained; live in-sandbox link → still contained (over-broad-guard regression). Dangling links point inside the asset dir — the only construction where the unguarded Linux path resolves to a non-null in-root target (i.e. actually gates the fix).
  • Spec: one ADDED requirement to permission-model (cross-platform fail-closed-on-broken-symlink for sandbox asset-containment).

Verification

  • make build — 0 warnings / 0 errors (TreatWarningsAsErrors)
  • make test — green (Dmon.Core.Tests 613 passed / 1 skipped)
  • openspec validate sandbox-symlink-fail-closed --strict — valid

⚠️ macOS/Windows already fail closed via the throw, so a green local make test does not prove this fix. Ubuntu CI is the authoritative verification for the Linux behaviour.

ADR

ADR-006 (conservative permission model) — hardens existing documented fail-closed behaviour; no ADR conflict.

Change: sandbox-symlink-fail-closed

🤖 Generated with Claude Code

… fail-open)

SandboxContainmentChecker.FollowLinkToTarget relied only on catch(IOException)
to fail closed on a dangling symlink. macOS/Windows throw and fail closed, but
Linux returns the non-existent target without throwing, so a dangling in-sandbox
symlink resolved to a non-null in-root path and the sandbox containment boundary
auto-allowed the write (fail-open). Add an explicit Path.Exists(final) guard
after re-anchoring so all platforms fail closed, and correct the backwards
platform comment. Mirrors the tools-layer twin fixed in ac2e315 (#96).

Adds cross-platform tests (dangling leaf inside sandbox, dangling ancestor
inside sandbox, live in-sandbox link regression); the fix is only observable on
Ubuntu CI since macOS already fails closed via the throw.

Change: sandbox-symlink-fail-closed

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ThatRendle
ThatRendle merged commit e059fe9 into main Jul 14, 2026
5 checks passed
@ThatRendle
ThatRendle deleted the change/sandbox-symlink-fail-closed branch July 14, 2026 22:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant