Recommended Windows 11 sandbox architecture for safely running .NET, Node.js and Gradle tools using PSEC/MXC

Balázs 0 Reputation points
2026-10-08T18:46:09.19+00:00

I am developing a local AI-assisted development application for Windows 11 x64. It needs to execute development tools such as .NET, Node.js and Gradle while preventing executed project code from accessing files outside an explicitly approved workspace.

The required security properties are:

  • Read/write access only to an approved workspace and isolated temporary/cache directories.
  • Read-only access to explicitly approved SDK/toolchain directories.
  • No access to arbitrary user files.
  • Network access denied by default.
  • Protection against hardlink, symlink, junction, reparse-point, UNC and path-traversal escapes.
  • Child processes must remain inside the same security boundary.
  • The entire process tree must be reliably terminable.
  • No sensitive environment variables or inherited handles should leak into the sandbox.

I initially tested AppContainer/LPAC. A native worker launches successfully and its AppContainer SID is correct. Own-scope read/write works and some restrictions work correctly, including runtime write denial and child-process denial.

However, host testing also exposed serious problems:

  • A protected canary file outside the workspace could unexpectedly be read.
  • In one test the external canary file could also be deleted.
  • A hardlink inside the permitted scope allowed reading the external target.
  • WSAStartup fails with error 10107 in the LPAC environment.

Because these failures affect the security boundary, project-code execution remains disabled.

I then investigated the Windows Process Security Environment APIs. On the Windows 11 test machine:

IsApiSetImplemented(securityenvironment) = TRUE

QueryProcessSecurityEnvironmentSupport succeeds and returns flags 0xF.

PSEC version 1.1 is reported as supported.

I am therefore considering making PSEC/MXC the primary sandbox provider, combined with a broker-controlled workspace policy and Windows Job Objects for process-tree management, instead of continuing to rely primarily on LPAC.

I would appreciate guidance from someone familiar with the current Windows security APIs:

  1. Is PSEC/MXC an appropriate supported security boundary for this type of local developer-tool sandbox on Windows 11?
  2. What is the recommended way to restrict filesystem access to an approved workspace while still allowing read-only access to .NET/Node/Java SDKs and isolated Temp/cache directories?
  3. How should hardlinks, symbolic links, junctions and other reparse points be handled so that they cannot escape the allowed filesystem boundary?
  4. What is the recommended mechanism for default-deny network isolation in combination with PSEC?
  5. Should Job Objects with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE / TerminateJobObject be used for reliable process-tree termination?
  6. Is AppContainer/LPAC still recommended as an additional layer in this scenario, or would PSEC/MXC normally replace it as the primary execution boundary?
  7. Are there official Microsoft samples or documentation showing a PSEC/MXC sandbox intended for untrusted developer/CLI processes?

I am not looking for a way to weaken Windows security. The goal is to build the sandbox using supported Windows security mechanisms and to verify the boundary before allowing any real project code to execute.

I can provide a minimal native test program and sanitized diagnostic results if needed.

Thank you for any guidance.I am developing a local AI-assisted development application for Windows 11 x64. It needs to execute development tools such as .NET, Node.js and Gradle while preventing executed project code from accessing files outside an explicitly approved workspace.

The required security properties are:

  • Read/write access only to an approved workspace and isolated temporary/cache directories.
  • Read-only access to explicitly approved SDK/toolchain directories.
  • No access to arbitrary user files.
  • Network access denied by default.
  • Protection against hardlink, symlink, junction, reparse-point, UNC and path-traversal escapes.
  • Child processes must remain inside the same security boundary.
  • The entire process tree must be reliably terminable.
  • No sensitive environment variables or inherited handles should leak into the sandbox.

I initially tested AppContainer/LPAC. A native worker launches successfully and its AppContainer SID is correct. Own-scope read/write works and some restrictions work correctly, including runtime write denial and child-process denial.

However, host testing also exposed serious problems:

  • A protected canary file outside the workspace could unexpectedly be read.
  • In one test the external canary file could also be deleted.
  • A hardlink inside the permitted scope allowed reading the external target.
  • WSAStartup fails with error 10107 in the LPAC environment.

Because these failures affect the security boundary, project-code execution remains disabled.

I then investigated the Windows Process Security Environment APIs. On the Windows 11 test machine:

IsApiSetImplemented(securityenvironment) = TRUE

QueryProcessSecurityEnvironmentSupport succeeds and returns flags 0xF.

PSEC version 1.1 is reported as supported.

I am therefore considering making PSEC/MXC the primary sandbox provider, combined with a broker-controlled workspace policy and Windows Job Objects for process-tree management, instead of continuing to rely primarily on LPAC.

I would appreciate guidance from someone familiar with the current Windows security APIs:

  1. Is PSEC/MXC an appropriate supported security boundary for this type of local developer-tool sandbox on Windows 11?
  2. What is the recommended way to restrict filesystem access to an approved workspace while still allowing read-only access to .NET/Node/Java SDKs and isolated Temp/cache directories?
  3. How should hardlinks, symbolic links, junctions and other reparse points be handled so that they cannot escape the allowed filesystem boundary?
  4. What is the recommended mechanism for default-deny network isolation in combination with PSEC?
  5. Should Job Objects with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE / TerminateJobObject be used for reliable process-tree termination?
  6. Is AppContainer/LPAC still recommended as an additional layer in this scenario, or would PSEC/MXC normally replace it as the primary execution boundary?
  7. Are there official Microsoft samples or documentation showing a PSEC/MXC sandbox intended for untrusted developer/CLI processes?

I am not looking for a way to weaken Windows security. The goal is to build the sandbox using supported Windows security mechanisms and to verify the boundary before allowing any real project code to execute.

I can provide a minimal native test program and sanitized diagnostic results if needed.

Thank you for any guidance.

Windows development | Windows API - Win32
0 comments No comments

1 answer

Sort by: Most helpful
  1. Zack Nguyen (WICLOUD CORPORATION) 165 Reputation points Microsoft External Staff Moderator
    2026-10-09T02:51:15.4233333+00:00

    Hi @Balázs  , 

    Thank you for the detailed post and questions. 

    A quick clarification first: PSEC is the OS-level API (processmodel.h), and MXC is the policy/SDK layer built on top of it. According to the MXC OS-version support doc, MXC chooses its isolation tier at runtime. It uses PSEC when the build and an enabled OS contract allow it, and otherwise falls back to AppContainer with DACL-based filesystem rules. Since "PSEC is reported as supported" doesn't prove your sandbox is actually running on PSEC, I'd verify that separately.   

    And for your questions:   

    1. Is PSEC/MXC a suitable, supported boundary for a local developer-tool sandbox? 

    MXC is now generally available as Microsoft's containment layer for agents, generated code and tools (announcement), so the use case fits. I couldn't confirm public documentation of a formal security-boundary or servicing commitment for the process-isolation tier, so I'd treat it as a strong layer that you verify yourself, not a guarantee. If the code you run could be actively hostile, VM-based isolation such as Windows Sandbox or Hyper-V is the more conservative choice. 

    2. The recommended way to restrict filesystem access to an approved workspace while still allowing read-only access to SDKs and isolated Temp/cache directories? 

    • Define the rules through the MXC SDK policy instead of hand-building the PSEC specification. The Learn pages I found list only function signatures. 

    Give read/write access to the workspace and one dedicated temp/cache folder, and mark the SDK/toolchain folders read-only. The exact field names are in the repo's policy docs. 

    Point tool state at the sandboxed cache with TEMP/TMP, NUGET_PACKAGES, DOTNET_CLI_HOME, the npm cache location and GRADLE_USER_HOME. 

    Decode the individual bits of PROCESS_SECURITY_ENVIRONMENT_SUPPORT_FLAGS. The combined value 0xF doesn't say which policy surfaces this build can enforce. 

    For your environment-variable and handle requirements, pass an explicit environment block. Use bInheritHandles = FALSE, or PROC_THREAD_ATTRIBUTE_HANDLE_LIST as described in Creating processes. 

    3. How should hardlinks, symbolic links, junctions and other reparse points be stopped from escaping the allowed boundary? 

    A hardlink can only point to a file on the same volume (Hard links and junctions). Creating one requires WRITE_ATTRIBUTES on the target (MSRC). 

    A hardlink shares the target file's security descriptor. Your hardlink result may therefore simply reflect the target's ACL, so I'd check that first. 

    Outside a sandbox, standard users can create junctions that point at any folder (same MSRC post). 

    Public documentation doesn't currently describe how PSEC path rules treat links, so test them directly. Cover junctions, symlinks, hardlinks, [\?]() paths, UNC and loopback paths (for example \localhost\c$), ..\ traversal, 8.3 short names and alternate data streams. 

    In your broker, scan the workspace before launch for reparse points and for files with a link count above 1. Open files with FILE_FLAG_OPEN_REPARSE_POINT and confirm with GetFinalPathNameByHandle. This approach is race-prone, so use it only as a supplement to the OS-enforced policy. 

    A separate volume or VHD for the workspace removes any cross-volume hardlink targets. 

    4. What's the recommended method to get default-deny networking alongside PSEC? 

    The MXC repo has a networking document. The published documentation doesn't specify how default-deny is enforced at the PSEC tier. I'd confirm it with tests of outbound TCP/UDP, DNS, loopback and SMB. Some tools need loopback even when external network access is blocked (the Gradle daemon is one example), so a blanket "no network" setting may break them. 

    5. Should Job Objects with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE / TerminateJobObject be used for process-tree termination?  

    Yes. Create the child suspended, add it to the job, then resume it. Set JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, disallow breakaway, and call TerminateJobObject for explicit teardown. processmodel.h also lists CloseProcessSecurityEnvironment and CancelProcessSecurityEnvironmentTerminateOnClose, which suggests PSEC has its own terminate-on-close behavior. Only the signatures are documented, so test it before relying on it. 

    6. Should AppContainer/LPAC stay in the design as a second layer, or is PSEC/MXC meant to replace it? 

    The MXC docs describe PSEC and AppContainer as alternative tiers selected at runtime, not as layers stacked together. There isn’t any documentation on combining them, so I wouldn't assume that stacking adds protection. If you want more depth, a dedicated low-privilege account or VM-based isolation is easier to reason about. 

    7. Official Microsoft documentation showing a PSEC/MXC sandbox intended for untrusted developer/CLI processes?  

    Microsoft doesn't currently publish an official sample aimed specifically at untrusted developer or CLI processes. The closest starting points are the processmodel.h reference, the MXC repo (see docs/process-container/guide.md and the examples), and the announcement linked above. 

    On the LPAC results: before ruling LPAC out, confirm that the token really is LPAC by querying TokenIsLessPrivilegedAppContainer. Also check whether the canary file's ACL grants ALL APPLICATION PACKAGES, ALL RESTRICTED APPLICATION PACKAGES or Everyone. Error 10107 is WSASYSCALLFAILURE. It may be related to missing network access.    I hope this helps. 

    If this instruction is applicable to your situation, I would greatly appreciate it if you could follow the instruction here so others experiencing similar behavior can benefit from it as well. 

     Hi @Balázs  , 

    Thank you for the detailed post and questions. 

    A quick clarification first: PSEC is the OS-level API (processmodel.h), and MXC is the policy/SDK layer built on top of it. According to the MXC OS-version support doc, MXC chooses its isolation tier at runtime. It uses PSEC when the build and an enabled OS contract allow it, and otherwise falls back to AppContainer with DACL-based filesystem rules. Since "PSEC is reported as supported" doesn't prove your sandbox is actually running on PSEC, I'd verify that separately.  And for your questions:  

    1. Is PSEC/MXC a suitable, supported boundary for a local developer-tool sandbox? 

    MXC is now generally available as Microsoft's containment layer for agents, generated code and tools (announcement), so the use case fits. I couldn't confirm public documentation of a formal security-boundary or servicing commitment for the process-isolation tier, so I'd treat it as a strong layer that you verify yourself, not a guarantee. If the code you run could be actively hostile, VM-based isolation such as Windows Sandbox or Hyper-V is the more conservative choice. 

    2. The recommended way to restrict filesystem access to an approved workspace while still allowing read-only access to SDKs and isolated Temp/cache directories? 

    Define the rules through the MXC SDK policy instead of hand-building the PSEC specification. The Learn pages I found list only function signatures. 

    Give read/write access to the workspace and one dedicated temp/cache folder, and mark the SDK/toolchain folders read-only. The exact field names are in the repo's policy docs. 

    Point tool state at the sandboxed cache with TEMP/TMP, NUGET_PACKAGES, DOTNET_CLI_HOME, the npm cache location and GRADLE_USER_HOME. 

    Decode the individual bits of PROCESS_SECURITY_ENVIRONMENT_SUPPORT_FLAGS. The combined value 0xF doesn't say which policy surfaces this build can enforce. 

    For your environment-variable and handle requirements, pass an explicit environment block. Use bInheritHandles = FALSE, or PROC_THREAD_ATTRIBUTE_HANDLE_LIST as described in Creating processes. 

    3. How should hardlinks, symbolic links, junctions and other reparse points be stopped from escaping the allowed boundary? 

    A hardlink can only point to a file on the same volume (Hard links and junctions). Creating one requires WRITE_ATTRIBUTES on the target (MSRC). 

    A hardlink shares the target file's security descriptor. Your hardlink result may therefore simply reflect the target's ACL, so I'd check that first. 

    Outside a sandbox, standard users can create junctions that point at any folder (same MSRC post). 

    Public documentation doesn't currently describe how PSEC path rules treat links, so test them directly. Cover junctions, symlinks, hardlinks, [\?] paths, UNC and loopback paths (for example [\localhost\c$]), ..\ traversal, 8.3 short names and alternate data streams. 

    In your broker, scan the workspace before launch for reparse points and for files with a link count above 1. Open files with FILE_FLAG_OPEN_REPARSE_POINT and confirm with GetFinalPathNameByHandle. This approach is race-prone, so use it only as a supplement to the OS-enforced policy. 

    A separate volume or VHD for the workspace removes any cross-volume hardlink targets. 

    4. What's the recommended method to get default-deny networking alongside PSEC? 

    The MXC repo has a networking document. The published documentation doesn't specify how default-deny is enforced at the PSEC tier. I'd confirm it with tests of outbound TCP/UDP, DNS, loopback and SMB. Some tools need loopback even when external network access is blocked (the Gradle daemon is one example), so a blanket "no network" setting may break them. 

    5. Should Job Objects with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE / TerminateJobObject be used for process-tree termination?  

    Yes. Create the child suspended, add it to the job, then resume it. Set JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, disallow breakaway, and call TerminateJobObject for explicit teardown. processmodel.h also lists CloseProcessSecurityEnvironment and CancelProcessSecurityEnvironmentTerminateOnClose, which suggests PSEC has its own terminate-on-close behavior. Only the signatures are documented, so test it before relying on it. 

    6. Should AppContainer/LPAC stay in the design as a second layer, or is PSEC/MXC meant to replace it? 

    The MXC docs describe PSEC and AppContainer as alternative tiers selected at runtime, not as layers stacked together. There isn’t any documentation on combining them, so I wouldn't assume that stacking adds protection. If you want more depth, a dedicated low-privilege account or VM-based isolation is easier to reason about. 

    7. Official Microsoft documentation showing a PSEC/MXC sandbox intended for untrusted developer/CLI processes?  

    Microsoft doesn't currently publish an official sample aimed specifically at untrusted developer or CLI processes. The closest starting points are the processmodel.h reference, the MXC repo (see docs/process-container/guide.md and the examples), and the announcement linked above. 

    On the LPAC results: before ruling LPAC out, confirm that the token really is LPAC by querying TokenIsLessPrivilegedAppContainer. Also check whether the canary file's ACL grants ALL APPLICATION PACKAGES, ALL RESTRICTED APPLICATION PACKAGES or Everyone. Error 10107 is WSASYSCALLFAILURE. It may be related to missing network access.  

    I hope this helps. 

    If this instruction is applicable to your situation, I would greatly appreciate it if you could follow the instruction here so others experiencing similar behavior can benefit from it as well. 

    Was this answer helpful?

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.