windows

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

README

Windows sandbox internals

This package owns Windows mechanism selection, path-to-ACL policy compilation, restricted-token construction, Job and handle confinement, elevated installation, the LocalSystem broker, account-scoped firewall policy, private desktops, and crash recovery. The public facade is the root sandbox package; consumers must not import this package.

Tiers and guarantees

RestrictedToken runs as the interactive user with a restricted token, a Job, an explicit handle list, UI restrictions, and temporary ACL restrictions. Those mechanisms are defense in depth: Windows user-session brokers remain reachable, so v1 honestly reports LevelNone and only the executor-owned GuaranteeEnvScrub.

Elevated uses installation-owned local accounts and a LocalSystem broker. When every prerequisite and requested policy axis verifies, it may report:

Guarantee Elevated v1 condition
process boundary restricted account token, private desktop, no-breakaway Job, and explicit handle list
write boundary identity-bound ACL projection succeeds
read boundary local NTFS/ReFS roots plus the approved runtime baseline
environment scrub executor-owned environment construction
network boundary verified offline-account firewall posture
address network the selected route also earns address enforcement
resource limits every requested Job limit reads back correctly
target network authenticated, target-scoped loopback proxy

Auto tries verified elevated setup when the profile requires guarantees the restricted tier cannot claim. It falls back only when setup is absent, never when installed state is stale or corrupt. Explicit Elevated never falls back.

Elevated runtime readiness requires a protected approved evidence artifact from the exact-token/runtime matrix on the matching supported disposable Windows 11 or Windows Server worker. Setup imports that artifact and inspection revalidates its source revision, clean-build state, Go toolchain, platform/filesystem facts, and all required runtime rows. Missing, skipped, stale, or mismatched evidence fails closed and must not be described as production-ready.

Trust boundary

Elevated setup installs a hash-pinned sandbox-host.exe, a protected manifest, two installation-owned accounts, DPAPI-protected credentials, a LocalSystem service, firewall rules, and a durable lease journal. The host application never receives account passwords or an unrestricted account token.

The broker authenticates named-pipe clients from kernel process/token facts and binds each request to PID, process creation time, a held process handle, and a one-shot nonce. Paths are descriptive; duplicated object handles and stable file identity are authority. The broker journals an exact ACL mutation before applying it, issues only a fully restricted account token into the bound client, and removes leases on release, disconnect, service restart, or reconciliation.

Cleanup invariants

  • ACL cleanup removes only the byte-identical ACE occurrence recorded for an owned lease and preserves unrelated and pre-existing ACEs.
  • Journal recovery completes before the broker serves status, lease, or token requests. A corrupt non-tail record fails closed.
  • process cancellation and executor close terminate the Job and close proxy activity, desktop objects, handles, and live leases.
  • setup rollback and removal act only on manifest-pinned SIDs, service identity, firewall rule identities, slot paths, and credential files.
  • removal refuses ambiguous or unowned state; it never adopts a matching name as proof of ownership.

Always run elevated tests on a disposable worker and inspect for residual accounts, services, firewall rules, journal entries, processes, and meaningful lease ACEs in an unconditional cleanup step.

Supported path classes

Windows v1 accepts canonical local DOS-drive paths on NTFS or ReFS, which provide persistent DACLs and stable file IDs. It rejects UNC/SMB, FAT/exFAT, drive-relative paths, alternate streams, object-manager and device namespaces, GLOBALROOT, named pipes, and raw devices. Broad host:* filesystem grants, nonexistent exact targets, exact directories, and multi-link exact files are unsupported in v1 and fail closed.

File map

  • types.go, setup_*, manifest.go, and host_install_windows.go: public configuration vocabulary and transactional installation/removal.
  • backend_*, restricted_*, and elevated_*: tier selection, policy compilation, leases, and launch composition.
  • token_windows.go, job_*, handlelist_windows.go, desktop_windows.go, and runner_windows.go: child-process confinement.
  • acl_*, path_*, and grant_*: identity-bound filesystem policy and temporary ACL projection.
  • protocol.go, pipe_windows.go, broker_*, and lease_journal_windows.go: authenticated broker and recovery.
  • account_windows.go, dpapi_windows.go, service_windows.go, and firewall_*: installation-owned machine state.
  • network_windows.go, ports_*, and proxy integration: guarded loopback transport and account-scoped egress.

Verification

Pure and cross-build checks can run on any development host:

go test ./internal/windows
./scripts/test-windows-build.sh

The restricted live suites require a standard-user disposable Windows worker:

$env:SANDBOX_WINDOWS_DISPOSABLE_RESTRICTED_TEST = "1"
$env:SANDBOX_WINDOWS_DISPOSABLE_ACL_TEST = "1"
go test -race -count=1 ./internal/windows ./internal/exec `
  -run 'RestrictedDisposable|RestrictedBrokerEscape|WindowsRestricted'

The elevated acceptance suite requires an elevated disposable Windows 11 or Windows Server worker and an unconditional setup-removal/residue check. A skip is not evidence that a requested mechanism works. At present the missing approved runtime baseline means this live gate is outstanding.

Documentation

Overview

Package windows owns the configuration and setup vocabulary for the Windows sandbox backends. Its public data types remain available on every platform.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrSetupRequired error = unavailableError("sandbox: Windows elevated setup required")
	ErrSetupStale    error = unavailableError("sandbox: Windows elevated setup is stale")

	ErrElevationRequired = errors.New("sandbox: Windows setup requires elevation")
)
View Source
var (
	// ErrJobCompletionWait marks a failure to prove that a terminated Job has
	// reached zero active processes. Callers must escalate containment rather
	// than treating this as successful cleanup.
	ErrJobCompletionWait = errors.New("sandbox: Windows Job completion wait failed")
)
View Source
var ErrRestrictedTargetChanged = errors.New("sandbox: restricted cleanup target changed")

ErrRestrictedTargetChanged means cleanup found a different filesystem object at the recorded path. The journal must never use a path alone as authority.

View Source
var ErrSIDReuse = errors.New("sandbox: one-shot Windows SID entropy reused")

Functions

func OpenRestrictedJournalAndSweep

func OpenRestrictedJournalAndSweep(stableScratchRoot string, cleaner RestrictedJournalCleaner) (*RestrictedJournal, RestrictedSweepReport, error)

OpenRestrictedJournalAndSweep is the construction path used by the backend: recovery runs before a fresh restricted SID or ACL lease is created.

func Remove

func Setup

func ValidateConfig

func ValidateConfig(config Config) error

ValidateConfig rejects Windows-only executor settings on non-Windows hosts.

Types

type ACERole

type ACERole uint8
const (
	ACERoleUnknown ACERole = iota
	ACERoleRestrictingAllow
	ACERoleRestrictingDeny
	ACERoleAccountNormal
)

type ACEType

type ACEType uint8
const (
	ACEAllow ACEType = iota
	ACEDeny
)

type ACLACE

type ACLACE struct {
	Type        ACEType
	Access      ACLAccess
	Inheritable bool
	Bytes       []byte
}

type ACLACEExpectation

type ACLACEExpectation struct {
	Role      ACERole
	SID       SID
	Access    ACLAccess
	Inherited bool
}

ACLACEExpectation describes an ACE that read-back must find logically. An inherited ACE's raw header is produced by Windows, so it is matched by role, SID, access, and inheritance rather than by pretending its bytes are stable.

type ACLAccess

type ACLAccess uint8
const (
	ACLRead ACLAccess = 1 << iota
	ACLExecute
	ACLWrite
)

type ACLLeaseID

type ACLLeaseID [16]byte

type ACLMutation

type ACLMutation struct {
	// contains filtered or unexported fields
}

func (ACLMutation) ACE

func (mutation ACLMutation) ACE() ACLACE

func (ACLMutation) Object

func (mutation ACLMutation) Object() ACLObjectIdentity

func (ACLMutation) Rollback

func (mutation ACLMutation) Rollback() ACLRollbackMetadata

type ACLObjectIdentity

type ACLObjectIdentity struct {
	VolumeSerial uint64
	FileID       [16]byte
	Kind         ACLObjectKind
	ReparseTag   uint32
	LinkCount    uint32
}

ACLObjectIdentity is the complete identity required to verify an object again immediately before mutation. A path string is deliberately absent.

type ACLObjectKind

type ACLObjectKind uint8
const (
	ACLObjectUnknown ACLObjectKind = iota
	ACLObjectFile
	ACLObjectDirectory
	ACLObjectReparsePoint
)

type ACLPlan

type ACLPlan struct {
	// contains filtered or unexported fields
}

ACLPlan is immutable after construction. Every slice-returning accessor returns a defensive copy so journal/apply code cannot accidentally alter it.

func BuildACLPlan

func BuildACLPlan(request ACLPlanRequest) (ACLPlan, error)

func (ACLPlan) Mutations

func (plan ACLPlan) Mutations() []ACLMutation

func (ACLPlan) Narrowings

func (plan ACLPlan) Narrowings() []string

func (ACLPlan) RootIdentity

func (plan ACLPlan) RootIdentity() ACLObjectIdentity

RootIdentity returns the root object encoded by this plan.

func (ACLPlan) SID

func (plan ACLPlan) SID() SID

func (ACLPlan) SkippedReparsePoints

func (plan ACLPlan) SkippedReparsePoints() []ACLObjectIdentity

func (ACLPlan) ValidationTargets

func (plan ACLPlan) ValidationTargets() []ACLValidationTarget

ValidationTargets retains every ordinary identity enumerated for the plan, even when it needs no explicit mutation. Task 10 pairs each target with its retained OS handle by complete identity and verifies inherited propagation.

type ACLPlanEntry

type ACLPlanEntry struct {
	Object ACLObjectIdentity
	Deny   ACLAccess
}

type ACLPlanRequest

type ACLPlanRequest struct {
	LeaseID ACLLeaseID
	SID     SID
	Scope   ACLScope
	Access  ACLAccess
	Root    ACLObjectIdentity
	Entries []ACLPlanEntry
}

ACLPlanRequest consumes an already no-follow-enumerated object set. Planning performs no filesystem traversal and skips every reparse entry.

type ACLRollbackMetadata

type ACLRollbackMetadata struct {
	LeaseID ACLLeaseID
	Role    ACERole
	SID     SID
	ACEHash [sha256.Size]byte
}

ACLRollbackMetadata identifies one lease-owned ACE without assuming it was absent before apply. Task 10 must capture the baseline occurrence count of identical ACE bytes atomically with read/apply; rollback removes only the lease's added occurrence above that baseline.

type ACLScope

type ACLScope uint8
const (
	ACLScopeUnknown ACLScope = iota
	ACLScopeExact
	ACLScopeTree
)

type ACLValidationTarget

type ACLValidationTarget struct {
	Object   ACLObjectIdentity
	Required []ACLACEExpectation
}

type Config

type Config struct {
	Mode      SandboxMode
	StateRoot string
}

Config contains the Windows backend settings attached to an executor set.

type OneShotSIDGenerator

type OneShotSIDGenerator struct {
	// contains filtered or unexported fields
}

OneShotSIDGenerator creates grant SIDs from injected cryptographic entropy. Never-reuse authority belongs to the injected atomic retirement store, not this process, so separate generators and process restarts cannot race reuse.

func NewOneShotSIDGenerator

func NewOneShotSIDGenerator(source io.Reader, store SIDRetirementStore) (*OneShotSIDGenerator, error)

func (*OneShotSIDGenerator) Next

func (generator *OneShotSIDGenerator) Next() (SID, error)

type RestrictedCleanupRecord

type RestrictedCleanupRecord struct {
	Path                string
	Object              ACLObjectIdentity
	Rollback            ACLRollbackMetadata
	ACE                 []byte
	BaselineOccurrences uint32
}

RestrictedCleanupRecord is cleanup authority, never access authority. An ACL implementation may use it only to remove the exact lease-owned occurrence above BaselineOccurrences after revalidating Object on a retained handle.

type RestrictedJournal

type RestrictedJournal struct {
	// contains filtered or unexported fields
}

RestrictedJournal is rooted outside any executor-owned temporary subtree. Separate instances coordinate through create-exclusive files; no in-memory check-then-write is used for SID retirement.

func OpenRestrictedJournal

func OpenRestrictedJournal(stableScratchRoot string) (*RestrictedJournal, error)

OpenRestrictedJournal creates the durable store below stableScratchRoot. Construction is deliberately separate from Sweep so callers control the handle-bound cleanup implementation and can report retained cleanup loss.

func (*RestrictedJournal) Close

func (j *RestrictedJournal) Close() error

Close releases the retained directory handles. It is safe to call more than once and waits for in-flight journal operations to finish.

func (*RestrictedJournal) CompleteCleanup

func (j *RestrictedJournal) CompleteCleanup(key string) error

CompleteCleanup removes a cleanup record only after its exact ACE is known absent. A missing record is tolerated because the untrusted child may delete journal data; the corresponding SID remains permanently retired.

func (*RestrictedJournal) PrepareMutation

func (j *RestrictedJournal) PrepareMutation(record RestrictedCleanupRecord) (string, error)

PrepareMutation durably records cleanup before a caller changes a DACL. The returned opaque key is passed to CompleteCleanup only after read-back proves the recorded ACE absent.

func (*RestrictedJournal) Prune

func (j *RestrictedJournal) Prune(pruner RestrictedPruner) error

Prune asks a safe enumerator to remove only exact restricting allows for SIDs durably retired by this store. Denies are intentionally excluded because the caller-writable retirement store cannot prove that removing one is harmless.

func (*RestrictedJournal) RetireSID

func (j *RestrictedJournal) RetireSID(sid SID) (bool, error)

RetireSID atomically and durably records a transient executor or one-shot SID before issuance. Installation SIDs are persistent names and are never valid restricted-tier cleanup capabilities.

func (*RestrictedJournal) Sweep

Sweep attempts cleanup for every valid record. Corrupt or concurrently deleted records are tolerated as cleanup loss. A target mismatch or a false cleaner result retains the record and cannot authorize any access.

type RestrictedJournalCleaner

type RestrictedJournalCleaner interface {
	RemoveRestrictedAllowACE(RestrictedCleanupRecord) (removed bool, err error)
}

RestrictedJournalCleaner is the handle-bound half of crash recovery. Sweep deliberately supplies restricting allows only: removing one can only narrow access, even when every byte of the caller-writable journal was forged. Deny cleanup requires live, independently trusted lease state and is never authorized by this journal. The implementation must re-open without following links, compare the complete object identity, and remove only the recorded ACE occurrence. A false result retains the record. Cleaners may call other journal operations, but must not call Close from the callback.

type RestrictedPruner

type RestrictedPruner interface {
	PruneRestrictedACEs(func(SID, ACERole, []byte) bool) error
}

RestrictedPruner may opportunistically remove exact allow ACEs for retired SIDs while it performs an independently safe, handle-bound tree enumeration. Journal data is supplied only as removal authority. Pruners may call other journal operations, but must not call Close while PruneRestrictedACEs is active because Close deliberately waits for the callback to return.

type RestrictedRuntime

type RestrictedRuntime struct {
	// contains filtered or unexported fields
}

RestrictedRuntime coordinates crash-recovery state for every restricted backend owned by one ExecutorSet. Construction is side-effect free; the platform implementation opens and sweeps its journal lazily at most once.

func AcquireRestrictedRuntime

func AcquireRestrictedRuntime(scratchRoot string) (*RestrictedRuntime, func() error)

AcquireRestrictedRuntime is a side-effect-free portability stub. Non-Windows platform selection rejects Windows mechanisms before the runtime is used.

func NewRestrictedRuntime

func NewRestrictedRuntime(scratchRoot string) *RestrictedRuntime

NewRestrictedRuntime constructs one unregistered coordinator, primarily for focused package tests. ExecutorSet construction uses AcquireRestrictedRuntime so concurrent same-root sets share a live coordinator.

func (*RestrictedRuntime) Close

func (runtime *RestrictedRuntime) Close() error

Close releases journal handles owned by a directly constructed runtime. ExecutorSet callers normally use the idempotent release returned by AcquireRestrictedRuntime instead.

type RestrictedSweepReport

type RestrictedSweepReport struct {
	Removed  int
	Retained int
	Corrupt  int
}

type SID

type SID struct {
	// contains filtered or unexported fields
}

SID is a module-issued private Windows trustee SID. Its representation and role are intentionally closed so callers cannot convert arbitrary text into a principal accepted by the token or ACL boundary.

func ExecutorSID

func ExecutorSID(installationID, executorID string) (SID, error)

ExecutorSID deterministically names one executor within an installation.

func InstallationSID

func InstallationSID(installationID string) (SID, error)

InstallationSID deterministically names installation-owned runtime objects.

func (SID) String

func (sid SID) String() string

type SIDRetirementStore

type SIDRetirementStore interface {
	RetireSID(SID) (retired bool, err error)
}

SIDRetirementStore atomically retires a SID before it is issued. It returns true only for the first retirement. Task 10 provides the durable journal- backed implementation; callers must never implement this as check-then-put.

type SandboxMode

type SandboxMode uint8

SandboxMode selects the Windows confinement tier.

const (
	Auto SandboxMode = iota
	RestrictedToken
	Elevated
)

type SetupConfig

type SetupConfig struct {
	InstallationID string
	StateRoot      string
	HostBinary     string
	// RuntimeEvidencePath names the reviewed Task 5 evidence artifact to
	// import into the protected installation. Setup never treats an
	// environment variable or a boolean flag as runtime approval.
	RuntimeEvidencePath string
	ProxyPorts          []uint16
}

SetupConfig identifies one elevated Windows sandbox installation.

type SetupProblem

type SetupProblem struct {
	Code     WindowsSetupProblemCode
	Resource string
	Path     string
	Port     uint16
	PID      uint32
	Detail   string
}

SetupProblem describes one problem found while inspecting setup state. Detail is diagnostic text, not a stable API.

type SetupStatus

type SetupStatus struct {
	Ready          bool
	Version        uint32
	InstallationID string
	OwnerSID       string
	OfflineAccount string
	OnlineAccount  string
	ProxyPorts     []uint16
	Problems       []SetupProblem
}

SetupStatus reports the inspected state of one Windows installation.

type WindowsSetupProblemCode

type WindowsSetupProblemCode uint16

WindowsSetupProblemCode identifies one stable setup inspection problem.

const (
	SetupProblemUnknown WindowsSetupProblemCode = iota
	SetupProblemManifestMissing
	SetupProblemOwnerMismatch
	SetupProblemHostBinaryStale
	SetupProblemServiceUnavailable
	SetupProblemAccountMissing
	SetupProblemCredentialUnavailable
	SetupProblemFirewallOverridden
	SetupProblemFirewallRuleChanged
	SetupProblemPortInUse
	SetupProblemRuntimeBaselineGap
	SetupProblemLeaseRecoveryPending
	SetupProblemProtocolMismatch
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL