Repository navigation
emrg: a registry-key grant is refused on every platform — Windows can enforce a key's DACL #2497
Description
Observed
A grant is typed as one of three object kinds — directory, file, registry — and the type flows end to end: emrg/sandbox/grants.py records it, emrg/sandbox/roots.py::granted_grants derives from it once for every dialect, and emrg/session.py persists it. But no dialect enforces the third kind: ENFORCED_KINDS in emrg/sandbox/grants.py lists registry for none of darwin, linux, win32, other, so named_grants raises GrantKindNotEnforced and every consumer refuses the grant by name. emrg/sandbox/providers/win32.py::_extra_root_spellings states the same thing from the other side: a registry grant never reaches it, because the Windows backend applies DACLs to paths.
So on Windows there is no way at all to grant a key. Measured consequence: lark-cli keeps its user-identity token under HKCU\Software\LarkCli\keychain, and it renews that token by taking a lock and writing it. Under a workspace-write sandbox the renewal fails, and there is no /sandbox add remedy for it — the token's location is a registry key, and a key is not a directory, a file, or anything a path grant can name. The failure repeats roughly every two hours for as long as the tool runs. On macOS and Linux the same tool's state is a directory and a manual grant fixes it; Windows is the only platform left with the hole.
Expected
On Windows a registry grant is enforced the same way a directory grant is:
- a capability SID is derived from the key's canonical spelling, in its own derivation domain so a key and a path can never collide on one identity;
- an allow ACE naming that SID is merged into the key's own DACL (
RegGetKeySecurity/RegSetKeySecurity), preserving every ACE already there — the same read-merge-writeemrg/sandbox/win32/acl.pyperforms withSetNamedSecurityInfoWon a path; - the same SID is placed in the confined token's restricting list, so writes inside the key pass both halves of the write-restricted check and writes outside it are still refused;
- a manifest row supplies the key so it is writable without a command, and that row names the parent key, so the first creation of the granted subkey succeeds.
ENFORCED_KINDS["win32"] then includes registry, and the other three families still do not: a platform that cannot enforce a kind must keep refusing it by name rather than dropping it.
Where the work lands
| Path | Change |
|---|---|
emrg/sandbox/win32/registry.py |
new: key spelling → root + subkey, the key's DACL read/merge/apply, grant and revoke |
emrg/sandbox/win32/ffi.py |
RegOpenKeyExW, RegCloseKey, RegGetKeySecurity, RegSetKeySecurity, GetSecurityDescriptorDacl, InitializeSecurityDescriptor, SetSecurityDescriptorDacl, MakeSelfRelativeSD |
emrg/sandbox/win32/abi.py |
the predefined HKEY handles, key access rights, the registry grant mask |
emrg/sandbox/win32/sid.py |
registry_write_sid, domain-separated from the path and temp derivations |
emrg/sandbox/win32/sandbox.py |
a registry grant's ACE and its capability SID in the restricted token |
emrg/sandbox/win32/runner.py |
--extra-key, repeatable, deriving each key's SID the way --extra-root derives a root's |
emrg/sandbox/providers/win32.py |
emit --extra-key for registry grants, keep --extra-root for paths |
emrg/sandbox/grants.py |
ENFORCED_KINDS["win32"], DEFAULT_MANIFEST["win32"] |
Acceptance
enforced_kinds("win32")containsregistry;enforced_kinds()fordarwin,linuxandotherstill do not.- On the Windows runner: a confined process can write a value under a granted key, and a write to a value under a key that is not granted is still refused.
- The merge preserves the key's pre-existing ACEs, and a revoke removes only the capability SID's ACE.
- The seam and the runner derive one key's SID identically — a second derivation would let the token carry a SID no ACE names, and this stack fails open when that happens.
- A registry grant whose key this host does not have is absent from the derivation, never an error — the same totality
default_grantsalready owes a directory nobody created. test_no_platform_enforces_a_registry_key_yetintests/test_sandbox_grants.pyis inverted on purpose, not deleted.
What this does not claim
The macOS and Linux legs cannot execute the Win32 path. The pure parts — key parsing, SID derivation, the refusal sentences, the manifest row's kind — are measured on all three legs; the DACL applier itself is measured only on the windows-2025 leg.
Activity
argszero commentedon Oct 22, 2026
Handled by #2498