Skip to content

feat(tls): TLS-root owner gate + leaf-rotation hardening + self-heal timeout#43

Merged
MichaelTaylor3d merged 2 commits into
mainfrom
feat/tls-selfheal-hardening
Jul 16, 2026
Merged

feat(tls): TLS-root owner gate + leaf-rotation hardening + self-heal timeout#43
MichaelTaylor3d merged 2 commits into
mainfrom
feat/tls-selfheal-hardening

Conversation

@MichaelTaylor3d

Copy link
Copy Markdown
Contributor

Summary

Batches four cohesive follow-ups from the #624 (v0.33.0 HTTPS/TLS) and #42 (v0.34.0 CORS/self-heal) arcs, all in crates/dig-node-service (tls.rs + self_heal.rs + a new shared security.rs + SPEC).

Closes #661
Closes #660
Closes #659
Closes #693

Fixes + tests

  • #661 — TLS-root owner gate (feat, defence-in-depth). Before load_https_material reads the leaf (and, on the rotation path, ca.key), it verifies the TLS root dir is owned by a privileged principal (Windows: well-known SYSTEM S-1-5-18 / Administrators S-1-5-32-544 SID by exact equality; unix: root + not group/world-writable) and fails closed to plaintext otherwise. A user-writable root could hold an attacker-swapped ca.key this privileged service would sign with.
    • DRY (§2.5): extracted PR feat(serve): desktop-app CORS + self-heal beacon re-arm and forcelist reconcile #42's spawn-free Win32 owner-SID reader (read_owner_sid_string / windows_owner_is_privileged / owner_sid_is_privileged via GetNamedSecurityInfoW + ConvertSidToStringSidW) from self_heal.rs into a new shared crates/dig-node-service/src/security.rs (dir_is_privileged), reused by BOTH the self-heal LPE spawn gate (#565) and this new TLS-root gate. The Win32 tests moved with it.
    • Test: tls::tests::load_refuses_when_the_tls_root_is_not_privileged_owned (a provisioned-but-user-owned tempdir → None).
  • #660 — missed-tick Delay. spawn_leaf_rotation's daily interval now uses MissedTickBehavior::Delay (via new_renewal_ticker), so a host sleep/suspend across intervals coalesces into ONE catch-up pass instead of a redundant burst.
    • Test: tls::tests::renewal_ticker_uses_delay_missed_tick_behavior (paused-clock; counts exactly 1 immediate catch-up tick after a 3-interval stall vs Burst's 3).
  • #659 — rotation-loop coverage. Factored the pass body into run_rotation_pass(manager, now) (behaviour-preserving) so the rotation action is testable without a day's wait.
    • Tests: rotation_pass_leaves_a_fresh_leaf_untouched and rotation_pass_reissues_a_leaf_inside_its_renewal_window (clock +61d past the 90d−30d window → leaf re-issued on disk).
  • #693 — self-heal non-gating hardening.
    • (a) Bounded per-child timeout (CHILD_TIMEOUT = 120s) via with_child_timeout, applied to both the kick spawns and the channel get read, so a hung dig-updater/dig-installer child can never stall future 6h passes. Test: self_heal::tests::kick_times_out_a_hung_child_so_the_pass_never_blocks (paused-clock hung child → SpawnFailed).
    • (b) Documented the Tauri app-origin wallet-read CORS exposure in SPEC §4.3 (same-machine trust; custody + signing stay token-gated; /ws never reflects desktop-app origins). A route/method-scoped CORS gate is not currently cheap (one router-wide CorsLayer; POST / multiplexes content + wallet reads by method), so it is documented rather than enforced, with the future split noted.

Version

minor: 0.34.0 → 0.35.0 — the #661 owner gate is a new backwards-compatible capability (feat); the rest are hardening + tests. Root [workspace.package].version bumped (the released dig-node binary tracks it).

Verification

  • cargo test -p dig-node-service --lib189 passed; 0 failed (incl. the 5 new tests).
  • cargo fmt --all -- --check clean; cargo clippy -p dig-node-service --all-targets --all-features -- -D warnings clean.
  • Added tokio test-util dev-dependency for the paused-clock tests.

gitnexus blast radius

  • spawn_leaf_rotation / load_https_material — sole caller server::bring_up_local_https; signatures unchanged. Owner gate returns None (fail-soft), same type the caller already handles.
  • dir_is_privileged extraction — call sites: self_heal::resolve_privileged_sibling_in (unchanged behaviour) + new tls::load_https_material. Win32 FFI unchanged (byte-moved).
  • spawn_process — now wrapped by with_child_timeout at both call sites; return type unchanged.

Do NOT merge — orchestrator re-gates (#661 + #693 security-adjacent → adversarial + loop-security) then squash-merges + releases (nightlies manual stable dispatch) + bumps the pointer.

🤖 Generated with Claude Code

MichaelTaylor3d and others added 2 commits July 16, 2026 15:02
…timeout

Batches four #624/#42-arc follow-ups in dig-node-service.

- #661: verify the TLS root is SYSTEM/Administrators-owned (Windows) or
  root-owned + not group/world-writable (unix) before reading the leaf or
  ca.key; fail CLOSED to plaintext otherwise. Extracts the spawn-free Win32
  owner-SID reader from self_heal (#565) into a shared `security` module reused
  by both the LPE spawn gate and this TLS-root gate (DRY).
- #660: set MissedTickBehavior::Delay on the daily leaf-rotation interval so a
  host sleep/suspend coalesces into one catch-up pass, never a burst.
- #659: unit-test the rotation cadence, missed-tick behaviour, and the rotation
  action (fresh leaf untouched; leaf inside its 30-day window re-issued).
- #693: bound each self-heal child spawn to a 120s timeout so a hung
  dig-updater/dig-installer child cannot stall future 6h passes; document the
  Tauri app-origin wallet-read CORS exposure in SPEC §4.3 (custody/sign stay
  token-gated; a route-scoped gate is not currently cheap).

SemVer: minor (0.34.0 -> 0.35.0) — the #661 owner gate is a new
backwards-compatible capability; the rest are hardening + tests.

Closes #661
Closes #660
Closes #659
Closes #693

Co-Authored-By: Claude <noreply@anthropic.com>
The spawn_process function spawned privileged siblings (dig-updater,
dig-installer) without kill_on_drop(true), so on CHILD_TIMEOUT elapse
the child process was orphaned instead of killed. The with_child_timeout
doc comment and error message claimed the child was "cancelled" but it
was actually still running.

Added .kill_on_drop(true) to the Command builder so tokio terminates the
child when the timeout future is dropped. Updated doc comments and error
text to accurately state the child IS killed on timeout.

Fixes accuracy (accuracy/LOW per gate agents) and prevents slow orphan
accumulation under wedged dependencies. Not a vulnerability (trusted
absolute-path siblings, hang not attacker-triggerable), but hardens
the implementation.

Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d
MichaelTaylor3d merged commit bb88328 into main Jul 16, 2026
13 checks passed
@MichaelTaylor3d
MichaelTaylor3d deleted the feat/tls-selfheal-hardening branch July 16, 2026 22:51
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