Skip to content

fix(core): enforce owner-only Windows ACLs on sensitive files and dirs - #3495

Open
pkhodade-NV wants to merge 2 commits into
windowsfrom
fix/windows-acl-owner-only-permissions
Open

pkhodade-NV wants to merge 2 commits into
windowsfrom
fix/windows-acl-owner-only-permissions

Conversation

@pkhodade-NV

Copy link
Copy Markdown
Collaborator

Summary

  • set_dir_owner_only/set_file_owner_only in openshell-core/src/paths.rs were unconditional no-ops on Windows (documented as such), so locally-stored CLI credential material (mTLS client private key, OIDC/edge tokens, cached SSH keys) and the gateway's key-encryption key relied entirely on inherited NTFS ACLs, with no OpenShell-applied restriction.
  • is_file_permissions_too_open (the one available detection mechanism) was also Unix-only, so there was no way to audit for this after the fact either.

Related Issue

No linked issue — this is a security-sensitive, localized fix to platform-specific permission-hardening helpers already documented as required (the doc comments on these functions state the intent plainly; only the Windows implementation was missing).

Changes

  • paths.rs: set_dir_owner_only/set_file_owner_only now apply a real owner-only Windows ACL via SetEntriesInAclW/SetNamedSecurityInfoW with PROTECTED_DACL_SECURITY_INFORMATION, which strips inherited ACEs from the parent directory — the actual source of the unintended access. Directories additionally propagate the ACE to children (SUB_CONTAINERS_AND_OBJECTS_INHERIT).
  • is_file_permissions_too_open is no longer Unix-only; on Windows it inspects the DACL via GetNamedSecurityInfoW and flags any ACE granted to a trustee other than the current user.
  • Added the windows crate (already a workspace dependency, used by the MXC driver's ETW consumer) as a Windows-only dependency of openshell-core, with the additional Win32_Security*/Win32_Storage_FileSystem/Win32_System_Memory/Win32_System_SystemServices/Win32_System_Threading features needed for the ACL APIs. The unsafe FFI surface is confined to a private windows_acl submodule, matching the existing precedent in openshell-driver-mxc.
  • Added Windows-side test coverage for openshell-driver-db-credstore's key-encryption-key generation (previously #[cfg(unix)]-only, so the gateway's master key-encryption key had no Windows test coverage at all).
  • Fixed a stale "no-op on non-Unix platforms" doc comment in openshell-server/src/persistence/sqlite.rs.
  • Regenerated the two example crates' separate Cargo.lock files (governance-interceptor, supervisor-middleware-content-guard) since both path-depend on openshell-core; diffs are purely additive (new windows-* crate entries only, no other package bumped).

No caller changes were needed in openshell-bootstrap/mtls.rs, edge_token.rs, oidc_token.rs, or openshell-cli/ssh.rs — they already route through the fixed paths.rs functions.

Testing

  • Added Windows-parity unit tests in paths.rs mirroring the four existing Unix tests.
  • Independently verified the resulting ACLs with icacls: a restricted file gets a single owner-only ACE (user:(F)), a restricted directory gets user:(OI)(CI)(F) — no leftover SYSTEM/Administrators/Users entries from inheritance.
  • cargo test -p openshell-driver-db-credstore --target x86_64-pc-windows-msvc generated_key_encryption_key_file_is_owner_only passes on Windows.

Checklist

  • Tests added/updated for the new behavior
  • Docs/comments updated where stale
  • No unrelated changes bundled in

Originally opened as GitLab MR !113 against our internal mirror; re-opened here against windows for upstream review.

set_dir_owner_only/set_file_owner_only were unconditional no-ops on
Windows, so the CLI's mTLS client private key, OIDC/edge tokens, cached
SSH keys, and the gateway's key-encryption key relied entirely on
inherited NTFS ACLs with no OpenShell-applied restriction. Apply an
owner-only DACL via SetEntriesInAclW/SetNamedSecurityInfoW with
PROTECTED_DACL_SECURITY_INFORMATION to strip inherited ACEs, matching
the 0700/0600 guarantee already provided on Unix. is_file_permissions_too_open
now also works on Windows instead of being Unix-only, closing the
detection gap alongside the prevention gap.

Signed-off-by: Prashant Khodade <pkhodade@nvidia.com>
(cherry picked from commit 71560e947f85819efbcddf70ddda94befab62b0b)
has_foreign_trustee conflated a NULL DACL with an unreadable/invalid
ACL and returned Some(false) (not too open) for both. Per the Win32
contract, a NULL DACL means the object grants full access to everyone
-- the most permissive state possible -- so it must be flagged as too
open. Split the null and invalid-ACL branches: null now returns
Some(true), invalid ACL keeps the existing unreadable-ACL fallback
(None, which the caller maps to false via unwrap_or). Adds a
regression test that constructs a real NULL DACL via a
SetNamedSecurityInfoW helper confined to the windows_acl module,
consistent with the existing unsafe-FFI confinement in that module.

Found by CodeRabbit review on MR !113.

Signed-off-by: Prashant Khodade <pkhodade@nvidia.com>
(cherry picked from commit 46e635a4ef1d6937cdb088f46aa85baee3d6ad28)
@copy-pr-bot

copy-pr-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

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