Agent ready-file O_NOFOLLOW + workspace consolidation + threat-model refresh#46
Merged
jgowdy-godaddy merged 5 commits intomainfrom Apr 17, 2026
Merged
Agent ready-file O_NOFOLLOW + workspace consolidation + threat-model refresh#46jgowdy-godaddy merged 5 commits intomainfrom
jgowdy-godaddy merged 5 commits intomainfrom
Conversation
- Correct crate count (9 -> 10) and add sshenc-tpm-bridge to the crate table. - Expand the sshenc-cli subcommand list (config, openssh, uninstall, identity, default, ssh, completions) to match main.rs. - Replace the stale "socat + npiperelay" WSL bridge description with the current JSON-RPC bridge (sshenc-tpm-bridge) on both the platform backends and platform support tables. - Document the Windows named pipe DACL and the Unix socket/directory permissions. - Document that the Linux software fallback is wrapped by an OS keyring key via enclaveapp-keyring, not raw P-256 on disk. - Add sshenc-tpm-bridge to the binaries list.
signal_ready now opens the ready file with
OpenOptions::new().write(true).create_new(true).custom_flags(libc::O_NOFOLLOW).mode(0o600)
on Unix. If the path already exists the open fails with EEXIST; if it's
a symlink the open fails with ELOOP. Either way the write never lands
on an attacker-chosen target, and the error surfaces to the parent
process via the daemonize handshake instead of being silently followed.
mode(0o600) at open time is load-bearing on umask-permissive systems;
the explicit set_permissions(0o600) after write is belt-and-suspenders.
The parent (daemonize in main.rs) is responsible for removing the
ready-file path before and after spawning the child, so signal_ready
always starts from a clean slate and can safely demand create_new
semantics without a separate unlink.
New test signal_ready_refuses_preplanted_symlink pre-plants a symlink
at the ready path and asserts that:
1. signal_ready returns an error (ELOOP / EEXIST / File exists),
2. the symlink's target file is NOT created — so the attack is blocked
rather than silently followed.
Consolidates on libenclaveapp's own crates/ as the single source of truth. The former ../crates/ location (top-level /enclaveapps/crates/) is gone — it was a divergent copy that periodically drifted from libenclaveapp/crates/. sshenc builds against the canonical tree now.
- 'Ready-File Symlink in $TMPDIR' rewritten around the new O_NOFOLLOW + create_new hardening; residual risk reduced from 'theoretically exploitable' to 'none for the symlink-redirect vector.' - 'Windows Named-Pipe Hijack' notes the explicit DACL now granting full control only to creator-owner + SYSTEM. - 'Metadata File Tamper (.meta)' notes the HMAC sidecar now written by the Linux keyring backend via enclaveapp-keyring::meta_hmac_key — .meta tamper without keyring access is now caught with a hard meta_hmac_verify error.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
signal_readyon the daemonize handshake now opens its\$TMPDIR/sshenc-agent-ready-<pid>-<nanos>.tmpfile withcreate_new(true).custom_flags(libc::O_NOFOLLOW).mode(0o600). Pre-planted symlinks at the target path are refused withELOOP; existing files fail withEEXIST. Either way the write cannot be redirected to an attacker-chosen target.Cargo.tomlpath deps now point at../libenclaveapp/crates/...instead of the (now-deleted) top-level../crates/. libenclaveapp is the single source of truth..metasections rewritten to reflect currently-in-tree mitigations.Depends on
libenclaveapp/crates/tree is where sshenc's path deps now resolve. The consumer-side change is pure path-rewrite; libenclaveapp's PR carries the actual shared-code hardening that sshenc picks up.Test plan
cargo check --workspaceclean.cargo fmt --all -- --checkclean.cargo clippy --workspace --all-targets -- -D warningsclean.cargo test --workspace— 383 tests passing, 0 failing.signal_ready_refuses_preplanted_symlinktest asserts both thesignal_readyerror AND that the decoy target file was NOT created.