Internals & Access Control

A deep dive into how Sandy manipulates DACLs, SIDs, and ACL Pipelines to achieve perfect, leak-free isolation in both AppContainer and Restricted Token modes.

DACLs, SIDs, and Grants

The fundamentals of the Windows Security Model and Sandy's per-instance isolation architecture.

Security Identifiers (SID)

A SID is a unique text or binary string representing a user or group. Both Sandbox modes rely on unique, per-instance SIDs, not a shared identity. This guarantees zero crossover between concurrent sandboxes.

  • AppContainer: Unique profile SID (e.g., S-1-15-2-<uuid>)
  • Restricted Token: Unique third-party SID under SECURITY_RESOURCE_MANAGER_AUTHORITY (e.g., S-1-9-<uuid>)

DACLs and ACEs

Every file and folder on Windows has a Security Descriptor containing a DACL (Discretionary Access Control List). A DACL is a list of Access Control Entries (ACEs). An ACE binds a SID to a Permission Mask (e.g., "Allow S-1-9-123 to Read").

Access Check Interaction

Access Token (Thread)
  • 👤 User SID
  • 👥 Group SIDs
  • 🔑 Privileges
Access Check Matches SIDs to ACEs
Security Descriptor (Object)
  • 👑 Owner SID
  • 🛡️ DACL
    • ACE Allow
    • ACE Deny

The ACL Pipelines

Sandy translates your [allow.*] rules into ACEs. [deny.*] is only supported in Restricted Token mode — AppContainer mode rejects deny rules at config validation time.

⚠️ AppContainer: Allow-Only Model

The Windows Kernel silently ignores standard DENY_ACCESS ACEs when evaluating access for an AppContainer SID. Sandy therefore rejects [deny.*] for AppContainer mode at config validation time (exit 128). Use Restricted Token mode for deny rules.—Mask Reduction—for AC.

Case 1: Single Item Allow

[allow.deep]
read = ['C:\Data']
PipelineAction
Restricted TokenInjects an ACCESS_ALLOWED_ACE for S-1-9-... with the Read mask. Done.
AppContainerInjects an ACCESS_ALLOWED_ACE for S-1-15-2-... with the Read mask. Done.
[ DACL for C:\Data ]
1. Allow SYSTEM (Full Control)
2. Allow Administrators (Full Control)
3. Allow Sandy_SID (Read)

Case 2: Single Item Mixed Grants

[allow.deep]
all = ['C:\Data']

[deny.deep]
write = ['C:\Data']
PipelineAction
Restricted TokenInjects a DENY_ACCESS_ACE (Write) before the ACCESS_ALLOWED_ACE (All). Windows evaluates Deny first.
AppContainerSince DENY_ACCESS is ignored, Sandy performs Mask Reduction. It subtracts Write bits from the All mask (preserving shared Read bits like SYNCHRONIZE), and injects a single ACCESS_ALLOWED_ACE with the resulting (All - Write) mask.
[ RT DACL for C:\Data ]
1. Deny Sandy_SID (Write)
2. Allow Sandy_SID (All)

[ AC DACL for C:\Data ]
1. Allow Sandy_SID (All minus Write bits)

Case 3: Overlapping Hierarchies

[allow.deep]
all = ['C:\Data']

[deny.deep]
write = ['C:\Data\Temp']

The C:\Data grant uses TreeSetNamedSecurityInfoW to recursively propagate inheritable ACEs to all child folders, including \Temp. Sandy then targets \Temp explicitly.

PipelineAction
Restricted TokenPlaces an explicit DENY_ACCESS_ACE (Write) on \Temp. Because Windows evaluates explicit ACEs before inherited ACEs, Deny Write wins over the inherited Allow All.
AppContainerReplaces the DACL on \Temp with the reduced mask (All - Write), but applies it with the PROTECTED_DACL_SECURITY_INFORMATION flag to sever inheritance. This prevents the parent's Allow All from flowing back down and overriding the reduced mask.
💡 The Overlap Cleanup Pipeline

If you specify an allow inside a deny (e.g., Allow on \Temp\Cache), the ACL Pipeline sorts paths by depth and applies them shallow-to-deep. When applying the deepest allow, it actively scrubs inherited sandbox deny/reduced ACEs off the target subtree to ensure the allow rule wins definitively over the parent deny.

Sandbox Cleanup

Because Sandy leverages the native Windows Security Model, the sandbox has no background daemon and requires meticulous environment restoration after execution.

Standard Clean Exits

When the sandbox process finishes normally (exit code 0), Sandy meticulously unwinds all isolation state via a specialized RAII Pipeline.

  • DACL Restoration: Sandy doesn't wipe ACLs. It loops through every grant, searches the DACL for ACEs matching the instance's unique SID, and precisely deletes them. Native permissions remain untouched.
  • Profile Cleanup: The temporary AppContainer profile and all mapping rules from the kernel's firewall (WFP) are cleanly removed.
  • Persistent State Clear: The registry entries tracking the grants for this instance ID are gracefully deleted to mark the session entirely clean.
  • Loopback Cleanup: If UWP loopback exemptions were granted, they are removed from the AppContainer.

Irregular Exits & Resilience

What happens if the power dies, or a user runs taskkill /f /im sandy.exe? The RAII cleanup is completely bypassed.

  • Durable Recovery Metadata: Sandy records the minimum ownership metadata needed to recover transient run state after abnormal termination. Profile-owned state is tracked separately from run-owned state so recovery can distinguish durable identities from crashed one-shot runs.
  • Detection & Warning: A new instance audits stale run metadata, orphaned cleanup tasks, and transient containers at startup, then reports what was recovered or still needs attention.
  • Manual Recovery: Running sandy.exe --cleanup performs the same recovery work on demand: remove orphaned run ACLs, loopback state, scheduled tasks, and transient AppContainer profiles while preserving live runs and durable saved profiles.
  • Scheduled Fallback: Each run also registers a per-instance logon task that invokes --cleanup. Sandy deletes its own task on clean exit; if the process or machine dies unexpectedly, the task provides a later self-healing path on the next logon.

References

For further understanding of the Windows Security Model, consult the official documentation: