Skip to content

fix(security): walk parent components + reject reparse points in dir_is_privileged (#712)#48

Merged
MichaelTaylor3d merged 3 commits into
mainfrom
fix/dir-privileged-parent-walk
Jul 17, 2026
Merged

fix(security): walk parent components + reject reparse points in dir_is_privileged (#712)#48
MichaelTaylor3d merged 3 commits into
mainfrom
fix/dir-privileged-parent-walk

Conversation

@MichaelTaylor3d

@MichaelTaylor3d MichaelTaylor3d commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What + why (dig_ecosystem #712)

Hardens the shared privileged-owner trust gate crate::security::dir_is_privileged
(crates/dig-node-service/src/security.rs), which backs THREE gates: self-heal spawn root (#565),
TLS material root (#661), and the system-service install target (#46). It previously checked only the
LEAF directory's owner. A privileged-owned leaf under a user-writable — or symlinked/junctioned —
ancestor
is still tamperable: an intermediate rename/replace is governed by the parent's
permissions, and a reparse point anywhere redirects the whole path. Aligns dig-node with the higher
bar dig-dns already ships (#701).

Change

dir_is_privileged now walks EVERY existing ancestor component (leaf → parent → … → filesystem root)
and holds each to the same bar; a single weak component fails the whole check. Per component:

  • unix — owned by root/uid 0, no group/other write bit, and NOT a symlink (no-follow
    symlink_metadata/lstat so a symlink is judged on its own identity).
  • Windows — owner SID (read spawn-free via GetNamedSecurityInfoW) equal to S-1-5-18,
    S-1-5-32-544, or NT SERVICE\TrustedInstaller, AND not a reparse point
    (FILE_ATTRIBUTE_REPARSE_POINT via no-follow symlink_metadata, which also catches junctions).

Fails CLOSED on any indeterminate/missing component. One fix hardens all three gates (they share the
helper).

No false-reject of the canonical path (the key risk)

The canonical Windows install root is %ProgramFiles%\DIG\bin. Its ancestor C:\Program Files is
owned by TrustedInstaller, NOT SYSTEM/Administrators — so a naive ancestor-walk accepting only
SYSTEM/Administrators would false-reject the legitimate path. The fix accepts the fixed, byte-identical
NT SERVICE\TrustedInstaller service SID (privileged: a non-admin cannot write it without taking
ownership). Ancestors resolve as: …\bin/…\DIG (Administrators/SYSTEM) → C:\Program Files
(TrustedInstaller, now accepted) → C:\ (SYSTEM) — all pass, none reparse.

Blast radius (gitnexus + manual)

dir_is_privileged has exactly 3 callers, all pass a single dir expecting true for a privileged
path — all now get the tighter whole-path check for free:

Tests (TDD)

  • unix_component_policy_requires_root_owned_non_writable_non_symlink — pure per-component policy incl. symlink rejection even when root-owned.
  • a_symlink_component_is_never_privileged — real-fs: a symlink leaf is refused (reparse rejection).
  • a_child_under_a_user_writable_parent_is_not_privileged — real-fs: the walk reaches ancestors; a non-writable child under a world-writable parent is refused.
  • windows_owner_policy_accepts_the_trusted_installer_sid — TrustedInstaller SID accepted (regression against the false-reject).
  • windows_reparse_attribute_is_detected — pure FILE_ATTRIBUTE_REPARSE_POINT bit test.
  • All existing security/self_heal/service/tls tests still green.

Gates (local, Windows, CARGO_TARGET_DIR short to dodge MAX_PATH)

  • cargo test -p dig-node-service --lib security (5), service (25), self_heal (9), tls (6) — all pass.
  • cargo fmt -p dig-node-service -- --check — clean.
  • cargo clippy -p dig-node-service --all-targets --all-features -- -D warnings — clean.

Version

0.38.0 → 0.38.1 (workspace root) — patch: fix: security hardening, no public-API signature
change (dir_is_privileged only refuses MORE — previously-trusted weak paths — never breaks the
canonical privileged paths). apps/nightlies model: rides the next nightly; no stable auto-cut.

Docs

SPEC.md — added a "Shared whole-path owner check" subsection + updated the TLS/install gate wording;
DEVELOPMENT_LOG.md — durable note on the ancestor walk + the TrustedInstaller Program Files gotcha.

Pre-merge security gate

This is a §565 LPE-adjacent check — built audit-clean, fail-closed, spawn-free owner read retained.

Refs #46 #565 #661 #701

MichaelTaylor3d and others added 2 commits July 16, 2026 22:52
Co-Authored-By: Claude <noreply@anthropic.com>
…is_privileged (#712)

The shared privileged-owner gate (self-heal #565, TLS root #661, service
install #46) checked only the LEAF directory's owner. A privileged-owned leaf
under a user-writable — or symlinked/junctioned — ancestor is still tamperable:
an intermediate rename/replace is governed by the parent's permissions, and a
reparse point anywhere redirects the whole path.

dir_is_privileged now walks EVERY existing ancestor component and holds each to
the same bar, rejecting any symlink/junction/reparse component (no-follow
symlink_metadata + FILE_ATTRIBUTE_REPARSE_POINT on Windows). Accepts the
well-known NT SERVICE\TrustedInstaller SID that owns C:\Program Files so the
canonical %ProgramFiles%\DIG\bin install root is not false-rejected. Aligns
with dig-dns #701. Fails closed. One fix hardens all three gates.

Refs #46 #565 #661 #701

Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d
MichaelTaylor3d marked this pull request as ready for review July 17, 2026 06:08
Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d
MichaelTaylor3d merged commit 837f5dd into main Jul 17, 2026
13 checks passed
@MichaelTaylor3d
MichaelTaylor3d deleted the fix/dir-privileged-parent-walk branch July 17, 2026 09:32
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