Skip to content

emrg: a registry-key grant is refused on every platform — Windows can enforce a key's DACL #2497

Closed

Description

@argszero

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-write emrg/sandbox/win32/acl.py performs with SetNamedSecurityInfoW on 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

  1. enforced_kinds("win32") contains registry; enforced_kinds() for darwin, linux and other still do not.
  2. 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.
  3. The merge preserves the key's pre-existing ACEs, and a revoke removes only the capability SID's ACE.
  4. 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.
  5. A registry grant whose key this host does not have is absent from the derivation, never an error — the same totality default_grants already owes a directory nobody created.
  6. test_no_platform_enforces_a_registry_key_yet in tests/test_sandbox_grants.py is 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

  1. argszero commented on Oct 22, 2026

    @argszero
    OwnerAuthor

    Handled by #2498

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions