# Privileges

---

# Privileges

_Peios / Peios Security Fundamentals / Privileges_

> A privilege is a system-wide right carried on a token — gating a specific operation regardless of who you are, granted by this machine's policy at logon.

A **privilege** is a system-wide right carried on a token. Where group membership decides "is this principal allowed to do things by virtue of who they are", a privilege decides "is this principal allowed to do this specific operation, regardless of who they are". Loading a kernel module, taking ownership of an object, changing the system clock, reading any file for backup — each is gated by a specific privilege, granted to principals whose role legitimately requires it.

Privileges sit on the token alongside the user SID and group list, but they are evaluated separately from identity. The access check reads a privilege bitmask, not a group membership. An ACE in a DACL cannot match a privilege the way it matches a SID. Privileges are their own axis of authority.

## What a privilege is

A privilege has three properties:

- A **name** — a specific string like `SeBackupPrivilege`. Most privilege names start with `Se` and end with `Privilege`; the convention is shared across the catalog.
- A **LUID** — a 64-bit identifier the kernel uses internally to reference the privilege. Privileges occupy specific bit positions in the 64-bit privilege bitmask on every token.
- A **specific operation it gates** — every privilege has a defined effect: which kernel operation, which AccessCheck pathway, which capability it controls.

Privileges are global. The catalog is fixed at build time; there is no API to define new privileges at runtime. A token either has a specific named privilege or does not. The number of distinct privileges in v0.20 is in the low tens.

## Where privileges come from

A token's privileges are decided at one moment: when the token is minted. That happens either:

- During boot, when the kernel constructs the SYSTEM token (which holds **every** privilege).
- When authd authenticates a principal and constructs their token, applying the privilege policy authd has loaded for the principal's role.
- When peinit mints a token for a service it is launching.
- When existing code creates a derived token via DuplicateToken (preserves privileges) or FilterToken (removes the listed privileges).

There is no path to *gain* a privilege after a token is minted. AdjustPrivileges can enable or disable a privilege the token already has, and can permanently remove a privilege, but cannot add one that was not present at creation. A token's privilege bitmask is at most what was put there at the start.

The privilege policy authd consults — which principals get which privileges — is **local to the machine**, and lives in the registry under `Machine\Generic\Authn\Policy`, one record per principal. authd reads it at every logon. See [assigning privileges](/peios/security-fundamentals/privileges/assigning-privileges.md) for the record format and the rules.

That it is local is the design rather than an implementation detail. A principal source — the local store, or a directory — says who someone is: their SID, their memberships, their POSIX identifiers. It never says how much this machine trusts them, and the protocol it speaks gives it no way to. So a directory can tell this machine that you are a member of `Domain Admins`; whether that membership carries `SeLoadDriverPrivilege` here is this machine's answer, not the directory's.

From the kernel's point of view the policy is invisible: it only ever sees the resulting token.

## The four states of a privilege

At any moment, each privilege on a token is in one of four states:

| State | Meaning |
|---|---|
| **Absent** | The privilege is not on this token. The token's bitmask has the relevant bit clear in both the "present" and "enabled" fields. |
| **Present, disabled** | The privilege is on the token but not currently in effect. The access check ignores it. The token may enable it via AdjustPrivileges. |
| **Present, enabled** | The privilege is on the token and is in effect. AccessCheck will use it where applicable. |
| **Used** | The privilege has been exercised at least once. A sticky audit bit; never cleared. |

The states layer rather than replace. A privilege that has been used remains in whatever present/enabled state it was in; the "used" bit is recorded alongside, for auditing.

Disabling rather than removing is the common pattern. A service runs most of the time with sensitive privileges present but disabled, enabling them only at the moment they need to be exercised and disabling them afterwards. The reason: the access check ignores disabled privileges entirely, so the service is not at risk of accidentally exercising a privilege it had not meant to use.

The full lifecycle — transitions, AdjustPrivileges semantics, FilterToken-based permanent removal, the "used" bit — lives in [Privilege lifecycle](/peios/security-fundamentals/privileges/lifecycle.md).

## Privileges in the access check

Privileges interact with the access check in three ways:

### Direct gating

Some operations are gated directly by a privilege held at the moment of the call, with no DACL involved.

- `SeLoadDriverPrivilege` gates kernel module loads. No DACL applies; either you have the privilege enabled or the load fails.
- `SeSystemtimePrivilege` gates `settimeofday`.
- `SeCreateTokenPrivilege` gates `kacs_create_token`.

These privileges are direct. The check is "is this privilege enabled on the calling token" and nothing else.

### Influencing the DACL walk

A handful of privileges, when enabled, change what the DACL walk would conclude. These are the **AccessCheck-influencing** privileges, covered in [Privilege categories](/peios/security-fundamentals/privileges/categories.md):

- `SeSecurityPrivilege` grants `ACCESS_SYSTEM_SECURITY` (SACL read/write) regardless of the DACL.
- `SeTakeOwnershipPrivilege` grants `WRITE_OWNER` on any object.
- `SeBackupPrivilege` grants read access to any object (with the backup intent flag set).
- `SeRestorePrivilege` grants write access and ownership-change rights (with the restore intent flag).
- `SeRelabelPrivilege` permits raising an object's integrity label above the caller's own.

These privileges produce grants that no DACL would have produced. They are recorded in the access check's audit state so audit events can record "this access was granted by privilege X" rather than appearing as a DACL grant.

### Intent-gated privileges

Two of the influencing privileges — `SeBackupPrivilege` and `SeRestorePrivilege` — only fire when the caller passes a specific **intent flag** to AccessCheck. Without the flag, the privileges might as well not be present. This avoids accidentally exercising a backup privilege when doing a normal read.

The intent model is covered in [Intent-gated privileges](/peios/security-fundamentals/privileges/intent-gated.md).

## Privileges vs groups

The split between privileges and group membership is one of the deliberate design decisions of the model:

| | Group membership | Privileges |
|---|---|---|
| What it grants | Whatever ACEs name the group | A specific operation, named directly |
| Where it lives | The token's `groups` field | The token's privilege bitmask |
| How it is matched | ACE SID matches token group SID | Privilege code is consulted in specific kernel paths |
| Who manages it | Whoever holds the accounts (a directory, or `lpsd`) and the per-object administrator (DACL entries) | This machine's administrator, in local policy |
| Granularity | Group-wide; the same group across all objects | Per-operation; the same privilege across all objects |

A user can be in a group and still not hold a privilege the rest of the group has, or vice versa, because the two are managed independently — and, more than that, they are managed by different parties. Memberships come from whoever holds the account; privileges come from this machine.

The classical example: BUILTIN\Administrators is a group, and being in it grants whatever ACEs in DACLs name the group. But the dangerous privileges — SeLoadDriver, SeBackup, SeRestore — are not automatic by group membership. Local policy specifies which administrators get which privileges, and it does so by naming principals: a record for one account's SID can grant it privileges the rest of `Administrators` does not have, or withhold ones they do.

This separation matters because the failure modes are different. A misconfigured DACL grants too much group access; a misconfigured privilege policy grants too much system-level authority. Both are fixable in different places.

## Privileges and inheritance

Group memberships propagate down through identity (you remain in your groups until your token changes). Privileges work the same way: a token holds the privileges authd minted it with, and the bitmask is preserved through fork, exec, and inheritance into child processes — exactly like the rest of the token.

Privileges do **not** propagate to ACEs or objects. There is no "privilege-bearing" ACE; no SD can be annotated with a privilege requirement; no object can demand "you must have SeBackup to open me". The privilege model and the SD model do not overlap that way. They interact only through the access check, when an influencing privilege is enabled on the caller's token and the AccessCheck pipeline decides whether to use it.

## Where to start

If you want the lifecycle in detail — the present/enabled/used/removed transitions, what AdjustPrivileges actually does, how FilterToken removes a privilege permanently — read [Privilege lifecycle](/peios/security-fundamentals/privileges/lifecycle.md).

If you want to understand intent-gated privileges — why SeBackup and SeRestore require an explicit flag and how that flag is passed — read [Intent-gated privileges](/peios/security-fundamentals/privileges/intent-gated.md).

If you want to see how privileges are organised — the kernel-standalone group, the AccessCheck-influencing group, the application-level group, the reserved group — read [Privilege categories](/peios/security-fundamentals/privileges/categories.md).

If you want the full catalog of named privileges with their numeric LUIDs and one-line descriptions, that is in the [Constants and catalogs](/peios/using-peios/constants-and-catalogs/overview.md) reference.

---

# Privilege lifecycle

_Peios / Peios Security Fundamentals / Privileges_

> Each privilege on a token is absent, present-disabled, present-enabled, or used — the transitions between states, AdjustPrivileges, and why removal is one-way.

A privilege's state on a token is more than just "yes, the token has it". The four-state model — absent, present-disabled, present-enabled, used — exists so a service can hold a sensitive privilege most of the time without exercising it, enable it briefly when it actually needs it, disable it again afterwards, and have all of those transitions auditable.

This page covers each transition: how privileges get added (only one moment), how they get enabled and disabled (many times), how they get permanently removed (one-way), and what the "used" bit means for auditing.

## The four states

From the [Privileges overview](/peios/security-fundamentals/privileges/overview.md):

| State | Bitmask representation | Meaning |
|---|---|---|
| Absent | `present` bit clear | Not on the token. AccessCheck cannot use it. |
| Present, disabled | `present` set, `enabled` clear | On the token but not in effect. AccessCheck ignores it. |
| Present, enabled | `present` set, `enabled` set | In effect. AccessCheck will use it where applicable. |
| Used (overlaid) | `used` bit set, independent of the others | Has been exercised at least once. Sticky. |

The bitmask is a 64-bit word. Each privilege occupies a fixed bit position (its LUID determines which). The token actually carries four independent 64-bit words: `present`, `enabled`, `enabled_by_default`, and `used`. Most code only cares about the first two.

`enabled_by_default` records the initial state — what was enabled at token creation. The kernel uses it to implement the "reset to defaults" sentinel in AdjustPrivileges.

## Adding a privilege: only at creation

There is exactly one moment when a privilege can be added to a token: when the token is being created. Specifically:

- During boot, the kernel constructs the SYSTEM token with every privilege present and enabled.
- When authd calls `kacs_create_token`, the wire-format specification names the privileges to include and which of them should start enabled.
- When peinit calls `kacs_create_token` for a service.

DuplicateToken preserves the privilege bitmask of the source — every present bit, every enabled bit, every used bit copies into the new token. The duplicate has the same privileges as the source.

That is the entire list of paths. There is no syscall, no ioctl, no privileged API to add a privilege to an existing token. A token's `present` bitmask is at most what it was at creation; it can only shrink over time.

This rule is one of the load-bearing parts of the security model. It is what makes "the principal's authority is fully expressed by their token" true. If a process could acquire new privileges at runtime, the kernel would need to track which privilege-acquisition paths exist and protect each one. By making creation the only entry point and authd the only entity that can use it, the kernel concentrates the privilege-policy decision into one component, audited at one moment.

## Enabling and disabling: AdjustPrivileges

The most common runtime operation on privileges is enabling or disabling them. The syscall is `AdjustPrivileges` (also reachable as the `KACS_IOC_ADJUST_PRIVS` ioctl on a token fd).

The caller passes an array of `kacs_priv_entry` records, each containing:

- A **LUID** identifying which privilege to adjust.
- An **attributes** value telling the kernel what to do with it.

The attributes value is one of:

| Value | Effect |
|---|---|
| `0` | Disable the privilege. Clears the `enabled` bit; leaves `present` set. |
| `SE_PRIVILEGE_ENABLED` (0x02) | Enable the privilege. Sets the `enabled` bit. The privilege must already be present. |
| `SE_PRIVILEGE_REMOVED` (0x04) | Permanently remove the privilege. Clears `present`, `enabled`, and `enabled_by_default`. Does **not** clear `used`. Irreversible. |
| `KACS_PRIV_RESET_ALL_DEFAULTS` (0x80000000) | Sentinel value, used with LUID 0. Resets every privilege on the token to its `enabled_by_default` state. |

The operation is **atomic**: every entry in the array is validated first, and if any one is invalid (a LUID for a privilege not present on the token, an unknown attributes value, a duplicate LUID), the entire call fails and no changes are made. There is no partial success.

The call returns the **previous state** of every privilege touched, as a bitmask. Callers that want to do scoped privilege enable — turn on a privilege, do work, turn it off — read the return value to know what state to restore to. The pattern:

1. Save previous state by calling AdjustPrivileges to enable the desired privilege.
2. Do the work.
3. Call AdjustPrivileges again with the saved state to restore.

If the privilege was already enabled before step 1, step 3 leaves it enabled. If it was disabled, step 3 restores it to disabled. Either way the work runs with the privilege enabled and the original state is preserved.

### What AdjustPrivileges cannot do

`AdjustPrivileges` cannot:

- **Add a privilege.** A LUID for a privilege not present on the token is rejected. The token must already have the privilege; AdjustPrivileges only changes its enabled state or removes it.
- **Resurrect a removed privilege.** Once `SE_PRIVILEGE_REMOVED` is applied, the privilege is absent. A subsequent attempt to enable it fails (the privilege is no longer present).
- **Toggle the `used` bit.** The kernel sets `used` automatically when a privilege is exercised. Userspace cannot clear it.
- **Set `enabled` without `present`.** The kernel rejects this — a privilege cannot be enabled if it is not present on the token. The "enabled but absent" state is not representable.

The right to call AdjustPrivileges is gated by `TOKEN_ADJUST_PRIVILEGES` (0x0020) on the target token. A thread can always adjust its own token's privileges (the default token SD grants this right to the token's user identity); adjusting another process's token requires `PROCESS_QUERY_INFORMATION` on that process, `TOKEN_ADJUST_PRIVILEGES` on the token, and the appropriate PIP dominance.

## The "used" bit and audit

When AccessCheck or any other kernel path actually exercises a privilege on a token, the kernel sets the corresponding bit in the token's `used` bitmask. Once set, the bit remains set for the life of the token. Nothing — not AdjustPrivileges, not FilterToken, not DuplicateToken into a fresh token — clears it.

The bit is for auditing. It answers the question "did this token, at some point in its existence, exercise this privilege?". A security audit can read a token's `used` bitmask to see which privileges have been engaged so far. A privilege that is present but never used is materially different from one that has been used; the `used` bit records the difference.

This is why removal does not clear `used`. If a privilege has been exercised, removing it later does not erase the fact. The audit trail remains.

DuplicateToken copies `used` from the source. A duplicate of a token that has used a privilege is itself marked as having used it, even though the duplicate has never actually exercised it. The rationale: a duplicate inherits the source's audit history; if the source has used a privilege, anything derived from it is suspect of having access to that privilege's effects.

If you need a fresh, audit-clean token, you need a freshly-minted token (a new authentication, or a new `kacs_create_token` call). Duplicates carry history.

## Permanent removal

`SE_PRIVILEGE_REMOVED` permanently removes a privilege from a token. After removal:

- `present` is clear.
- `enabled` is clear.
- `enabled_by_default` is clear.
- `used` remains whatever it was (still set if the privilege was exercised before removal).

The removal is irreversible. There is no syscall to re-add. The only way back to a token with the privilege is to start with a different token — typically by FilterToken from a source that still has the privilege, or by a fresh authentication.

Removal is the right tool for:

- **Service hardening.** A service that holds privileges by default but only needs them at startup can remove them after the startup phase. Removing rather than disabling makes the privilege unrecoverable for the remainder of the service's lifetime — even if an attacker compromises the service later, the privileges are gone.
- **Sandbox launchers.** Code that forks a child to run untrusted work removes every dangerous privilege from the child's token before exec. The child has no way to get them back.
- **One-way demotion.** A process starting with high authority that wants to demote itself to a lower-privileged identity, irrecoverably, removes the privileges it no longer wants. This is stronger than disabling: a disabled privilege can be enabled by code running on the token (assuming `TOKEN_ADJUST_PRIVILEGES`); a removed privilege cannot.

The one-way nature is the point. The model is built around the assumption that authority shrinks. A removed privilege is gone for the same reason a disabled group can be marked use-for-deny-only and not un-marked: each is a deliberate restriction the kernel will not let userspace undo.

## FilterToken and bulk removal

For removing multiple privileges at once — typically during sandbox creation — `FilterToken` is the appropriate API. Where AdjustPrivileges with `SE_PRIVILEGE_REMOVED` removes one privilege from the calling token, FilterToken produces a **new token** with the listed privileges removed.

The difference matters: FilterToken does not modify an existing token. It creates a copy with the listed adjustments (privileges removed, groups marked use-for-deny-only, restricted SIDs added) and returns the new token. The original is unchanged.

This is the right pattern for sandbox launchers, which want to filter a token down to a restricted version for a child process without affecting their own running identity. AdjustPrivileges with `SE_PRIVILEGE_REMOVED` is the right pattern for code that wants to demote *itself*.

FilterToken is covered in [Token lifecycle](/peios/security-fundamentals/tokens/lifecycle.md) and [Restricted and write-restricted tokens](/peios/security-fundamentals/tokens/restricted-tokens.md).

## The reset-to-defaults sentinel

A token records `enabled_by_default` separately from `enabled`. The two diverge as the token's privileges get enabled and disabled at runtime; the default record stays as it was at creation.

The `KACS_PRIV_RESET_ALL_DEFAULTS` sentinel — a `kacs_priv_entry` with `luid = 0` and the sentinel attributes value — tells AdjustPrivileges to copy `enabled_by_default` back into `enabled`. Every privilege that was enabled at creation is now enabled again; every privilege that was disabled at creation is now disabled again.

The sentinel is useful for code that does "enable a few privileges, do work, restore". Instead of saving the prior state and restoring it explicitly, the code can simply reset to defaults — which is the right behaviour as long as nothing else has changed the defaults (which nothing should, since defaults are immutable at creation).

The reset sentinel only affects `enabled`. It does not bring back removed privileges. It does not clear the `used` bit. It is a cheap way to "go back to how this token started" within the set of operations that still make sense.

## Field mutability summary

To pin the entire model on one table:

| Field | Mutability |
|---|---|
| `present` | Only ever *clears* (via `SE_PRIVILEGE_REMOVED`). Set only at token creation. |
| `enabled` | Toggled freely between `0` and the corresponding `present` bit. |
| `enabled_by_default` | Immutable after token creation. |
| `used` | Set by the kernel when the privilege is exercised. Never clears. |

The asymmetric mutability is the model's spine. Authority can decrease at runtime but not increase. State for auditing accumulates. Defaults are fixed. The "easy" mutation — toggle enabled — is the only one that goes both ways, and it does not affect what the token fundamentally is.

## Where to go next

For the two privileges that require an intent flag on top of being enabled, read [Intent-gated privileges](/peios/security-fundamentals/privileges/intent-gated.md).

For bulk privilege removal as part of building a sandbox token, read [Restricted and write-restricted tokens](/peios/security-fundamentals/tokens/restricted-tokens.md).

---

# Intent-gated privileges

_Peios / Peios Security Fundamentals / Privileges_

> SeBackup and SeRestore participate in a check only when the caller passes BACKUP_INTENT or RESTORE_INTENT — why intent gating exists and how the flags work.

Most privileges work the same way: if they are enabled on the calling token at the moment of the call, the kernel uses them; if not, it does not. AdjustPrivileges manages enabled state; the access check reads it. There is no other input.

Two privileges are special: **`SeBackupPrivilege`** and **`SeRestorePrivilege`**. Both are present-and-enabled on the tokens of backup-and-restore tools. Both grant access that the DACL alone would not. And both will refuse to do anything unless the calling code passes a specific **intent flag** to AccessCheck — `BACKUP_INTENT` for SeBackup, `RESTORE_INTENT` for SeRestore.

Without the intent flag, the privilege is invisible to the access check. The token has it. The kernel knows it has it. But the privilege does not participate in the access decision. The DACL walk runs as if the privilege were not present.

This page covers why intent gating exists, how the flags are passed, and what each privilege actually does when its intent flag is set.

## Why intent gating exists

The privileges in question grant very broad access. SeBackup grants read on any object regardless of the DACL. SeRestore grants write, ownership change, and SACL access on any object. They are designed for backup-and-restore tools — programs that legitimately need to bypass discretionary access control to do their job.

The problem: if these privileges were always in effect when enabled, every access check on a token holding them would benefit from them. A backup tool that opens a configuration file as part of its initialisation — for entirely ordinary reasons — would silently exercise SeBackup, even though the configuration file's normal DACL would have granted the access anyway. The audit trail would record privilege exercises that were operationally meaningless. Worse, a buggy code path in the tool that did something unintended would silently get backup-level access to the unintended object.

Intent gating solves this by making the privilege opt-in **per call**. The tool's main backup loop sets `BACKUP_INTENT` on every AccessCheck call it makes; everything else does not. The privilege fires only where the tool deliberately asked for it. Audit events for privilege exercise are accurate (they only appear where the tool intentionally invoked the privilege), and bugs in non-backup code paths cannot accidentally exercise SeBackup.

The split is between "enabled" (the token can use the privilege) and "intent" (the caller wants to use the privilege right now). Both must be true.

## The intent flags

The flags are passed to AccessCheck as part of the `privilege_intent` parameter. They are independent bits:

| Flag | Effect |
|---|---|
| `BACKUP_INTENT` (0x01) | Tells AccessCheck that the caller wants `SeBackupPrivilege` to participate, if present and enabled. |
| `RESTORE_INTENT` (0x02) | Tells AccessCheck that the caller wants `SeRestorePrivilege` to participate. |

A caller can set both, one, or neither. The flags are not exclusive — a backup-and-restore tool might do a restore-with-verification operation that wants both privileges to fire.

Without either flag, the corresponding privilege is stripped from the access check's view of the token for this call. It is not removed from the token; the token still has it; the next call from the same code can set the flag and use it. The strip happens only for the current AccessCheck.

The flags are at the AccessCheck API surface. Lower-level open and access paths (the file system's open, the registry's read) translate higher-level semantics into AccessCheck calls; whether they pass the flags depends on whether they were told to. Most do not; an opener of a file in the ordinary course of work does not pass BACKUP_INTENT.

## What SeBackupPrivilege does

With `BACKUP_INTENT` set and SeBackup enabled, AccessCheck grants the caller read-category rights on the object regardless of the DACL:

- For files: `FILE_READ_DATA`, `FILE_READ_ATTRIBUTES`, `FILE_READ_EA`, and `READ_CONTROL`.
- For registry keys: the read-category rights on the key (the specific values depend on the registry's GenericMapping).
- For tokens: `TOKEN_QUERY` and `READ_CONTROL`.

The grant happens **after** the DACL walk has run. Specifically, the walk produces a `granted` mask for whatever the DACL alone would grant; the privilege then adds the read-category bits to that mask without consulting the DACL. If the DACL already granted some of them, the privilege grant is redundant; if the DACL granted nothing, the privilege adds them.

The grant is recorded in the access check's audit state. An audit event for this call records "the read rights came from SeBackupPrivilege", distinguishing privilege-granted access from DACL-granted access. Audit consumers that care about privilege use can filter by this marker.

SeBackup does **not** grant write access. A backup tool that needs to write its output also reads but cannot use SeBackup for that — write is SeRestore's territory.

## What SeRestorePrivilege does

With `RESTORE_INTENT` set and SeRestore enabled, AccessCheck grants the caller write-category rights and metadata-modification rights regardless of the DACL:

- Write rights (`FILE_WRITE_DATA`, `FILE_APPEND_DATA`, `FILE_WRITE_ATTRIBUTES`, `FILE_WRITE_EA` for files; corresponding rights on other object types).
- `DELETE`.
- `WRITE_OWNER` and `WRITE_DAC`.
- `ACCESS_SYSTEM_SECURITY` (SACL read/write).

Plus, separately, SeRestore bypasses the "new owner must be self or SE_GROUP_OWNER group" restriction during `kacs_set_sd`. A restore tool can set the owner of a restored object to any well-formed SID, not just the caller's own. This is what lets a backup restore reconstitute an object's original owner even when the original principal is not present on the running system.

Like SeBackup, the grant is recorded in audit state — write rights granted by SeRestore are distinguished from those granted by the DACL.

The reason SeRestore also grants `ACCESS_SYSTEM_SECURITY` (which would normally require SeSecurityPrivilege) is the same: a restore operation needs to set the SACL as part of reconstituting the object's policy. Forcing the tool to also hold SeSecurity would be redundant; SeRestore folds that authority in.

## What the flags do not do

Two clarifications:

- **The flags do not grant privileges.** A token that does not have SeBackup gets nothing from setting BACKUP_INTENT. The flag tells the kernel "use this privilege if I have it"; it cannot conjure a privilege the token lacks.
- **The flags do not enable disabled privileges.** A token that has SeBackup present but disabled gets nothing from BACKUP_INTENT, just as it would get nothing without the flag. The privilege must be enabled for AccessCheck to consider using it. Intent is on top of enabled, not in place of it.

The state machine is: **token has the privilege** AND **token has the privilege enabled** AND **caller has set the intent flag** = AccessCheck uses the privilege. Any one of the three missing means it does not.

## Why only these two privileges

The intent-gating model exists specifically for privileges that grant broad, blanket access. SeBackup grants read on everything; SeRestore grants write on everything. The risk of accidental exercise is high enough to be worth a per-call gate.

Other privileges that influence the access check — `SeSecurityPrivilege` (SACL access), `SeTakeOwnershipPrivilege` (WRITE_OWNER), `SeRelabelPrivilege` (raising integrity) — are not intent-gated. They are scoped enough that the "just check if enabled" model is appropriate. SeSecurity only fires when accessing the SACL; SeTakeOwnership only when changing the owner; SeRelabel only when changing the integrity label — none of which is done by accident.

The split is between "this privilege only fires when you do something specific anyway" (no intent needed) and "this privilege would fire on every access if we let it" (intent required). SeBackup and SeRestore are the only two privileges in the second class.

## Calling pattern

A typical backup tool's loop:

1. Token at startup has `SeBackupPrivilege` present and enabled. (Granted by authd's privilege policy to backup-role principals.)
2. For each object to back up:
   - Call AccessCheck with `BACKUP_INTENT` set in `privilege_intent`. The kernel grants the read-category rights via SeBackup if the DACL would not.
   - Read the object.
3. Other operations the tool does (reading its own configuration, writing log output, opening its output file) call AccessCheck without `BACKUP_INTENT`. These run through the DACL like any other access. The privilege is not exercised.

A typical restore tool's loop is the mirror:

1. Token at startup has `SeRestorePrivilege` present and enabled.
2. For each object to restore:
   - Call AccessCheck with `RESTORE_INTENT` set. The kernel grants write-category rights via SeRestore.
   - Write the object's contents.
   - Set its SD via `kacs_set_sd`, including owner. The "any well-formed SID can be the owner" rule is in effect because RESTORE_INTENT was set on the AccessCheck that produced the WRITE_OWNER grant.
3. Other operations run normally without the flag.

The pattern is symmetric. Both privileges work the same way; both follow the same intent rule.

## What about privileges in audit events

Audit events for privilege exercise carry enough information to distinguish:

- The privilege that was exercised.
- The bits it contributed to the final granted mask.
- Whether those bits survived to the final access decision (i.e., were not stripped by some later layer like restricted-token intersection or confinement).

A backup tool with `BACKUP_INTENT` set on every AccessCheck call produces clean audit events: one privilege-use event per backup-flavoured access, none for incidental accesses. This is the audit trail intent gating was designed to produce. Without the flag, the privilege would fire on every access from the same token, and the audit would be unable to distinguish "I exercised SeBackup because I'm a backup" from "I exercised SeBackup because the file would have been readable to me anyway".

The full audit model lives in [Auditing](/peios/security-fundamentals/auditing/overview.md).

## Where to go next

For the four functional categories the rest of the privileges fall into, read [Privilege categories](/peios/security-fundamentals/privileges/categories.md).

For exactly where SeBackup and SeRestore fire during a check — and which layers can still strip their grants — read [Privileges in the pipeline](/peios/security-fundamentals/access-decisions/privileges-in-the-pipeline.md).

---

# Privilege categories

_Peios / Peios Security Fundamentals / Privileges_

> The four functional categories of privilege — kernel-standalone, AccessCheck-influencing, application-level, and reserved — and what each one does.

The privileges in Peios fall into four functional categories. Each category has a different relationship to the kernel and to the access check. Knowing which category a privilege is in tells you what it does, where it fires, and whether to expect it to participate in the DACL walk.

This page is organised around the four categories. The full per-privilege catalog — name, LUID bit, one-line description for every privilege — is in [Constants and catalogs](/peios/using-peios/constants-and-catalogs/overview.md).

## The four categories

| Category | What the privileges do | Examples |
|---|---|---|
| **Kernel-standalone** | Gate specific kernel operations directly. Not consulted during the DACL walk. | `SeLoadDriverPrivilege`, `SeSystemtimePrivilege`, `SeCreateTokenPrivilege` |
| **AccessCheck-influencing** | Cause the access check to grant access the DACL alone would not. Consulted at specific points during the access pipeline. | `SeSecurityPrivilege`, `SeTakeOwnershipPrivilege`, `SeBackupPrivilege`, `SeRestorePrivilege`, `SeRelabelPrivilege` |
| **Application-level** | Defined for the directory and authd to interpret. The kernel records them on the token but does not enforce them itself. | `SeSyncAgentPrivilege`, `SeEnableDelegationPrivilege`, `SeMachineAccountPrivilege` |
| **Reserved** | Present in the catalog for ABI parity but not used in v0.20. | `SeCreatePagefilePrivilege`, `SeUndockPrivilege`, `SeTimeZonePrivilege` |

The categorisation is functional, not structural. A token's privilege bitmask does not partition by category; all bits live in the same 64-bit word. The category is a property of how each individual privilege is consumed.

## Kernel-standalone privileges

These are the largest category. A kernel-standalone privilege gates a specific kernel operation: the kernel checks at the entry point whether the caller's effective token has the privilege enabled, and refuses the operation if not. The DACL walk is not involved.

Representative members:

| Privilege | What it gates |
|---|---|
| `SeCreateTokenPrivilege` | `kacs_create_token`. Token minting. Held only by authd and peinit. |
| `SeAssignPrimaryTokenPrivilege` | Installing a token as another process's primary. Used by peinit. |
| `SeImpersonatePrivilege` | Impersonating any user (when not running as the same user). Held by every service that handles user requests. |
| `SeTcbPrivilege` | "Act as part of the TCB" — a catch-all for operations that should only happen in trusted code. Required for `KACS_IOC_LINK_TOKENS`, `kacs_set_caap`, **mount-policy changes** (`policy=synth-*`, which author security descriptors), and a handful of other system operations. It also satisfies every check `SeManageVolumePrivilege` satisfies, since the TCB may do anything a volume manager may. |
| `SeLoadDriverPrivilege` | Loading and unloading kernel modules. Held only by peinit on its primary token; explicitly stripped via FilterToken from every other service. |
| `SeManageVolumePrivilege` | Mounting, unmounting and reshaping the mount tree, including mount policy (`policy=synth-*`). Granted to Administrators. **The most powerful privilege routinely granted outside the TCB** — see the warning below. |
| `SeShutdownPrivilege` | Local shutdown and reboot. |
| `SeRemoteShutdownPrivilege` | Shutdown from a remote connection. Requires SeShutdown as well. |
| `SeDebugPrivilege` | Inspecting another process regardless of its SD. Crucially, it does not bypass PIP dominance — a SeDebug holder can bypass an unrelated process's SD but still cannot cross a PIP boundary. |
| `SeSystemtimePrivilege` | Setting the system clock. |
| `SeIncreaseBasePriorityPrivilege` | Raising another process's scheduling priority or setting its CPU affinity. |
| `SeIncreaseQuotaPrivilege` | Overriding resource limits for a process. |
| `SeLockMemoryPrivilege` | Locking pages in physical memory (`mlock`/`mlockall`). |
| `SeAuditPrivilege` | Writing entries to the audit log. |
| `SeProfileSingleProcessPrivilege` | Cross-task profiling of a specific other process (`perf_event_open`). Own-task profiling needs no privilege. |
| `SeSystemProfilePrivilege` | System-wide profiling (`perf_event_open` with `pid == -1`): per-CPU, all-task, kernel-mode events. |
| `SeBindPrivilegedPortPrivilege` | Binding to TCP/UDP ports below 1024. A Peios-specific privilege. |
| `SeChangeNotifyPrivilege` | Bypassing traverse checks during path resolution. Granted to every principal by default; rarely the answer to a question. |
| `SeCreateSymbolicLinkPrivilege` | Creating symbolic links. Granted to every principal by default. |

For all of these, the calling pattern is the same: the kernel reads the calling token's privilege bitmask at the entry point of the gated operation; if the privilege is not enabled, the operation fails with the appropriate error.

The privileges in this category are mostly held in narrow ways. The most dangerous of them — SeCreateToken, SeTcb, SeLoadDriver — appear on only a handful of TCB token holders. Most other services hold a small, role-specific subset.

## AccessCheck-influencing privileges

These privileges, when enabled, change what the access check itself decides. They produce grants the DACL alone would not.

| Privilege | What it does |
|---|---|
| `SeSecurityPrivilege` | Grants `ACCESS_SYSTEM_SECURITY` on any object (SACL read/write). Also gates kernel-standalone audit-system operations. |
| `SeTakeOwnershipPrivilege` | Grants `WRITE_OWNER` on any object regardless of the DACL. Subject to MIC and PIP — does not bypass those. |
| `SeBackupPrivilege` | Grants read-category rights on any object when `BACKUP_INTENT` is set. Intent-gated. |
| `SeRestorePrivilege` | Grants write, metadata, and ownership-change rights on any object when `RESTORE_INTENT` is set. Intent-gated. Also bypasses the "new owner must be self or SE_GROUP_OWNER group" restriction. |
| `SeRelabelPrivilege` | Permits setting an object's mandatory integrity label above the caller's own. Also acts as the kernel-standalone gate for `kacs_set_sd` with `LABEL_SECURITY_INFORMATION` set to a higher label. |

These privileges fire at specific points in the access pipeline:

- `SeSecurityPrivilege` is consulted when AccessCheck sees `ACCESS_SYSTEM_SECURITY` in the requested mask.
- `SeBackupPrivilege` and `SeRestorePrivilege` are consulted near the start of AccessCheck, but only if the corresponding intent flag is set in `privilege_intent`. See [Intent-gated privileges](/peios/security-fundamentals/privileges/intent-gated.md).
- `SeTakeOwnershipPrivilege` is consulted after the DACL walk: if the walk did not grant `WRITE_OWNER` and the mandatory policy did not block it, the privilege grants it.
- `SeRelabelPrivilege` is consulted by `kacs_set_sd` when the caller is trying to set an integrity label.

When any of these privileges contributes to the final granted mask, the access check records the fact so audit events can attribute the grant correctly. Audit consumers can distinguish "the user got read access because the DACL allowed it" from "the user got read access because they hold SeBackup and asked to use it".

> [!IMPORTANT]
> **Not currently grantable.** `SeTakeOwnershipPrivilege`, `SeRelabelPrivilege` and `SeSystemProfilePrivilege` are enforced by the kernel exactly as described above and named in audit records, but they are missing from the published ABI headers that userspace is built against — so no policy can name them and `authd` cannot put them on a token. Everything on this page about how they behave is accurate; what is not yet true is that anyone can hold one. In practice that means **a Peios machine currently has no way to grant take-ownership**, so the usual escape hatch for a file whose DACL excludes you — take ownership, then rewrite the DACL — is unavailable.

## Application-level privileges

These privileges are defined for authd, the directory, and federation services to interpret. The kernel records them on the token (so authd has somewhere to put them) but does not enforce them itself.

| Privilege | What it does |
|---|---|
| `SeSyncAgentPrivilege` | Lets the holder read all directory objects regardless of per-object permissions. Used by directory replication agents. |
| `SeEnableDelegationPrivilege` | Lets the holder mark a principal as trusted for delegation in the directory. |
| `SeMachineAccountPrivilege` | Lets the holder add computer accounts to the domain. |

From the kernel's point of view, these are token attributes that no kernel path checks. They appear on the token's privilege bitmask; AdjustPrivileges can enable, disable, or remove them like any other privilege; but no AccessCheck path consults them. Their effect happens entirely in user-space services that read the token and act on its privilege bitmask themselves.

The kernel still enforces the present/enabled/removed/used state machine for these privileges as it does for others — AdjustPrivileges treats them identically. The application-level distinction is about *who consumes them*, not how they are stored or transitioned.

### What SeManageVolumePrivilege is actually worth

Mounting is an administrative act rather than a TCB one, which is why this
privilege exists separately: without it no administrator could mount anything,
and `peios-install` could not run outside a SYSTEM shell.

But be clear about its reach before granting it more widely. Its holder may:

- mount a filesystem whose **synthesised security descriptors it chooses**
  (`policy=synth-*` with `--synth-sddl`), and
- **mount over an existing path**.

Together those are enough to author policy on a subtree and to shadow a system
path — so in the hands of a determined holder it is a route to authority
approaching the TCB's.

This is a deliberate design decision, not an oversight. The narrower
alternatives — permitting only filesystems that carry their own descriptors, or
constraining synthesis to a fixed system template — were considered and
rejected, because the FAT ESP carries no descriptors of its own and must be
mounted `synth-ephemeral` for an installation to work at all.

Treat `SeManageVolumePrivilege` as sitting beside `SeLoadDriverPrivilege` in
sensitivity, not beside `SeChangeNotifyPrivilege`.

Mounting passes three separate checks, and the privilege covers all
three: `may_mount()` (may I reshape my namespace), `mount_capable()` (may
this context create a superblock), and KACS's `set_mount_policy` (may I
choose how descriptors are synthesised). They were found one at a time by
driving a real image — each fix moved the failure to the next gate — which
is worth knowing if a fourth ever appears.

## Reserved privileges

A handful of privileges appear in the catalog for binary compatibility with the spec lineage but have no implementation in v0.20:

| Privilege | Why it is reserved |
|---|---|
| `SeCreateGlobalPrivilege` | No per-session object namespaces in Peios. |
| `SeCreatePagefilePrivilege` | Folded into `SeTcbPrivilege`. |
| `SeCreatePermanentPrivilege` | No Linux equivalent. |
| `SeIncreaseWorkingSetPrivilege` | Linux does not gate memory-residency hints. |
| `SeTrustedCredManAccessPrivilege` | Reserved for future secrets infrastructure. |
| `SeSystemEnvironmentPrivilege` | Replaced by SDs on EFI variable files under FACS. |
| `SeTimeZonePrivilege` | Linux does not gate timezone changes. |
| `SeUndockPrivilege` | Server OS; not applicable. |

A reserved privilege's LUID position in the bitmask is allocated, but no kernel path consults it. They are placeholders that keep the bitmask layout stable for future use. A reserved name is not in the privilege vocabulary at all, so a policy record naming one is dropped with a warning rather than granting anything.

## Default-grant privileges

Two privileges deserve a special note: `SeChangeNotifyPrivilege` and `SeCreateSymbolicLinkPrivilege` are **granted to every principal on a stock machine**. The reason is that they are needed for almost every program to function normally — without `SeChangeNotifyPrivilege`, a process cannot traverse a directory to reach a file, so a token lacking it cannot so much as start a shell; without `SeCreateSymbolicLinkPrivilege`, a process cannot create the symlinks that build systems and packaging tools depend on.

They are granted by the shipped policy — a record for `Everyone` — rather than being built into authd, so both are visible and both can be taken away. See [assigning privileges](/peios/security-fundamentals/privileges/assigning-privileges.md). `SeChangeNotifyPrivilege` alone is additionally authd's compiled floor, applied when a machine has no policy key at all, because a machine that cannot start a shell cannot be repaired from a console.

Their effect is broad-but-uninteresting: on a stock machine every token has them, so any reasoning about access that does not explicitly involve their absence can ignore them. They are mentioned here for completeness; they will rarely be the answer to a question about who can do what.

A token can have these stripped by FilterToken if a sandbox wants to operate without them. Doing so creates a token that cannot traverse arbitrary directories — useful in a tightly confined sandbox, not useful much elsewhere.

## How to find the catalog

The four-category model on this page is the conceptual structure. The byte-level catalog — every privilege name, its LUID bit position, its one-line effect — lives in [Constants and catalogs](/peios/using-peios/constants-and-catalogs/overview.md). Cross-reference between the two when you need to look up a specific privilege.

The naming convention is uniform: every privilege starts with `Se` and ends with `Privilege`. The middle is descriptive: `SeLoadDriver`, `SeBackup`, `SeChangeNotify`. There are no privileges outside this convention.

## Where to go next

For the per-privilege reference — every name, LUID bit position, and one-line effect — see the [Privilege catalog](/peios/using-peios/constants-and-catalogs/privilege-catalog.md).

For how the AccessCheck-influencing category actually participates in a check, read [Access decisions](/peios/security-fundamentals/access-decisions/overview.md).

---

# Assigning privileges

_Peios / Peios Security Fundamentals / Privileges_

> The policy records under Machine\Generic\Authn\Policy that authd reads at every logon — which privileges a principal gets, and at what integrity level.

A principal source says **who someone is** — their SID, their memberships, their POSIX identifiers. It never says how much this machine trusts them. Privileges and integrity are **local policy**: `authd`'s alone, decided here, and there is no message with which a source could ask for one.

That policy lives in the registry:

```
Machine\Generic\Authn\Policy
```

`authd` reads it at **every logon**, not once at startup. A policy change takes effect the next time someone signs in, rather than the next time you restart the one daemon on the system that is most disruptive to restart.

## One record per principal

Each subkey is a principal, and holds everything this machine grants them:

```
Machine\Generic\Authn\Policy
  DeniedPrivileges  REG_MULTI_SZ  ["SeDebugPrivilege"]
  \Everyone
      Privileges    REG_MULTI_SZ  ["SeChangeNotifyPrivilege"]
  \Administrators
      Privileges    REG_MULTI_SZ  ["SeBackupPrivilege", "SeRestorePrivilege"]
      Integrity     REG_SZ        "High"
      Owner         REG_SZ        "Administrators"
      DefaultDacl   REG_SZ        "D:(A;;GA;;;SY)(A;;GA;;;BA)"
```

Keyed by principal rather than by privilege on purpose. One key shows the totality of a principal's authority — `reg ls` on `\Administrators` answers "what can an administrator do on this machine?" completely. Authority scattered across twenty per-privilege values is authority nobody audits: a right granted somewhere unexpected does not surface when you look at the principal, and you would have to know to check every other place.

It is also the only shape that holds more than privileges. Integrity, the default owner and the default DACL live on the same record, and logon rights will when they arrive.

### Naming a principal

A subkey's name is either a **well-known name** or a **literal SID**:

```
\Administrators
\S-1-5-21-2847362817-1094533892-3310298447-1000
```

Names are matched case-insensitively. The recognised ones are `SYSTEM`, `Everyone`, `Authenticated Users`, `Administrators`, `Users`, `Guests`, `Local Service`, `Network Service`, and the logon types `Interactive`, `Network`, `Batch`, `Service` and `Anonymous`.

Two limits are worth knowing before you hit them:

**`BUILTIN\Administrators` cannot be a subkey name.** A backslash is the registry's path separator, so the qualified spelling is unrepresentable. Write the bare name.

**A local group's name does not resolve.** `authd` cannot know what `developers` means without asking `lpsd`, and policy must never depend on a principal source being up — that would make "what may this principal do" unanswerable exactly when a source is broken. Name local groups by SID, which `lps group list` will show you.

A subkey that is neither a known name nor a parseable SID is **ignored with a warning**. It is worth checking for that warning after editing: a record naming nobody sits in the key looking authoritative and applying to no one.

## If the key exists, the key is the whole policy

This is the most important behaviour on the page.

`authd` carries a compiled-in floor, but it applies **only when the key is absent entirely**. It is not merged in value by value. So a policy you write is the complete policy: anything not granted below is not granted.

| State | What a principal gets |
|---|---|
| Key absent | The compiled floor — `Everyone` gets `SeChangeNotifyPrivilege`, and nothing else |
| Key present | Exactly what the records say |
| Key present but unreadable | Nothing, and a loud log line |

The alternative — a compiled default that each record replaces — fails in the wrong direction. An administrator who writes one record believing they have locked the machine down would still be handing out whatever a compiled table said for every principal they did not mention, from a table they cannot read. That is a security failure, and a silent one. Failing towards less privilege makes things stop working, which is far easier to diagnose than authority you did not know you were granting.

An unreadable key is treated as granting nothing rather than as absent, for the same reason: reading it as absent would silently restore every privilege an administrator may have deliberately removed, at the one moment nobody can check.

## What a machine ships with

The defaults are **registry data, not compiled in** — `authd-policy.reg`, shipped to `/usr/share/regim/` and applied if the image opts in. So they can be read, edited, and replaced wholesale by an image that wants a different policy, rather than being invisible inside a binary.

A stock machine grants:

| Principal | Privileges | Integrity |
|---|---|---|
| `Everyone` | `SeChangeNotifyPrivilege`, `SeCreateSymbolicLinkPrivilege` | (Medium, by default) |
| `Administrators` | the operational set below, plus both of the above | High |

The operational set is `SeBackupPrivilege`, `SeRestorePrivilege`, `SeShutdownPrivilege`, `SeRemoteShutdownPrivilege`, `SeSystemtimePrivilege`, `SeSecurityPrivilege`, `SeLoadDriverPrivilege`, `SeImpersonatePrivilege`, `SeIncreaseQuotaPrivilege`, `SeIncreaseBasePriorityPrivilege` and `SeProfileSingleProcessPrivilege`.

**`SeDebugPrivilege` is deliberately not granted.** It writes to any process regardless of integrity label, so it is the single privilege that most directly defeats the integrity boundary, and ordinary administration does not need it. Add it when a machine genuinely does.

Three privileges are never granted by the shipped policy and should not be added: `SeCreateTokenPrivilege` mints any identity without going near `authd`, which would make every other control here decorative; `SeTcbPrivilege` and `SeAssignPrimaryTokenPrivilege` belong to the trusted computing base.

Note that `Administrators` is granted `SeChangeNotifyPrivilege` **directly**, not only through `Everyone`. Privileges accumulate across every SID on a token, so that repetition is insurance: if someone edits the key and drops the `Everyone` record, ordinary users lose the ability to traverse a directory — but administrators keep working and can repair it.

## How the values compose

**Privileges accumulate.** A token holds the union of every record naming a SID it carries — the principal's own, and each of their groups. A group marked `USE_FOR_DENY_ONLY` contributes nothing.

**`DeniedPrivileges` wins.** Listed on the `Policy` key itself rather than on a record, it is applied *after* the union, so no record you have not read can defeat it. It is on the parent key so it cannot collide with a principal who happens to be called `Denied`.

**Integrity, `Owner` and `DefaultDacl` are single values**, so they cannot accumulate. The principal's own record wins outright; otherwise the groups decide:

- **Integrity** takes the **maximum** across the groups that name one.
- **`Owner`** and **`DefaultDacl`** have no ordering to compare, so two groups naming *different* values is a misconfiguration: it logs a warning and falls back to the default rather than picking by whichever the registry enumerated first. Two groups naming the same value is not a conflict.

The user's record winning outright is what makes a principal possible to **lower**. Under a plain maximum, a guest whose own record said `Low` would still come out Medium the moment any group they belonged to named Medium. The consequence is worth stating plainly: a group cannot impose an integrity floor on a member. If `Administrators` names High and a member's own record names Low, that member gets Low.

## Writing each value

### `Privileges` — `REG_MULTI_SZ`

Full ABI names, `SeBackupPrivilege` rather than `SeBackup`, matched **case-sensitively**. A name this build does not recognise is dropped with a warning and the rest of the list still applies — a policy written for a newer Peios should not lose the privileges it spelled correctly.

An empty list is meaningful: it grants nothing, and is different from the value being absent.

### `Integrity` — `REG_SZ` or `REG_DWORD`

A tier name, matched case-insensitively:

| Name | Value |
|---|---|
| `Untrusted` | 0 |
| `Low` | 4096 |
| `Medium` | 8192 |
| `High` | 12288 |
| `System` | 16384 |

Or a raw `REG_DWORD`. The kernel compares integrity numerically and any value is legal, so the numeric form reaches levels between the tiers — `8193` sits just above Medium. Use the name unless you need that.

A principal no record names gets **Medium**. That default is compiled in and is not affected by the key existing, because unlike a privilege it is not a grant: every token must carry some level to be valid at all.

### `Owner` — `REG_SZ`

Which principal owns objects this token creates. Names a principal, exactly like a subkey name does; `authd` converts it to the index the token actually carries, which is a number meaningful only within one token and different on the next logon.

Absent — the ordinary case — means objects are owned by their creator.

The case this exists for is a shared administrative estate: objects an administrator creates being owned by `Administrators` rather than by the individual, so they remain manageable when that person's account goes away.

### `DefaultDacl` — `REG_SZ`

The DACL objects this token creates inherit when nothing else supplies one, written as **SDDL**:

```
D:(A;;GA;;;SY)(A;;GA;;;BA)
```

SDDL rather than raw bytes because the whole argument for policy living in the registry is that an operator can read it. Conditional ACEs are preserved, so `D:(XA;;GA;;;WD;(@USER.Department == "Engineering"))` works and keeps its condition.

A value that does not parse is dropped with a warning and the system default applies — a typo costs the customisation, not the session.

## Locking a machine down

To forbid a privilege regardless of what any record says:

```
Machine\Generic\Authn\Policy
  DeniedPrivileges  REG_MULTI_SZ  ["SeDebugPrivilege", "SeLoadDriverPrivilege"]
```

That is one edit, in one place, that a record you have not read cannot defeat. Removing the privilege from each record individually relies on having found them all.

## What this cannot express yet

**Whether a principal may sign in at all.** Policy decides what a session gets, not whether one happens. `lps disable` covers the blunt case; per-logon-type restriction — allowed over the network but not at the console — is not built.

**Two privileges the kernel enforces but nothing can name.** `SeTakeOwnershipPrivilege` and `SeRelabelPrivilege` are honoured by the access check and appear in audit records, but are absent from the published ABI, so no policy can grant them. `SeSystemProfilePrivilege` is in the same position on current builds. Until that is resolved, a Peios machine has no way to grant take-ownership — which means the documented escape hatch for a file whose DACL excludes you is not currently reachable.

## Seeing what a token actually got

```
$ token show --all
```

`[privileges]` lists what the token holds and `integrity` shows the label. A privilege the tool cannot name appears as `<privilege bit N>` rather than being omitted, so the list is always complete even when the name table is not.

## See also

- [Privileges](/peios/security-fundamentals/privileges/overview.md) — the model these records feed.
- [The token command](/peios/security-fundamentals/tokens/token-command.md) — reading the privileges and integrity a live token actually carries.
- [Managing local principals](/peios/security-fundamentals/managing-local-principals/overview.md) — the accounts these records apply to.
