Sandbox Modes

Detailed comparison of AppContainer (AC / LPAC) and Restricted Token — architecture, capabilities, constraints, and how to choose the right mode for your use case.

Architecture Overview

Sandy supports two fundamentally different Windows sandbox mechanisms. Transient runs use per-instance identities, while saved profiles preserve one durable identity across repeated runs.

AppContainer AC

Uses the same kernel-level isolation as UWP apps and Microsoft Edge. Each instance generates a UUID-based profile (Sandy_<uuid>) and receives a unique SID (S-1-15-2-*) from CreateAppContainerProfile.

  • Isolated object namespace — invisible to other processes
  • Configurable network isolation (internet, LAN/localhost via unified lan key)
  • Private registry hive — no host registry access
  • LPAC mode opts out of App. Packages — explicit grants only
  • DENY implemented via allow-mask reduction (kernel ignores DENY ACEs for AC SIDs)
  • Strongest isolation layer available to unprivileged users
  • No named pipe creation
  • No COM/RPC server access
  • Network must be explicitly enabled
Restricted Token RT

Creates a restricted token with a per-instance SID (S-1-9-*) under the SECURITY_RESOURCE_MANAGER_AUTHORITY. The old fixed S-1-5-12 SID remains in restricting SIDs for system object access only. Configurable integrity level: low or medium.

  • Real DENY_ACCESS ACEs — kernel evaluates deny-before-allow
  • Named pipe support (named_pipes = true)
  • Configurable desktop access (desktop = true by default)
  • COM/RPC server access for complex applications
  • Configurable registry grants ([registry] section)
  • Strict mode (strict = true): exclude user SID from restricting list for grant-only access model
  • Medium integrity for wider app compatibility
  • Network always allowed (no config needed)
  • Sharing host object namespace
  • Low integrity: user profile writes blocked by MIC

Identity & Isolation Layers

Both modes generate a unique SID per instance — no two concurrent Sandy instances share security identifiers, even if they grant access to the same paths.

AspectAC / LPACRestricted LowRestricted Medium
SID authorityS-1-15-2-* (AppContainer)S-1-9-* (Resource Manager)S-1-9-* (Resource Manager)
SID uniquenessUUID-derived, per-profileGUID-derived, per-instanceGUID-derived, per-instance
Integrity level🔒 Low (fixed)🔒 Low (fixed)🔒 Medium (fixed)
Object namespace🔒 Isolated, private🔒 Shared with host🔒 Shared with host
Privilege stripping🔒 All stripped🔒 All except SeChangeNotify🔒 All except SeChangeNotify
Elevation Blocked Blocked Blocked
Isolation layers2: SID + namespace2: SIDs + integrity1: SIDs only
DENY ACE mechanismMask reduction (kernel ignores DENY ACEs)Real DENY_ACCESS ACEsReal DENY_ACCESS ACEs
Pre-launch token validationN/A Integrity verified (0x1000) Integrity verified (0x2000)
⚠️ Critical: AC deny is mask reduction, not DENY ACEs

The Windows kernel ignores DENY_ACCESS ACEs for AppContainer SIDs — this is a kernel property, not a design choice. Sandy implements AC deny by computing reducedMask = existingMask & ~denyOnlyBits and setting PROTECTED_DACL on denied paths to prevent re-inheritance from parent grants. Restricted Token mode uses real DENY_ACCESS ACEs, which the kernel evaluates deny-before-allow as expected.

Capability Comparison

All sandbox aspects across all three operating modes.

🔒 Fixed / always enforced Blocked Always allowed ⚙️ Configurable ⚠️ Warning / caveat
AspectAC / LPACRestricted LowRestricted Medium
Identity & Process
Integrity level🔒 Low🔒 Low🔒 Medium
Object namespace🔒 Isolated🔒 Shared🔒 Shared
Elevation Blocked Blocked Blocked
Network
Internet / outbound⚙️ network Allowed Allowed
LAN access⚙️ lan Allowed Allowed
Localhost / loopback⚙️ lan = 'with localhost' ¹ Allowed Allowed
IPC & Interop
Named pipes Blocked⚙️ named_pipes⚙️ named_pipes
Desktop access Inherited⚙️ desktop⚙️ desktop
COM / RPC servers Blocked Allowed Allowed
Window messages (UIPI) Blocked Blocked Allowed
Scheduled tasks Blocked Blocked Allowed
File System
System dir reads (Windows, PF) Via App. Packages ³ Allowed Allowed
System dir writes Blocked Blocked Blocked
User profile reads⚙️ [allow.*] Allowed Allowed
User profile writes⚙️ [allow.*]⚙️ [allow.*] ² Allowed
Custom file/folder grants⚙️ [allow.*]⚙️ [allow.*]⚙️ [allow.*]
DLL/API set resolution Allowed ³⚠️ May break apps Allowed
Registry
Registry reads Private hive Allowed Allowed
HKCU writes Blocked Blocked Allowed
HKLM writes Blocked Blocked Blocked
Custom registry grants N/A⚙️ [registry]⚙️ [registry]
Process & I/O
Child processes⚙️ child_processes⚙️ child_processes⚙️ child_processes
Stdin⚙️ stdin⚙️ stdin⚙️ stdin
Clipboard read⚙️ clipboard_read⚙️ clipboard_read⚙️ clipboard_read
Clipboard write⚙️ clipboard_write⚙️ clipboard_write⚙️ clipboard_write
Environment⚙️ inherit⚙️ inherit⚙️ inherit
Resource limits⚙️ [limit]⚙️ [limit]⚙️ [limit]

¹ Loopback (AppContainer) is enabled with lan = 'with localhost'. Sandy uses CheckNetIsolation.exe to manage a per-instance loopback exemption. Loopback always implies LAN — there is no localhost-only capability in Windows.
² Restricted Low writes to medium-integrity folders (most of C:\Users) are blocked by Mandatory Integrity Control even with [allow.*] grants. Use AppData\LocalLow or Restricted Medium for user profile writes.
³ LPAC opts out of ALL APPLICATION PACKAGES (S-1-15-2-1). Windows system dirs (C:\Windows, System32, Program Files) carry ALL RESTRICTED APPLICATION PACKAGES (S-1-15-2-2) ACEs on Win10+ and are readable by LPAC. However, user-installed application dirs (e.g. Python under AppData\Local\Programs) typically carry only the APP ACE without ARAP, making them invisible to LPAC unless explicitly granted via [allow.*]. DLLs from those paths also need explicit execute grants.

Choosing a Mode

Both modes share one run pipeline, but ownership differs: transient runs clean up their grants on exit, while saved profiles keep profile-owned grants and container state until --delete-profile.

🛡️

Use AppContainer / LPAC when…

  • You need network isolation (control internet/LAN separately)
  • Maximum isolation is the priority
  • Running Python scripts, CLI tools, simple agents
  • App does not need named pipes or COM
  • Running agentic AI workflows
🔧

Use Restricted Low when…

  • App needs named pipes (Flutter, Chromium, Bun IPC)
  • COM/RPC servers required
  • Maximum restriction still desired
  • App only writes to AppData\LocalLow
  • Security research with network access
⚖️

Use Restricted Medium when…

  • App needs named pipes AND user-profile writes
  • Interactive CLI tools (e.g. Claude Code, git)
  • COM, scheduled tasks, or window messages needed
  • App breaks under Low integrity and AC doesn't work
  • Trusted app, narrower threat surface needed
💡 Not sure? Start transient, then promote to a profile

Use -c or -s while you are discovering the minimum grant set. Once the policy is stable, create a saved profile with --create-profile so repeated runs reuse one durable sandbox identity.

Cleanup & Resilience

Run-scoped state is recovered on clean exit and after crashes, but durable profile state intentionally survives normal runs. Cleanup must therefore distinguish run-owned resources from profile-owned resources.

ResourceAC / LPACRestricted (any)
ACL grantsRemove ACEs for instance SID (S-1-15-2-*)Remove ACEs for instance SID (S-1-9-*)
AppContainer profileDeleted: Sandy_<uuid>N/A
Loopback exemptionRemoved via CheckNetIsolation.exeN/A
Desktop / WinSta ACLsN/AACEs removed by SID (if desktop = true)
Registry grantsN/AACEs removed by SID
Instance subkeyDeleted from HKCU\Software\Sandy\Grants\<UUID>
Scheduled taskDeleted: SandyCleanup_<UUID>
✅ Multi-instance safe

Because each instance uses a unique SID, cleanup is ACE-level: RemoveSidFromDacl removes only ACEs matching the instance's SID. Concurrent instances never interfere — one instance's cleanup cannot accidentally revoke another's grants.