# Security

---

# Access control on keys

_Peios / Using Peios / Security_

> Every key is a secured object under the same AccessCheck as everything else — checked once at open, per key not per value, with no traversal check.

Every registry key is a secured object. It carries a [security descriptor](/peios/security-fundamentals/security-descriptors/overview.md) — owner, DACL, and optionally a SACL — exactly like a file, a process, or a token. And the registry does not invent its own access logic: opening a key runs the same [AccessCheck](/peios/security-fundamentals/access-decisions/overview.md) pipeline that governs every other protected object in Peios, against the same tokens and the same SD format. If you understand access control for files, you already understand most of it for the registry.

## Access control on keys, in one sentence

**Every key carries a security descriptor, and access to it is decided by the same AccessCheck that governs everything else — evaluated once when you open the key, then cached on the handle for the life of that handle.**

## Security is per key, not per value

A security descriptor lives on the **key**. Values do not have their own — they inherit their key's access control. "Who can read this value, who can change it" is answered by the SD on the key that contains it, and there is no finer-grained permission than that. If two values need different access rules, they belong in different keys.

## The handle model

Access is checked at **open**, not on every operation. When you open a key for some set of rights, AccessCheck runs once; if it grants them, you get a key handle (a file descriptor) with a **granted access mask** baked in. Every later operation through that handle is a cheap bitmask check against the cached mask — *is this operation's required right in what I was granted?* — not a fresh AccessCheck.

The consequence is the same **check-at-open** rule that [files](/peios/security-fundamentals/file-access/the-handle-model.md) and [central access policies](/peios/security-fundamentals/central-access-policies/overview.md) follow: changing a key's SD affects *future* opens, not handles that are already open. An administrator who tightens a key's DACL does not retract access from a service that already has the key open — the recourse is to make the service reopen (typically by restarting it). The handle is a snapshot of the decision made at open time.

## The registry-specific rights

A key's DACL is written in terms of rights specific to keys:

| Right | Gates |
|---|---|
| `KEY_QUERY_VALUE` | Reading values. |
| `KEY_SET_VALUE` | Writing and deleting values. |
| `KEY_CREATE_SUB_KEY` | Creating child keys. |
| `KEY_ENUMERATE_SUB_KEYS` | Listing child keys. |
| `KEY_NOTIFY` | Arming a [watch](/peios/using-peios/registry-concepts/watches.md). |
| `KEY_CREATE_LINK` | Creating a [link key](/peios/using-peios/registry-advanced/registry-links.md) (privileged). |
| `DELETE` | Deleting the key, or hiding it in a layer. |
| `READ_CONTROL` | Reading the SD and key metadata. |
| `WRITE_DAC` / `WRITE_OWNER` | Changing the DACL / the owner. |
| `ACCESS_SYSTEM_SECURITY` | Reading or changing the SACL (audit policy). |

The usual convenience bundles apply — `KEY_READ` (query, enumerate, notify, read the SD), `KEY_WRITE` (set values, create subkeys), and `KEY_ALL_ACCESS`.

Ordinary access — reading a value, writing one, creating or listing subkeys — is decided entirely by the key's SD through AccessCheck. **No privilege grants ordinary registry access.** A few privileges appear only where a write means more than storage: establishing a layer that *outranks* others (policy — see [Layers](/peios/using-peios/registry-layers/layers.md)), creating a link, or bulk [backup and restore](/peios/using-peios/registry-administration/lcs-and-sources.md). Those are special operations, covered where they arise; everyday access is the SD's job alone.

## No traversal check

One surprise sets the registry apart from a filesystem: **only the SD on the key you open is checked.** The keys above it on the path are not. A process can open `Machine\System\Services\Jellyfin` with no access whatsoever to `Machine\System\Services` or `Machine\System`. There is no "execute/traverse" right that you must hold on every ancestor the way a filesystem demands. Access is decided at the destination, full stop.

This matters when you reason about exposure: locking down a parent key does **not** lock down what is beneath it. If a subtree must be protected, the protection has to be on the keys that hold the data, not merely on a key somewhere above them.

## Where a key's SD comes from

A key's SD is computed **once, at creation**, by inheriting from its parent — the same eager, static [inheritance](/peios/security-fundamentals/security-descriptors/inheritance.md) files use. It is then a complete value stored on the key; the parent is not consulted again at access time. Changing a parent's SD does **not** ripple down to children that already exist — re-applying inheritance to an existing subtree is a deliberate administrative walk, not something that happens on its own.

The chain has to start somewhere, and it starts at the hive roots, whose SDs are the seeds for everything below:

| Hive root | Default access |
|---|---|
| `Machine\` | SYSTEM and Administrators: full control. Authenticated Users: read. (All inheritable.) |
| `Users\<SID>\` | That user, SYSTEM, and Administrators: full control. |

Subsystems that need something tighter than "Authenticated Users can read" set an explicit SD on their own subtree root at creation, overriding the inherited default for everything beneath it.

## Watching requires permission to read

Arming a [watch](/peios/using-peios/registry-concepts/watches.md) needs `KEY_NOTIFY`, so a process cannot monitor a key it could not otherwise observe. One deliberate asymmetry: a *subtree* watcher learns that a descendant key was created or deleted — structural facts — without holding any access to that descendant. It learns that something appeared; it must still open it (and pass AccessCheck) to read what is inside. Structure visibility is intentionally weaker than content visibility.

## The sharp edge: security is not layered

This is where the registry's two big ideas meet, and the result surprises people. [Layers](/peios/using-peios/registry-layers/what-layers-are-for.md) revert cleanly — delete a layer and its values fall away. **A security change does not.**

A key's SD is a direct property of the key object. It is *not* tagged with a layer and it is *not* a write in the per-value contest. So when you change a key's DACL — tightening access, say — you are mutating the key itself, permanently. If a role layer existed at the time and is later uninstalled, the values it set revert, but the security change you made stays exactly where it is.

The reasoning is deliberate: security is **operational state, not configuration overlay.** Imagine the alternative — an administrator locks down a sensitive key while some unrelated role happens to be installed, and then removing that role silently reopens the key. Configuration is the kind of thing you want to revert in bundles; an access decision is not. So the registry keeps them on different tracks: values and key existence are layered and revertible; ownership and permissions are mutations on the object that outlive any layer.

The short version: **layers revert what the system is configured to do; they never revert who is allowed to do it.**

## Where to go next

If you want to see how a process *reacts* to a change instead of polling for it — and how watch events report effective-state changes with the layering invisible — read [Watching for changes](/peios/using-peios/registry-concepts/watches.md).

If you want the kernel/store split underneath all of this — who actually holds the SDs, and the trust boundary that follows — read [LCS and sources](/peios/using-peios/registry-administration/lcs-and-sources.md).

For the broader access model these rights plug into — the AccessCheck pipeline itself — read [Access decisions](/peios/security-fundamentals/access-decisions/overview.md).

---

# Default security descriptors

_Peios / Using Peios / Security_

> Where a component keeps the security descriptors it stamps at runtime — the SdDefaults convention — and the reject-or-keep rule that guards it.

Some software stamps [security descriptors](/peios/security-fundamentals/security-descriptors/overview.md) onto objects it creates at runtime — a freshly mounted filesystem root, a state file, a spool directory. Each of those descriptors is a policy decision: who can enumerate this directory, who can read this file, what everything created beneath this root will inherit. Decisions like that are configuration, and in Peios configuration has one home. The **SdDefaults** convention is the standard place a component keeps these descriptors, so that an operator can inspect them, change them, and trust that every component publishes them the same way.

## Default security descriptors, in one sentence

**A component keeps each security descriptor it stamps at runtime as a named SDDL value under `Machine\Software\<Software>\SdDefaults\`, with a compiled-in copy as the fallback — a value that is present and valid overrides the compiled default; a value that is invalid is ignored, loudly.**

## The convention

```
Machine\Software\<Software>\SdDefaults\<SD Name>
```

- `Machine\Software\<Software>` is the component's own configuration key — the same key the rest of its settings live under.
- `SdDefaults` is the literal subkey name.
- `<SD Name>` names the object the descriptor protects: `SpoolDirectory`, `StateFile`, `Run`. One value per descriptor, stored as a string containing SDDL.

A fictional spooler that creates its spool directory at startup would publish:

```
Machine\Software\ExampleSpooler\SdDefaults\SpoolDirectory
    REG_SZ  "O:SYG:SYD:(A;OICI;GA;;;SY)(A;OICI;GA;;;BA)"
```

The defaults are **per component, deliberately**. A single shared tree of default descriptors would need every SD name to be unique across every application ever written — a namespacing promise nobody can keep. Scoping the names under the component that reads them dissolves the problem, and it keeps discovery unsurprising: a component's descriptors live where the rest of its configuration lives.

## Compiled default, registry override

The registry value is an override, never the only copy. Every component that follows the convention carries a compiled-in default for each named descriptor, and resolves the one to use at the point where it stamps it:

| State of `SdDefaults\<SD Name>` | Descriptor in force |
|---|---|
| Absent | The compiled-in default. This is the normal state — the key need not exist at all. |
| Present, valid SDDL | The registry value. |
| Present, invalid | The compiled-in default. The stored value is ignored, and an event records the key, the rejected value, and the descriptor actually in force. |

This is the registry's general [reject-or-keep](/peios/using-peios/registry-concepts/configuration-and-meaning.md) rule applied to descriptors, and here it is doing its most important work. A security descriptor that fails to parse is never repaired, never approximated, and never replaced with something broader: the descriptor in force is always one that was compiled in and reviewed. A typo in an SdDefaults value costs you your customisation, not your system.

## When a change takes effect

A default descriptor is read when the component stamps it — typically when the object is created. Two consequences follow:

- Changing a value affects objects stamped **after** the change. It does not rewrite objects that already exist.
- Where the stamped object is a directory whose descriptor carries inheritable ACEs, everything created beneath it derives its descriptor at creation time ([inheritance](/peios/security-fundamentals/security-descriptors/inheritance.md) is computed once, when the child is born). Changing the default later does not re-derive existing children.

So the honest answer to "when does my change apply?" varies by descriptor — next boot, next mount, next time the object is recreated — and the component's [`regman`](/peios/using-peios/registry-administration/regman.md) documentation is where that answer lives. Every `<SD Name>` a component publishes has a regman entry, and its `Applies` line states exactly this.

## The values are access policy — protect them

Whoever can write a component's SdDefaults values decides what access the component will grant on the objects it creates. These values are not settings *about* security; they **are** the security. A component's `SdDefaults` key therefore carries a tight security descriptor of its own — writable by Administrators and SYSTEM, no wider — which the registry enforces like [any other key](/peios/using-peios/registry-security/access-control.md). The arrangement is self-hosting: the store that holds the descriptors is protected by the same mechanism the descriptors configure.

## What the convention does not cover

**Bootstrap seeding.** The very first descriptors of a boot — the seed stamped onto a fresh root before any registry source is attached — are compiled into the tools that write them, and are not customisable through the registry. There is no registry to read at that point in boot; that is not a gap in the convention but the reason it has a floor.

**Kernel fallbacks.** The descriptors the kernel synthesises when a mount policy has no template, and the default DACL applied when a created object has no parent to inherit from, are fixed. They are the floor under a missing policy, not configuration.

**Files installed by packages.** A package payload entry gets its descriptor by inheritance from its destination directory, or from a declaration in the package manifest — see [PSPU §5.20](/peios/advanced-peios/pspu/package-format-and-repository-protocol/security-descriptor-overrides.md). SdDefaults governs what software stamps at runtime, not what the package manager installs.

## For component authors

If your component stamps descriptors at runtime, follow the convention:

1. Ship a compiled default for every named descriptor. The registry value is an override; your component must work, with reviewed policy, on a system where the `SdDefaults` key has never been created.
2. Name each value for the object it protects, not for its content — `SpoolDirectory`, not `SystemOnlyOici`.
3. Resolve with reject-or-keep. An unparseable value is ignored in favour of the compiled default, and the rejection is recorded in an event naming the key, the rejected value, and the descriptor in force. Never substitute anything broader than the compiled default.
4. Document every name in `regman`, including an `Applies` line that says when a change is picked up.

## Where to go next

For the doctrine behind reject-or-keep — why the registry stores values it does not validate, and why readers keep their last known-good — read [Configuration, not storage](/peios/using-peios/registry-concepts/configuration-and-meaning.md).

For what an SDDL string actually says — owner, DACL, ACEs, and the inheritance flags that make one descriptor govern a whole tree — start at [Security descriptors](/peios/security-fundamentals/security-descriptors/overview.md) and [Inheritance](/peios/security-fundamentals/security-descriptors/inheritance.md).

For protecting the `SdDefaults` key itself, read [Access control on keys](/peios/using-peios/registry-security/access-control.md).
