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.