Skip to content

emrg: a host cannot type a registry key — /sandbox add has no --registry flag and the writer refuses the kind Stage 2 made enforceable #2511

Closed

Description

@argszero

A host cannot name a registry key as a writable object. /sandbox add accepts a filesystem path and nothing else, and the writer-side judge refuses the registry kind outright — even on the one platform that now enforces it.

What is there today

emrg/sandbox/roots.py::judge_extra_root takes a kind argument and has a branch for it, but that branch ends in a refusal:

if kind == GRANT_REGISTRY:
    refusal = enforcement_refusal(GRANT_REGISTRY, path.strip())
    if refusal is None:  # pragma: no cover - no platform reaches this in Stage 1
        refusal = (
            "a registry grant is enforced on this platform but its key spelling is not "
            "validated yet — issue #2492 Stage 2 lands the validation and the enforcement "
            "together"
        )
    return ExtraRootVerdict("", refusal=refusal)

Two things are wrong with that block, and they are the same thing seen twice.

It is reachable now, and says it is not. emrg/sandbox/grants.py::ENFORCED_KINDS lists registry for win32, so enforcement_refusal(GRANT_REGISTRY, …) returns None there and the second branch is taken — the pragma: no cover above it claims a line no platform reaches, on the one platform that does. pytest --cov therefore reports a branch that executes as never executed.

Its sentence is false. The host reads that the key is not validated because Stage 2 has not landed, when the enforcement half has. A refusal is a boundary the host is owed a true reason for.

The client half is missing too: emrg/client/app.py builds set_sandbox_roots from op and path alone and never sends a kind, so even a writer that accepted a key could not be reached by typing one. The daemon already carries the field through (msg.get("kind") or None), and it already stores whatever typed grant the judge returns — so the wire and the storage are not the gap.

What the fix must satisfy

  • A key spelling is validated, not guessed. emrg/sandbox/win32/registry.py::split_key already decides which key a grant named — it refuses a bare hive, an unknown root, and an empty subkey, each with its own sentence. It is pure and imports on every platform, so the writer reuses it rather than growing a second parser with a second wording.
  • Validation is not enforcement. A key named on a platform whose dialects cannot apply it stays refused, with enforcement_refusal's own platform sentence — that sentence is right and stays the one producer of it. What changes is that a platform which can enforce the kind now validates the spelling instead of refusing it.
  • The accepted key is stored as a typed grant (kind="registry", the spelling the host typed as its target), so the derivation and the DACL applier read one word for it.
  • Naming a key already granted reports that rather than storing it twice — the same "already covered" outcome a path has.
  • One producer for the flag, so the client cannot spell a kind the model does not have.
  • It must be measurable on every platform: the spelling rules and the platform refusal do not need a Windows host, only the DACL application does.

Activity

  1. argszero commented on Oct 23, 2026

    @argszero
    OwnerAuthor

    Handled by #2512

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