Skip to main content

Module runner_root_access

Module runner_root_access 

Source
Expand description

Who may write inside the platform default runner root, and how that is established.

runner_root decides where runner workspaces go and deliberately mutates nothing: 02-target-architecture.md keeps “directory creation and the narrowly scoped default-root ACL operation” as explicit application steps that happen after validation passes. This module is that step, and it is the only place in the workspace that creates or re-permissions a runner root.

§The threat, stated exactly

04-security-recovery.md lists it in one line: %SystemDrive%\rman is writable by unrelated local users.” That is not hypothetical, it is the default on every Windows host. The security descriptor of C:\ carries an inherit-only ACE of roughly this shape:

(A;OICIIO;SDGXGWGR;;;AU)

Authenticated Users, delete plus generic read/write/execute, inherited by every child of C:\. A directory created there with inheritance left on is therefore writable by every account that can log in, including the account a hostile workflow’s own leftovers could be running as. Runner workspaces are executable content that a later job re-enters, so this is a code-execution boundary rather than a tidiness one.

The control is one character: P, the SE_DACL_PROTECTED flag, which severs inheritance. Everything else in this module exists to apply it without ever widening anything, to prove afterwards that it took, and to refuse rather than adopt a directory that was already open.

§What is admitted, and why that is the minimum

TrusteeRightsWhy it cannot be dropped
SY — LocalSystemFull control, inheritedA boot registration runs as LocalSystem and must create, materialize and clean attempt directories
BA — AdministratorsFull control, inherited07-security.md already places a local administrator outside this threat model; without it an operator cannot clean up after a service account they are not logged in as
the selected accountADMITTED_RIGHTS, inheritedA login task or a foreground daemon runs as an ordinary user whose token contains neither of the above

The third row is load-bearing in a way that is easy to miss. A login-mode registration is a Task Scheduler task rendered with RunLevel = LeastPrivilege (see crate::service::windows_scheduled_task_xml), so it runs under the operator’s filtered token — in which the Administrators group is present but deny-only. A DACL naming only SY and BA therefore grants such a task nothing at all, even when the operator is an administrator. The explicit per-account ACE is what makes login mode work, and it is also why a mode change has to reconcile it: the account admitted for login mode is not the account boot mode needs.

§Why the account gets modify rather than full control

FA is FILE_ALL_ACCESS, which includes WRITE_DAC and WRITE_OWNER — the two rights that would let the admitted account undo the protection this module exists to apply. ADMITTED_RIGHTS is read, write, execute and delete, which is everything “create a child, materialize a runner into it, and clean it up again” needs and nothing that can re-open the root. Deleting a whole attempt tree works because the inherited ACE grants DELETE on every entry below, which is what remove_dir_all actually requires; the parent’s FILE_DELETE_CHILD is a convenience this deliberately does not grant.

§Custom roots are read, never rewritten

An operator’s host set-runtime-root path is theirs. This module offers no public function that applies a security descriptor to a caller-chosen path: ensure_default_root resolves crate::runner_root::default_runner_root itself and takes no path at all, and report — the entry point for a configured custom root — only reads. That is the whole of “custom roots are never re-ACLed”, enforced by the shape of the API rather than by a check somebody has to remember to write.

§Everything decidable is decided purely

default_root_sddl, grants_broad_write, admits_exactly and redact are pure functions over text with no cfg, no privileges and no filesystem, for the reason this crate gives everywhere else: a Linux CI leg can assert the exact descriptor a Windows host will write, and the one test that needs a real DACL is the privileged one that has a real machine.

Structs§

RootAccessChange
A RootAccessSummary plus what it would take to undo.

Enums§

AdmittedTrustee
One trustee a runner root admits, named without naming an account.
Reversal
What undoing a RootAccessChange achieved.
RootAccessError
Why the default runner root could not be created, inspected, or reconciled.
RootAccessReport
What a runner root’s access control amounts to, said without naming an account.
RootAccessSummary
What creating or reconciling the default runner root amounted to.
RootAdmission
Which account, beyond the two constants, the root must admit.

Constants§

ADMITTED_RIGHTS
The rights the selected login or foreground account is admitted with.
INHERITANCE
The inheritance flags every ACE this module writes carries.
WRITE_MASK
The access mask bits that amount to “may change what is in this directory, or who may”.

Functions§

admits_exactly
Whether a DACL is already exactly what default_root_sddl would write.
default_root_sddl
The security descriptor the default runner root is created and reconciled with, in SDDL.
ensure_default_root
Creates or reconciles the platform default runner root.
grants_broad_write
Whether a DACL lets a local user unrelated to this product write inside the object it protects.
is_protected
Whether a DACL carries SE_DACL_PROTECTED, so that nothing is inherited into it from the volume root.
redact
A security descriptor with account SIDs reduced to the fact that they are account SIDs.
report
Describes a runner root’s access control without changing it.
write_trustees
Every trustee a DACL grants write access to, canonicalised.