Skip to content

fix(network): normalize Windows binary paths (NVBug 6782969) - #3482

Open
prekshivyas wants to merge 2 commits into
NVIDIA:windowsfrom
prekshivyas:fix/nvbug-6782969-windows-binary-path
Open

prekshivyas wants to merge 2 commits into
NVIDIA:windowsfrom
prekshivyas:fix/nvbug-6782969-windows-binary-path

Conversation

@prekshivyas

@prekshivyas prekshivyas commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes NVBug 6782969 on Windows by giving configured network-policy binary paths and runtime executable identities one stable comparison representation before Rego evaluates them. Windows ASCII casing and \// separators are normalized consistently across protobuf/YAML policy loading, L4 authorization, ancestor matching, and the generation-pinned L7 evaluator; the original executable path remains unchanged for filesystem access and SHA-256 identity checks.

Base: NVIDIA/OpenShell@fb2980e077288b61ef03a2e6187e162d158526aa (windows).

Related Issue

  • Internal tracking issue.
  • No public GitHub issue required: localized Windows policy-matching bug.

Root cause

MXC passes its raw command[0] path to the host proxy. Rego compares b.path == exec.path literally, so Windows-equivalent spellings such as C:/WINDOWS/SYSTEM32/CMD.EXE and C:\WINDOWS\System32\cmd.exe did not match. The exact byte-for-byte control matched, proving that endpoint authorization worked and isolating the defect to binary-path representation.

Changes

  • Normalize Windows network binary comparison paths to lowercase ASCII with / separators.
  • Apply the same representation to protobuf and YAML policy data, runtime executable/ancestor inputs, and L7 relay evaluation.
  • Preserve byte-for-byte behavior on non-Windows platforms.
  • Preserve original paths for executable hashing and filesystem access.
  • Add positive protobuf/YAML equivalence tests and a negative different-executable assertion.

Before / after reproduction

Environment: Windows ARM64 (Yukon), Rust 1.95.0 aarch64-pc-windows-msvc, signed ARM64 wxc-exec.exe SHA-256 DDE1C592270E9A659B01DCCAD70362DA7B99FEC114885FA4D625507AA775A503.

The live repro used the real processcontainer backend with egress_proxy = true, allowed example.com:443, and isolated L4 binary identity by omitting L7 protocol fields. The policy used C:/WINDOWS/SYSTEM32/CMD.EXE; command[0] used the equivalent native Windows spelling. A known test-only proxy preflight issue was bypassed; none of those harness edits are committed.

Command:

cargo test -p openshell-driver-mxc --test wxc_exec_real pc_https_egress_reads_injected_ca_bundle -- --ignored --exact --nocapture --test-threads=1
  • Before (fb2980e077..., unmodified path-matching code): FAIL, exit 101; workload exit 7; CONNECT tunnel failed, response 403; scenario 0.50 s (command 16.58 s).
  • After (d7926c8bf..., identical scenario): PASS, exit 0; AgentCompleted; scenario 0.85 s (command 17.07 s).
  • Durable regression before the fix: from_proto_matches_windows_equivalent_binary_path failed because the equivalent binary was denied.
  • Durable regression after the fix: protobuf and YAML equivalence tests pass; a different executable remains denied.

Testing

  • cargo fmt --all -- --check
  • git diff --check
  • cargo test -p openshell-supervisor-network — 1,233 passed, 0 failed, 2 ignored; integration tests 2 passed, 5 environment-dependent tests ignored
  • cargo test -p openshell-driver-mxc --lib — 93 passed
  • Focused Windows path tests — 2 passed
  • Existing cross-layer MCP/L7 identity tests — 3 passed
  • Real-MXC before/after replay above
  • cargo clippy -p openshell-supervisor-network --all-targets — passes; one unchanged dependency warning in openshell-extension-core
  • mise run pre-commit — blocked on this ARM64 host by unrelated tip/toolchain issues: Biome exits 5 without diagnostics; Python 3.14 ARM64 cannot build grpcio-tools because MSVC receives incompatible /std:c++17 and /std:c11; workspace Rust lint separately reaches unchanged openshell-driver-mxc/src/driver.rs:819 and fails -D warnings on unused_self.

Security impact and scope

This remains fail-closed: it broadens equality only for ASCII case and separator differences that refer to the same ordinary Windows path. A genuinely different executable is still denied, command-line paths remain excluded from grants, hashes still use the original file, and non-Windows semantics are unchanged. The static per-sandbox identity model, per-process attribution, and unrelated policy-enforcement behavior are out of scope. Non-ASCII Windows case equivalence is not broadened by this change.

Checklist

  • Follows Conventional Commits
  • Commit is SSH-signed, GitHub-verified, and DCO-signed off
  • Architecture docs not applicable; no public API or architecture change

Match Windows executable identities using a stable case-insensitive, separator-normalized representation across policy data, L4 input, and L7 relay evaluation. Preserve exact matching on other platforms and keep the original path for hashing and filesystem access.

NVBug 6782969

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
@prekshivyas

Copy link
Copy Markdown
Contributor Author

Verification update for NVBug 6782969 (Windows ARM64, real signed MXC):

  • Base: fb2980e077288b61ef03a2e6187e162d158526aa
  • Commit: d7926c8bf3c318f75acf0785756faafc4ea707e7 (SSH signature verified by GitHub)
  • Identity under test: policy C:/WINDOWS/SYSTEM32/CMD.EXE; runtime C:\WINDOWS\System32\cmd.exe
  • Before: workload exit 7, CONNECT tunnel failed, response 403; scenario 0.50 s; command exit 101
  • After, identical real-MXC scenario: AgentCompleted; scenario 0.85 s; command exit 0
  • Negative coverage: powershell.exe remains denied when curl.exe is configured
  • Focused path tests: protobuf + YAML paths both pass
  • Full network package: 1,233 passed, 0 failed, 2 ignored; integration 2 passed, 5 environment-dependent ignored
  • MXC library: 93 passed
  • Formatting/diff checks: pass

The live harness removed L7 protocol fields solely to isolate CONNECT/L4 binary identity and bypassed the known test-only proxy preflight issue; those harness edits are not in this PR. Original executable paths remain in use for hashing/filesystem access, and non-Windows path matching remains exact.

@prekshivyas

Copy link
Copy Markdown
Contributor Author

CI follow-up:

  • Passed: Rust (x86_64-linux, aarch64-linux, aarch64-darwin), Python (amd64, arm64), Go SDK, TypeScript SDK, Cargo Deny, license headers, Markdown, DCO, Helm, dependency review, Actionlint, Zizmor, and policy/deployment gates.
  • Sole failure: mise Lockfile reports the base repository's mise.lock is out of sync with mise.toml.
  • This PR changes neither file (git diff upstream/windows..HEAD -- mise.toml mise.lock is empty), so no unrelated generated lockfile update is included.

The PR remains mergeable and has no review requests outstanding.

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>

@shailendra-nv shailendra-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the Windows path-spelling mismatch reported by NVBug. I am requesting changes because blanket ASCII lowercasing crosses the binary-identity authorization boundary on Windows configurations that opt into case-sensitive lookup. The inline comments also call out the missing L7/native-Windows validation and the public documentation contract.


#[cfg(any(target_os = "windows", test))]
fn windows_network_binary_match_path(path: &str) -> String {
path.replace('\\', "/").to_ascii_lowercase()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comparison is unsafe for Windows directories with case sensitivity enabled. NTFS can treat Trusted.exe and trusted.exe as distinct files there, but to_ascii_lowercase() grants them the same network/provider permissions. The runtime identity ultimately comes from the MXC command[0], so a case-distinct executable can match a policy intended for another file. Please make equality reflect actual Windows file identity (or detect these paths and fail closed), add a negative test using a case-sensitive directory, and handle \\?\ verbatim paths explicitly rather than rewriting them. References: Windows case sensitivity and verbatim path behavior.

.binary
.as_ref()
.map(|path| path.to_string_lossy().into_owned())
.map(|path| crate::opa::network_binary_match_path(path))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please validate this L7 path directly. The new tests exercise OpaEngine::evaluate_network (the L4 decision) only; none sends an HTTP request through this changed relay with mixed casing/separators. The PR also lacks the test:windows label, so its native Windows x64 and ARM64 PR jobs are currently skipped. Add a mixed-case/separator L7 regression test and run both native Windows jobs before merge.

/// Normalizing both policy data and runtime input prevents equivalent spellings from
/// being denied while leaving the original path intact for filesystem access and
/// executable hashing. Other platforms retain exact path matching.
pub(crate) fn network_binary_match_path(path: &Path) -> String {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces an externally observable policy-matching contract, but the schema and security guidance remain unchanged. Please document the Windows normalization behavior—including ASCII-only case folding, separator handling, and the chosen behavior for case-sensitive/verbatim paths—in docs/reference/policy-schema.mdx and docs/security/best-practices.mdx. Users and policy-authoring agents rely on the published schema as the source of truth.

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.

2 participants