# Tokens

---

# Tokens

_Peios / Peios Security Fundamentals / Tokens_

> A token is the kernel object that carries an identity into every system call — what it contains, where it comes from, and how it lives and dies.

A **token** is the runtime carrier of identity in Peios. It is a reference-counted kernel object that holds a user SID, group SIDs, privileges, confinement capabilities, and a handful of other policy fields. Every thread on the system has exactly one effective token at any moment, and every access decision starts by reading that token.

If [Identity](/peios/security-fundamentals/identity/overview.md) describes who a principal is, the token is the concrete thing that says "this thread is acting as that principal, right now". Tokens appear throughout these docs because they are the input to AccessCheck for files, registry keys, processes, sockets, and the tokens themselves.

## What a token contains

A token is a structured object with around twenty fields. The full field list is covered in [Token types and fields](/peios/security-fundamentals/tokens/token-types.md); this section sketches the shape so you have a mental model going into the rest of the topic.

| Group | Fields | What they do |
|---|---|---|
| **Identity** | `user_sid`, `groups`, `logon_sid`, `restricted_sids` | Who the token represents. |
| **Type and level** | `token_type`, `impersonation_level`, `integrity_level`, `mandatory_policy` | What kind of token this is and how it may be used. |
| **Defaults** | `owner_sid_index`, `primary_group_index`, `default_dacl` | What the token contributes when new objects are created without an explicit security descriptor. |
| **Session and provenance** | `auth_id`, `source`, `token_id`, `created_at`, `expiration`, `origin` | Where the token came from and which authentication event it belongs to. |
| **Privileges** | privilege bitmask (present / enabled / used / removed states) | System-wide rights the token holds. |
| **Confinement** | `confinement_sid`, `confinement_capabilities`, `confinement_exempt` | Sandboxing constraints, when the token is for a confined application. |
| **Claims** | `user_claims`, `device_claims`, `device_groups` | Typed attributes for conditional ACE evaluation. |
| **Projection** | `projected_uid`, `projected_gid`, `projected_supplementary_gids` | Linux-compatibility identity numbers, derived from the rest of the token. |
| **Audit policy** | `audit_policy` bitmask | Per-token forced auditing flags. |
| **Self SD** | `security_descriptor` | The token is itself an object — this is its own SD, governing who can read or adjust it. |

Most fields are set once when the token is minted and never change. A few — the enabled state of privileges, the enabled state of groups, the default DACL — are adjustable at runtime within strict rules. None of the identity fields (the SIDs themselves) ever change. See [Token types and fields](/peios/security-fundamentals/tokens/token-types.md) for the exact rules.

## Tokens and threads

```mermaid
flowchart LR
    A["Thread"] -->|primary token| B["Primary token"]
    A -->|impersonation token, when set| C["Impersonation token"]
    B -->|read by AccessCheck| D["Kernel"]
    C -->|read by AccessCheck when in effect| D
```

Every thread has a **primary token** — its baseline identity, inherited at fork and shared with every other thread in the process. While the thread is running normally, the primary token is what AccessCheck reads.

A thread may also temporarily install an **impersonation token** — a second token that overrides the primary for that one thread until it is reverted. Services use impersonation to act on behalf of a client they are handling: accept the connection, impersonate the client, do the work as them, revert. The primary token survives unchanged; only the one thread sees the impersonated identity, and only until it reverts.

Impersonation has its own topic: [Impersonation](/peios/security-fundamentals/impersonation/overview.md). For this page it is enough to know that a thread can have two tokens at once, and the kernel reads whichever is "in effect" right now.

## Where a token comes from

A token is always minted by code holding `SeCreateTokenPrivilege`. In a running system that effectively means **authd** — the authentication authority — which mints tokens once a principal source has verified who is signing in, and **peinit**, which mints service tokens during boot before authd is up.

The minting flow is straightforward:

1. Authentication succeeds, or a service is being launched.
2. The minting component gathers what the token will carry: the identity and memberships the principal source asserted, and the privileges and integrity level this machine's policy grants the principal.
3. It calls `kacs_create_token` with the resolved values.
4. The kernel returns a token file descriptor with `TOKEN_ALL_ACCESS`.
5. The minting component installs the token on the target process (the new login session's first process, or the service binary) and closes the fd.

From that point on the token lives in the kernel, referenced by every thread of every process that inherits it. The original fd is gone; the only handle into the token is through the threads carrying it (and any other process that opens it via `/proc/<pid>/token` or `kacs_open_process_token`).

No other component creates tokens. There is no API for an ordinary process to fabricate an identity for itself. A process can ask the kernel to **duplicate** a token it already has, or **filter** one to a more restricted version, but the original always comes from authd or peinit. This is deliberate: it is what makes the kernel's "where did this identity come from?" question always answerable.

## Tokens are objects too

A token is itself one of the objects KACS protects. It has a security descriptor of its own, and operations on it (querying, adjusting, impersonating, duplicating, installing) all go through AccessCheck against that SD.

| Right | Action it gates |
|---|---|
| `TOKEN_QUERY` | Read token information — fields, groups, privileges, etc. |
| `TOKEN_DUPLICATE` | Create an independent copy via DuplicateToken or FilterToken. |
| `TOKEN_IMPERSONATE` | Install as a thread's impersonation token. |
| `TOKEN_ASSIGN_PRIMARY` | Install as a process's primary token. |
| `TOKEN_ADJUST_PRIVILEGES` | Enable, disable, or permanently remove privileges. |
| `TOKEN_ADJUST_GROUPS` | Enable or disable groups. |
| `TOKEN_ADJUST_DEFAULT` | Change the default DACL, owner index, or primary group index. |
| `TOKEN_ADJUST_SESSIONID` | Change `interactive_session_id` (additionally requires `SeTcbPrivilege`). |

By default, a freshly minted token grants the SYSTEM identity and the creating principal full access, and grants the token's own user identity `TOKEN_QUERY` and the adjustment rights.

This self-protection is what stops one thread from "stealing" another's identity. Even reading the fields of another process's token requires the `PROCESS_QUERY_INFORMATION` right on the target process and the relevant token rights on the token itself.

## The two token rules to remember

Almost every confusion about tokens reduces to one of two rules.

**Rule 1: A thread always has a token.** There is no "no identity" state. A thread that has not been given a more specific token is running on whichever token its process inherited — at the very least, the SYSTEM token from boot. Code that runs before authd is up runs as SYSTEM, which has every privilege. Code that runs after authd is up runs as whichever identity authd assigned. There is no third option.

**Rule 2: Identity is the token, not the process.** A process does not "have an identity" except through the tokens of its threads. Two threads in the same process can be running as different principals at the same instant, because one is impersonating and the other is not. When you debug an "access denied" question, the answer is in the thread's token, not the process's notional identity.

## Where to start

If you want to understand the token field-by-field — what each value means, when it can change, how it is encoded — read [Token types and fields](/peios/security-fundamentals/tokens/token-types.md).

If you want to know how a token moves through fork, exec, adjustment, and destruction, read [Token lifecycle](/peios/security-fundamentals/tokens/lifecycle.md).

If you are interested in the restricted-token model — the sandbox primitive where a token is intersected with a secondary identity list — read [Restricted and write-restricted tokens](/peios/security-fundamentals/tokens/restricted-tokens.md).

If you need to understand UAC-style elevation — the linked Full/Limited token pair that lets one principal have two tokens for the same session — read [Elevation and linked tokens](/peios/security-fundamentals/tokens/elevation.md).

To work with tokens from a shell — inspect, adjust, duplicate, restrict, impersonate — read [The token command](/peios/security-fundamentals/tokens/token-command.md).

---

# Token types and fields

_Peios / Peios Security Fundamentals / Tokens_

> The axes a token is classified along — primary or impersonation, impersonation level, restricted, elevation type — and the token's fields grouped by purpose.

Every token in Peios is classified along several axes at once. A given token is either a primary token or an impersonation token. If it is an impersonation token it has an impersonation level. It is either restricted or not. If it is part of an elevation pair it has an elevation type. These are not types in an inheritance sense — they are independent dials, and a token's behaviour is the product of all of them.

This page walks through each axis, then through the token's fields grouped by what they do.

## token_type — primary or impersonation

This is the first and most important classification.

A **primary token** is the baseline identity of a process. Every process has exactly one. It is inherited across fork — child processes start with a copy of the parent's primary token — and survives exec. When a thread runs without doing anything special, AccessCheck reads its primary token.

An **impersonation token** is a thread-local override. A thread installs an impersonation token to act as someone else for a span of time, then reverts back to its primary. While the impersonation is in effect, AccessCheck reads the impersonation token instead of the primary. Other threads in the same process are unaffected.

The two types are not interchangeable. Trying to install a primary token as a thread's impersonation, or vice versa, fails. The kernel's `kacs_open_self_token` and related syscalls return tokens of the correct type for what they were asked to do.

| Use case | Token type |
|---|---|
| A login session's identity | Primary |
| A server thread acting on behalf of a connected client | Impersonation |
| A service running with the identity authd assigned to it | Primary |
| A thread querying its caller's identity for an audit log entry | Impersonation (at the Identification level — see below) |

## impersonation_level — how far an identity may travel

Every token carries an impersonation level. For primary tokens this is set to a conventional value (Anonymous) and ignored. For impersonation tokens it is what bounds what the impersonator may do.

There are four levels, ordered from least to most permissive:

| Level | What a server may do with this token |
|---|---|
| **Anonymous** | No identity. The token's user SID is the well-known Anonymous SID (`S-1-5-7`). The server learns nothing about the client. |
| **Identification** | The server may inspect the client's identity — read their SIDs, groups, integrity level — but may not use the token for any access check. AccessCheck with an Identification-level token returns ACCESS_DENIED immediately, no matter what. |
| **Impersonation** | The server may act as the client for all local operations. This is the default, and the level most server-to-client code paths assume. |
| **Delegation** | Same as Impersonation locally, plus the server may forward the client's credentials to a remote machine over Kerberos. KACS only tracks the level; the actual forwarding is authd's job. |

The level is set by the **client**, on the socket, before connecting. The server cannot raise it. It can only end up with the level the client granted, or lower if other constraints intervene (see [Impersonation](/peios/security-fundamentals/impersonation/overview.md) for the full two-gate model).

The Identification level is a common source of confusion. A frequent bug — in services not written for Peios — is to take a client's token and try to open files as them, finding that every open returns ACCESS_DENIED for reasons the code cannot diagnose. The cause is almost always that the client connected at Identification level and the server is supposed to be inspecting, not acting. The correct fix is at the client end: ask for Impersonation.

## restricted — a secondary identity check

A **restricted token** carries two SID lists: the normal `groups` list and a secondary `restricted_sids` list. During AccessCheck the kernel runs the DACL walk twice and intersects the results — once with the full identity, once with only the restricted SIDs. A right is granted only if both passes grant it.

The effect is to limit a token's identity-based access to what the restricted SIDs would independently receive. The user is still the same user; they just cannot use group memberships or other SIDs that are not in the restricted list to satisfy an ACE.

A **write-restricted** token is the same idea, but the intersection applies only to write-category rights. Reads and execute come from the normal pass alone. This is the common case: a sandbox that should be able to read system files but only write into a narrow allowed set.

Both variants are covered in detail under [Restricted and write-restricted tokens](/peios/security-fundamentals/tokens/restricted-tokens.md). For this page it is enough to know that "restricted" is a property of the token, set at creation by FilterToken, and that the kernel handles the second pass automatically.

## elevation_type — half of a linked pair

When a principal has both a normal (Limited) token and an elevated (Full) token — the UAC-style pattern — the two tokens are linked at the session level. Each carries an `elevation_type` saying which half it is:

| Value | Meaning |
|---|---|
| **Default** | The token is not part of a linked pair. Most tokens. |
| **Full** | The elevated half of a linked pair. |
| **Limited** | The non-elevated half of a linked pair. |

A linked pair is established by an explicit call to `KACS_IOC_LINK_TOKENS`. The elevation type does not change anything about the token's access rights by itself — it is a label that lets the system locate the partner token when the user requests elevation. The mechanics live in [Elevation and linked tokens](/peios/security-fundamentals/tokens/elevation.md).

## Field mutability classes

Token fields fall into three classes. Knowing which class a field is in tells you whether you can ever change it after the token is minted.

| Class | Meaning | Examples |
|---|---|---|
| **Fixed** | Set at creation, never changes. Cannot be adjusted at runtime even by SYSTEM. | `user_sid`, `groups[].sid`, `logon_sid`, `restricted_sids`, `token_id`, `auth_id`, `created_at`, `source`, `confinement_sid`, `mandatory_policy`. |
| **Adjustable** | Can be changed at runtime through a specific syscall. | Privilege enabled-state (via AdjustPrivileges), group enabled-state (via AdjustGroups), default DACL / owner index / primary group index (via AdjustDefault), `security_descriptor` (via kacs_set_sd). |
| **One-way** | Can be tightened but never loosened. | Privilege presence (a privilege can be removed but not re-added), group `USE_FOR_DENY_ONLY` (can be set but not cleared). |

This split is the source of one rule worth memorising: the identity in a token cannot change. You cannot relabel a token to a different user. You cannot add a group to a token. You can only adjust how existing identity bits are used — enable or disable a privilege, mark a group as deny-only, narrow the default DACL.

If you need a different identity, you need a different token. Either authd mints a new one for you (with a re-authentication), or you DuplicateToken / FilterToken your existing one into a more restricted copy.

## Fields by purpose

The token's fields, grouped by what they do. None of this is a low-level binary layout — that lives in the [Kernel ABI reference](/peios/using-peios/kernel-abi-reference/overview.md) topic. This is the conceptual field list.

### Identity

The fields that say who the token represents.

| Field | What it is |
|---|---|
| `user_sid` | The primary identity of the token. The principal's SID. |
| `groups` | An array of `SID_AND_ATTRIBUTES` — every group the principal is a member of, each with its current enabled/disabled state and other flags. |
| `logon_sid` | The session-specific SID (`S-1-5-5-X-Y`) for the logon session this token belongs to. Also appears in `groups` with the `SE_GROUP_LOGON_ID` flag. |
| `restricted_sids` | Secondary identity list for restricted tokens. Empty on unrestricted tokens. |

### Type and level

| Field | What it is |
|---|---|
| `token_type` | Primary or Impersonation. |
| `impersonation_level` | One of the four levels listed above (only meaningful for Impersonation tokens). |
| `integrity_level` | Normally one of Untrusted / Low / Medium / High / System; technically any numeric `S-1-16` level (see [MIC](/peios/security-fundamentals/access-decisions/mandatory-integrity-control.md)). |
| `mandatory_policy` | The token's MIC enforcement flags (`NO_WRITE_UP`, `NEW_PROCESS_MIN`). |

### Defaults

These fields contribute to the security descriptor of a new object when the creator does not supply one explicitly.

| Field | What it is |
|---|---|
| `owner_sid_index` | Index into `[user_sid, groups[0..N-1]]` selecting which SID becomes the default owner. |
| `primary_group_index` | Same idea for the default primary group. |
| `default_dacl` | The DACL applied to new objects when no explicit SD is supplied. |

### Session and provenance

| Field | What it is |
|---|---|
| `token_id` | A unique LUID identifying this token instance. |
| `auth_id` | The LUID of the logon session this token belongs to. |
| `source` | An 8-character name plus a LUID identifying the component that minted the token (e.g. authd). |
| `created_at` | Timestamp of original minting. Copied unchanged by DuplicateToken and FilterToken. |
| `expiration` | Expiry timestamp. Set by authd. **Informational only in v0.20** — not enforced by AccessCheck. |
| `origin` | The originating logon session for derived tokens (S4U, network logon). |
| `modified_id` | A counter bumped on every adjustment. Used as a cache-invalidation key. |

### Privileges

Privileges live in a 64-bit bitmask with four states per privilege:

- **Absent** — not on this token at all.
- **Present, disabled** — on the token but not currently in effect. May be enabled.
- **Present, enabled** — in effect; AccessCheck will use it.
- **Used** — a sticky audit bit set when the privilege has been exercised. Never cleared.

See [Privileges](/peios/security-fundamentals/privileges/overview.md) for the model in detail and the catalog of named privileges.

### Confinement

| Field | What it is |
|---|---|
| `confinement_sid` | If non-null, the token is for a confined application; its identity for access checks is intersected with this SID. |
| `confinement_capabilities` | The list of capability SIDs the confined application has declared. |
| `confinement_exempt` | Escape hatch — if set, confinement is skipped entirely. |
| `isolation_boundary` | Reserved for future use; not enforced in v0.20. |

See [Confinement](/peios/security-fundamentals/confinement/overview.md) for the sandbox model.

### Claims

| Field | What it is |
|---|---|
| `user_claims` | Typed attributes about the user, populated from the directory at authentication time. Used by `@User.*` references in conditional ACEs. |
| `device_claims` | Same idea for the machine. Used by `@Device.*`. |
| `device_groups` | The machine's group memberships, for compound identity. |

See [Claims on a token](/peios/security-fundamentals/identity/claims.md).

### Projection

These fields hold pre-computed Linux UID/GID values for compatibility. They are derived from the token, never the other way around.

| Field | What it is |
|---|---|
| `projected_uid` | The Linux UID the token's identity maps to (or `65534` if unmapped). |
| `projected_gid` | Same for primary GID. |
| `projected_supplementary_gids` | Same for supplementary GIDs. |

See [Linux compatibility](/peios/advanced-peios/linux-compatibility/overview.md) for what consumes these.

### Audit policy

| Field | What it is |
|---|---|
| `audit_policy` | A bitmask of per-token forced-audit flags (`OBJECT_ACCESS_SUCCESS`, `OBJECT_ACCESS_FAILURE`, `PRIVILEGE_USE_SUCCESS`, `PRIVILEGE_USE_FAILURE`). |

When a flag is set, AccessCheck emits the corresponding audit event regardless of the object's SACL.

### Self SD

| Field | What it is |
|---|---|
| `security_descriptor` | The token is itself a protected object; this is its own SD. Governs who may query, duplicate, impersonate, install, or adjust the token. |

### Other

| Field | What it is |
|---|---|
| `interactive_session_id` | The interactive session number. Zero for services. Adjustable only with `SeTcbPrivilege`. |
| `elevation_type` | Default / Full / Limited, for linked-pair membership. |

## Where to go next

For how these fields are set, shared, adjusted, and destroyed over a token's life — mint, fork, exec, impersonation, adjustment — read [Token lifecycle](/peios/security-fundamentals/tokens/lifecycle.md).

For the full rules around impersonation tokens and their levels, read [Impersonation](/peios/security-fundamentals/impersonation/overview.md).

To read these fields off a live token from a shell, read [The token command](/peios/security-fundamentals/tokens/token-command.md).

---

# Token lifecycle

_Peios / Peios Security Fundamentals / Tokens_

> A token's life from minting by authd or peinit to destruction at the last reference — fork, exec, impersonation, adjustment, and the default DACL.

A token is reference-counted. It comes into existence when a privileged component mints it, is reference-counted up and down as it gets attached to processes and threads, and is destroyed when the last reference drops. Between mint and destruction it moves through a handful of well-defined transitions. None of them change a token's identity — but each affects how the token is reached, how many references it has, or how its mutable fields are set.

## Three ways to mint a token

A token is always created by code holding the right privilege:

| Operation | Effect | Privilege required |
|---|---|---|
| **`kacs_create_token`** | Mint a token from scratch using a wire-format specification. | `SeCreateTokenPrivilege` |
| **DuplicateToken** | Make an independent copy of an existing token. The copy is a new object; modifying it does not affect the source. | `TOKEN_DUPLICATE` on the source |
| **FilterToken** | Make a copy of an existing token with privileges removed, groups marked deny-only, or restricted SIDs added. | `TOKEN_DUPLICATE` on the source |

`kacs_create_token` is what authd and peinit use to mint genuinely new identities. The other two — DuplicateToken and FilterToken — produce copies derived from a token a process already has. A process can FilterToken its own primary token down to a more restricted version and install the result as the primary token of a child it is about to launch.

In all three cases the kernel:

1. Validates the inputs (well-formed SIDs, no duplicate luids, all index references in range, etc.).
2. Allocates a new token object with `refcount = 1`.
3. Assigns a fresh `token_id` LUID and `modified_id = 0`.
4. Stamps the `created_at` timestamp.
5. Injects the appropriate logon SID into the groups list (callers must not supply it).
6. Returns a token file descriptor with `TOKEN_ALL_ACCESS` (for create) or the access mask the caller asked for (for duplicate).

Any validation failure results in no token being created — the operation is all-or-nothing.

## Attaching to a process

A token by itself is just an object the kernel holds. It does not yet identify anyone. To take effect, it has to be **attached** to a process or thread.

There are three attachment paths:

- **Inheritance at fork** — the child's primary token is the parent's primary token. The token's reference count goes up by one.
- **Installation by a privileged caller** — `KACS_IOC_INSTALL` on a token fd makes the token the calling process's primary. Requires `TOKEN_ASSIGN_PRIMARY` on the token and `SeAssignPrimaryTokenPrivilege` on the caller. Used by peinit when launching a service: fork, install the service token on the child, exec the binary.
- **Impersonation on a thread** — `KACS_IOC_IMPERSONATE` on a token fd makes the token the calling thread's impersonation token. Requires `TOKEN_IMPERSONATE`. The process-wide primary token is unchanged.

A given token can be attached in all three ways at once: shared as the primary of N processes, also held as the impersonation token of M threads. Each attachment is a reference.

## Fork, exec, and the primary token

```mermaid
flowchart LR
    A["Parent process (primary token T)"] -->|fork| B["Child process (primary token T)"]
    B -->|exec| C["Child process (primary token T, possibly with lowered integrity)"]
```

**Fork** copies the parent's primary token pointer into the child. Both processes now share the same token object. Adjustments made by the parent are visible to the child instantly — they share the storage. Adjustments made by either process via `AdjustPrivileges` etc. affect the shared token.

What does not survive fork is the parent's impersonation. A thread that forks while impersonating gets a child whose primary token is the parent's primary (not the impersonation). The child is not impersonating; its first thread is running on the inherited primary.

**Thread clone** (`CLONE_THREAD`) is different: the new thread is part of the same process, so it shares the same primary token. Privilege or group adjustments made by any thread are visible to all of them immediately.

**Exec** keeps the primary token. The new binary runs as the same identity. One subtle exception: if the token's `mandatory_policy` has `NEW_PROCESS_MIN` set and the executable carries a lower integrity label than the token, the kernel creates a copy of the token with integrity lowered to match, replaces the primary with that copy, and drops the original reference. This is the mechanism that prevents Medium-integrity code from running at Medium when its image is labelled Low.

Impersonation is **always reverted at exec**. A thread that execs while impersonating has its impersonation token released before the new program runs. This is enforced — the new binary cannot inherit an impersonation it did not establish.

## Impersonation install and revert

A thread becomes an impersonator by installing an impersonation token. The two ways to do it:

- **`kacs_impersonate_peer(fd)`** — extract the peer's identity from a connected Unix socket and install it at the appropriate level. The most common path for services accepting client connections.
- **`KACS_IOC_IMPERSONATE`** on a token fd — install a specific token (for transports that do not carry a peer token, or when the server has obtained a token by some other means).

Either operation has the same effect: the thread now has a primary token (unchanged) and an impersonation token (newly installed). AccessCheck reads the impersonation token from this point.

A thread reverts with **`kacs_revert`**. It always succeeds. It drops the impersonation reference and restores the original primary as the effective identity.

If a thread that is already impersonating installs a different impersonation token, the kernel silently reverts the old one first and then installs the new one. There is no nesting.

Impersonation tokens come from one of three sources during the install:

- A peer's identity captured at socket connect time.
- A duplicate of an existing token (DuplicateToken to a token type of Impersonation with the desired level).
- A new mint from authd (rare).

The level on an impersonation token is set by the **client** at connect time, never raised by the server. The full two-gate model — identity gate plus integrity ceiling — is in [Impersonation](/peios/security-fundamentals/impersonation/overview.md).

## Adjustment in place

Some fields can be changed at runtime. The operations are:

| Syscall / ioctl | What it changes |
|---|---|
| **AdjustPrivileges** | Enable, disable, or permanently remove privileges. A reset-all sentinel restores defaults. |
| **AdjustGroups** | Enable or disable group entries. Cannot target mandatory groups, deny-only groups, the logon SID, or the user SID. |
| **AdjustDefault** | Modify the default DACL, owner index, or primary group index. |
| **`kacs_set_sd`** on the token fd | Modify the token's own self-SD. Requires `WRITE_DAC` on the token. |

All adjustments are atomic — invalid input rolls the whole operation back, no partial change. Each successful adjustment bumps `modified_id` so caches keyed on token state can invalidate.

Because the token storage is shared across all threads of a process, an adjustment made by one thread is visible to all of them immediately. This includes adjustments to the primary token of a process that has just installed it but not yet had every thread converge — the kernel handles the convergence asynchronously, and during the brief window other threads may still see the old token. This window is small enough not to matter in practice but is worth knowing about if you are writing tests.

### Permanent privilege removal

When a privilege is **removed** rather than just disabled, it is gone from the token permanently. The token's privilege bitmask loses the present and enabled-by-default bits. The `used` bit stays — its purpose is auditing, and the fact that the privilege was once exercised is information that should not disappear.

Removal is irreversible by design. There is no API to re-add a removed privilege. The only way back is a different token (a fresh authentication, or a duplicate from a source that still has the privilege).

The same applies to `SE_GROUP_USE_FOR_DENY_ONLY` set on a group: it can be marked, but never cleared. Marking a group deny-only is a one-way trip down the access ladder.

## The default DACL

Every token carries a **default DACL**. It is the DACL that gets applied to a new object when:

- The creator (for example a `kacs_open` with `CREATE` disposition, or a registry-key create) does not supply an explicit SD.
- The object's parent has no inheritable ACEs that fully cover it.

In other words, the default DACL is the fallback. It guarantees that any object a token creates has at least some DACL, even when nothing else supplies one.

The default DACL is adjustable via `AdjustDefault`. A typical default DACL grants the token's user identity and SYSTEM full access and excludes everyone else. Services that want a more permissive or more restrictive default can change it once at startup.

The default owner and primary group also live on the token, as indices into the `[user_sid, groups[0..N-1]]` array. They tell the kernel which SID to stamp as `owner` and which to stamp as `primary_group` when synthesising a new SD. They are adjustable through the same call.

## Reference counts and destruction

Every attachment is a reference. A token is alive as long as any of these are true:

- Any process has it as a primary token.
- Any thread has it as an impersonation token.
- Any process has it open via a token fd.
- It is part of an established linked pair on a still-existing logon session.

When the last reference drops, the kernel destroys the token: frees the storage, releases the reference on the logon session. If the destroyed token's session loses its last token reference too, the session itself is destroyed and a `logon-session-destroyed` event is emitted via KMES. See [Logon sessions](/peios/security-fundamentals/logon-sessions/overview.md).

A token's `expiration` field has no effect on the lifecycle in v0.20 — it is stored for future use but not enforced. A token lives until its references drop. Session revocation, when needed, is implemented by userspace (authd) walking `/proc/*/token`, identifying tokens with the offending `auth_id`, and killing the holding processes. This is documented under [Inspecting security state](/peios/security-fundamentals/inspecting/overview.md).

## Quick reference: which transitions change what

| Transition | Identity | Privileges | Groups | Integrity | Refcount |
|---|---|---|---|---|---|
| Fork | Same | Same | Same | Same | +1 (shared) |
| CLONE_THREAD | Same | Same | Same | Same | Same object |
| Exec (same integrity) | Same | Same | Same | Same | Same object |
| Exec (NEW_PROCESS_MIN downgrade) | Same | Same | Same | Lowered | New token |
| Impersonation install | Different (impersonation token) | Different | Different | Different | +1 on impersonation token |
| Impersonation revert | Back to primary | Back to primary | Back to primary | Back to primary | −1 on impersonation token |
| DuplicateToken | Same | Same | Same | Same | New token with its own count |
| FilterToken | Same | Subset | Subset (some deny-only, restricted_sids added) | Same | New token |
| AdjustPrivileges | Same | Changed (within rules) | Same | Same | Same object |
| AdjustGroups | Same | Same | Changed (within rules) | Same | Same object |
| KACS_IOC_INSTALL | Same token, different process | — | — | — | New attachment |
| Process exit | — | — | — | — | −1 |

## Where to go next

For what FilterToken's restricted variants actually do at access-check time, read [Restricted and write-restricted tokens](/peios/security-fundamentals/tokens/restricted-tokens.md).

For the session a token belongs to and what happens when the last token of a session is released, read [Logon sessions](/peios/security-fundamentals/logon-sessions/overview.md).

To drive these transitions from a shell — duplicate, restrict, adjust, install — read [The token command](/peios/security-fundamentals/tokens/token-command.md).

---

# Restricted and write-restricted tokens

_Peios / Peios Security Fundamentals / Tokens_

> A restricted token carries a secondary SID list; AccessCheck runs twice and intersects the results. Write-restricted applies the same idea to write rights only.

A **restricted token** is the kernel's narrow-the-identity primitive. It is a normal token with an extra list of SIDs attached — the `restricted_sids` list. During AccessCheck the kernel runs the DACL walk twice: once with the token's full identity, once with only the restricted SIDs. A bit is granted only if both passes grant it.

The effect is to put a ceiling on what the token can reach, expressed as "the rights this token would have if it were just those restricted SIDs". The identity (user, groups, privileges) is unchanged — but the kernel will only honour an ACE if the restricted-only view of the token would have honoured it independently.

Restricted tokens are the building block underneath several patterns: service hardening, sandbox processes, anti-malware quarantine, anything that says "this code should have less authority than the user it is running as".

## The two-pass model

```mermaid
flowchart TD
    A["AccessCheck"] -->|with full identity| B["Normal pass (user_sid + groups + ...)"]
    A -->|with restricted SIDs only| C["Restricted pass (restricted_sids only)"]
    B -->|granted_normal| D["Intersect"]
    C -->|granted_restricted| D
    D -->|granted = normal ∩ restricted| E["Final granted mask"]
```

When KACS evaluates AccessCheck for a restricted token, it runs through the same pipeline twice:

1. **Normal pass.** The DACL walk uses the token's `user_sid`, all enabled groups, and all the usual rules. Produces a `granted_normal` mask.
2. **Restricted pass.** The DACL walk runs again, but the only SIDs considered for matching are those in `restricted_sids`. The `user_sid` does not match. None of the regular `groups` match. Only the entries in `restricted_sids` count. Produces a `granted_restricted` mask.
3. **Intersection.** The final granted mask is `granted_normal & granted_restricted`.

Both passes must agree to grant a right. If the normal pass would grant `FILE_WRITE_DATA` but the restricted SIDs do not appear in any allow ACE for that right, the bit is dropped.

The restricted SIDs are usually narrow on purpose. A common pattern is to put a single capability SID (for example `internetClient`) in the restricted list — the token now has authority only on objects whose DACLs explicitly grant access to that capability.

## What is not affected

A few things are explicitly **not** narrowed by the restricted pass:

- **Privileges.** Privilege-granted access (SeBackup, SeRestore, SeTakeOwnership, SeSecurity) is restored after the intersection. A restricted token with SeBackup can still read any file the privilege would have granted.
- **The default DACL on new objects.** A restricted token still creates new objects with its default DACL, derived from its full identity.
- **Reading the token's own state.** Querying a restricted token does not require both passes; the token's self-SD governs that as normal.
- **MIC.** Mandatory integrity is evaluated before the DACL walks. A restricted token cannot use the restricted-SID trick to escape integrity rules.

Privileges being orthogonal to the restricted pass is the most important of these. The restricted-token model narrows identity-based access, not capability-based access. If you want to drop privileges too, that is a separate operation — see "Creation" below.

## Write-restricted: the common case

A **write-restricted** token narrows only the write-category rights. Reads and execute come from the normal pass alone; only write-mapped bits go through the intersection.

The motivation: it is rare to want a sandbox that cannot read anything in the normal world. Sandboxed code usually needs to load shared libraries, read configuration, perhaps look up its own metadata. What it must not do is write — into the user's home directory, into system paths, into any file outside the small set the sandbox explicitly allows.

In a write-restricted token:

| Right category | Where it comes from |
|---|---|
| Read (FILE_READ_DATA, FILE_LIST_DIRECTORY, etc.) | Normal pass only. |
| Execute (FILE_EXECUTE, FILE_TRAVERSE) | Normal pass only. |
| Write (FILE_WRITE_DATA, FILE_APPEND_DATA, WRITE_DAC, etc.) | Intersection of both passes. |

The "category" is determined by which generic right the bit maps to. `FILE_WRITE_EA` is in the write category; `FILE_READ_EA` is in the read category; and so on. The generic mapping for each object type defines the partition.

There is a quirk worth noting. When a token is write-restricted, the kernel also sets a `user_deny_only` flag on the token internally. This causes the token's own `user_sid` to match **only deny ACEs** in any pass, never allow ACEs. The motivation is to prevent a token from getting write access on an object simply because its user SID matches an allow ACE — the write-restricted intersection would otherwise be too easy to bypass by writing an ACE that names the user directly. With `user_deny_only` set, the user SID can still trigger denials but cannot grant.

This is a subtle interaction; most code that uses write-restricted tokens does not need to think about it, but if you are debugging an access denial on a write-restricted token and the user appears in the DACL with an allow ACE, the explanation is here.

## Creation

There is one path to creating a restricted token: **`KACS_IOC_RESTRICT`** on a token fd — the ioctl behind the FilterToken operation named elsewhere in this topic. The operation takes:

- A list of privilege LUIDs to remove from the new token.
- A list of group indices to mark `SE_GROUP_USE_FOR_DENY_ONLY` in the new token (set once, irreversible).
- A list of restricting SIDs to put in the new token's `restricted_sids`.
- An optional flag enabling write-restricted mode (which also sets `user_deny_only`).

The result is a new token fd. The source token is unchanged.

A typical sandbox launcher does something like this:

1. Open its own primary token.
2. Call `KACS_IOC_RESTRICT` to produce a restricted variant: remove every privilege except `SeChangeNotifyPrivilege`, mark unsafe groups deny-only, add the sandbox's allowed capability SIDs as the restricted SIDs, set the write-restricted flag.
3. Fork.
4. `KACS_IOC_INSTALL` the restricted token on the child.
5. Exec the sandboxed binary.

The child now runs as the same user, but the user's group memberships are mostly invisible, the privileges are gone, and writes are confined to objects whose DACLs explicitly allow the sandbox's restricted SIDs.

## Restricted tokens vs confinement

Two things in Peios narrow what a token can reach: the restricted-token model on this page, and **confinement** (covered in [Confinement](/peios/security-fundamentals/confinement/overview.md)). They look similar at a glance, but they exist for different audiences.

**Restricted tokens are a tool for code.** A program — a service, a sandbox launcher, an anti-malware engine — uses FilterToken or `KACS_IOC_RESTRICT` to narrow a token it already has, then runs sensitive work on the result. The decision is made in code, by the program itself, before it hands the restricted token to the constrained operation. Nothing outside the program needs to know about it, and nothing outside the program enforces it — the program is choosing to give itself less authority.

**Confinement is a tool for policy.** A sysadmin — or a service definition the sysadmin has chosen to deploy — declares that some component runs as a confined application with a specific confinement SID and an enumerated set of capabilities. The kernel enforces that policy whether or not the confined code is aware of it. Confinement is an administrative decision applied from outside the program, and the program cannot opt out of it.

That difference in audience is the reason behind the technical differences:

| | Restricted | Confinement |
|---|---|---|
| Who decides | The program itself, in code | Administrative policy, applied from outside |
| Typical caller | A sandbox launcher, a hardened service, an anti-malware engine | A service manager applying a service definition; a container runtime |
| Storage on the token | `restricted_sids` list | `confinement_sid` + `confinement_capabilities` |
| Where it fires in AccessCheck | Inside the DACL walk, identity-based intersection | After the DACL walk and privileges, absolute intersection |
| Bypassable with a privilege? | Yes — privilege-granted bits survive | **No** — confinement is absolute |
| Owner implicit rights still apply? | Yes | **No** |
| Write-only variant available? | Yes (write-restricted) | No |

The technical asymmetry follows from the audience. A program restricting itself is trusting itself to use the primitive correctly — it can drop privileges if it wants to, leave them in if it doesn't, choose what its restricted SID set should be. Confinement is enforced against the code, so it has to be a harder line: privilege exercise and owner implicit rights are exactly the kinds of escape routes a confined application would otherwise reach for.

The two can be combined. A service that runs under a confinement policy and additionally restricts its own internal worker threads sets both. The kernel applies each layer; the final granted mask is the intersection of all of them.

## Practical patterns

A few patterns worth recognising:

- **Drop privileges only.** Sometimes you want to remove dangerous privileges without restricting identity at all. `KACS_IOC_RESTRICT` with a privilege removal list, an empty deny-only list, and an empty restricted_sids list does this — the result is a token with reduced privileges and no restricted-SID intersection.
- **Write-restricted with the deny-only user trick.** Set the write-restricted flag, leave `restricted_sids` containing only what you want the sandbox to be able to write to. The token's user SID can still match deny ACEs (so user-targeted denials still work) but cannot match allow ACEs on writes.
- **Capability-style sandbox.** Put one or more capability SIDs (well-known or derived) in `restricted_sids`. The sandbox can then reach only objects whose DACLs explicitly grant access to those capabilities, plus whatever its user identity grants in the normal pass.
- **Anti-malware quarantine.** Restrict to a narrow set of well-known SIDs (Everyone, Authenticated Users) and remove all privileges. The result is a token that can reach widely-shared resources but cannot exercise any system-level rights.

All of these are FilterToken / KACS_IOC_RESTRICT applied to the appropriate source token, with different inputs. The kernel does not distinguish between them; they are just patterns of use.

## Where to go next

For the linked Full/Limited token pair — the other derived-token pattern this topic covers — read [Elevation and linked tokens](/peios/security-fundamentals/tokens/elevation.md).

For the policy-driven counterpart to restricted tokens, read [Confinement](/peios/security-fundamentals/confinement/overview.md).

To build a restricted token from a shell, read [The token command](/peios/security-fundamentals/tokens/token-command.md).

---

# Elevation and linked tokens

_Peios / Peios Security Fundamentals / Tokens_

> Some principals carry two linked tokens — a Limited default and a Full elevated half — paired on the logon session and switched on request.

> [!IMPORTANT]
> **Not yet built in userspace.** The kernel side of this page is complete and works as described — `KACS_IOC_LINK_TOKENS`, `KACS_IOC_GET_LINKED_TOKEN`, and the `elevation_type` field are all implemented. **What does not happen yet is anything creating a pair.** `authd` mints a single token carrying everything the principal is entitled to, and `login` runs the session on it; no interactive logon produces a Limited half, and there is no broker holding a Full one.
>
> So on a current machine an administrator's shell holds their administrative privileges and High integrity for its whole life, and there is no elevation boundary. Read what follows as the model the system is built towards and the kernel already supports, not as a description of how your machine behaves today.
>
> The blocker is not the token work. A Limited token marks `BUILTIN\Administrators` as `USE_FOR_DENY_ONLY`, and the security descriptor a stock image ships grants only `LocalSystem` and `BUILTIN\Administrators` — so a Limited shell could not list a directory. Per-subtree descriptors have to come first, otherwise the boundary would land on a machine nobody could use.

A **linked token pair** is two tokens for the same principal, one elevated (Full) and one not (Limited), associated with each other through their shared logon session. The point of the pair is to give a user a default identity that is not fully privileged, while keeping a second identity available for explicit elevation when the user actually wants to do administrative work.

If you have used UAC on a Windows desktop, this is the same model. The Limited token is what runs the user's shell and most of their software. The Full token is what runs the action they have just been prompted to authorise. The kernel does not decide when to switch — that is a user-space decision, prompted by some authority broker — but the kernel is responsible for keeping the pair linked, locating the partner on demand, and enforcing the rules around who is allowed to see what.

## The model

```mermaid
flowchart LR
    A["Logon session"] -->|associates| B["Full token (elevation_type = Full)"]
    A -->|associates| C["Limited token (elevation_type = Limited)"]
    B <-->|partner| C
```

Both tokens share the same logon session (`auth_id`). They have the same user SID, the same logon SID, the same `created_at`. What differs:

- The Full token has whatever privileges and group memberships the principal is entitled to when running elevated — typically including BUILTIN\Administrators, SeBackup, SeRestore, etc.
- The Limited token is a filtered version — privileges removed, sensitive groups marked `USE_FOR_DENY_ONLY`. It is the version the user runs in by default.

Each carries an `elevation_type`:

| Value | Meaning |
|---|---|
| **Default** | Token is not part of a linked pair. The vast majority of tokens. |
| **Full** | The elevated half of a pair. |
| **Limited** | The non-elevated half of a pair. |

A token's elevation_type is set when it joins a pair, never cleared. If the session is destroyed, the pair linkage is removed but the individual elevation_type values stay until the token objects themselves are freed.

## Establishing a pair

A pair is created by an authority broker — almost always authd, occasionally peinit — using:

```
KACS_IOC_LINK_TOKENS(elevated_fd, filtered_fd, session_id)
```

The kernel requires:

- `SeTcbPrivilege` on the caller.
- `TOKEN_DUPLICATE` on both token fds.
- Neither token already linked.
- Both tokens part of the same session (`session_id`).
- The two tokens not the same token.

When the call succeeds, the kernel:

1. Records the pair on the session.
2. Sets the elevated token's `elevation_type = Full`.
3. Sets the filtered token's `elevation_type = Limited`.

Both tokens continue to be valid as independent tokens. The pair linkage is additional state on the session, not a property that bundles the two into one object.

A typical flow during user login — **none of which authd does yet**, per the note at the top of this page:

1. authd authenticates the user.
2. authd creates the user's session.
3. authd mints the Full token with all the user's entitled privileges and groups.
4. authd FilterTokens the Full token down to the Limited version — privileges removed, admin groups deny-only.
5. authd calls `KACS_IOC_LINK_TOKENS` to link the two.
6. authd installs the Limited token as the primary of the user's first process.
7. The Full token is kept available (via session state or an authority broker process) for later elevation requests.

The user's shell, file manager, web browser, and most of their applications now run on the Limited token. Whenever the user does something administrative, the authority broker — after whatever consent step is appropriate — fetches the Full token via `KACS_IOC_GET_LINKED_TOKEN` and installs it on the new process.

## Looking up the partner

```
KACS_IOC_GET_LINKED_TOKEN(token_fd) -> partner_fd
```

Given a token that is part of a pair, this ioctl returns a handle to the partner. The semantics depend on who is asking:

- **With `SeTcbPrivilege`**, the caller gets a full handle on the partner token — `TOKEN_ALL_ACCESS`, the actual token object. This is what authority brokers use to perform an elevation: fetch the Full token's fd, install it on a child process.
- **Without `SeTcbPrivilege`**, the caller gets a degraded handle — a freshly duplicated Identification-level clone of the partner, opened only with `TOKEN_QUERY`. The clone is enough to inspect the partner's identity ("am I currently the Limited token? what would the Full version contain?") but cannot be used for any access check and cannot be installed.

The asymmetry is deliberate. Anyone who can prove they hold one half of a pair is allowed to learn something about the other half — that is useful for diagnostics and for user-facing tools that want to display "you are running as Limited; admin rights are available". But actually wielding the elevated identity requires `SeTcbPrivilege`, the privilege held only by the authority broker that is supposed to gate elevation.

`KACS_IOC_GET_LINKED_TOKEN` fails with `-ENOENT` on a token whose `elevation_type` is `Default`, or on a token whose pair was destroyed when its session was destroyed.

## What the link does not do

A few clarifications on what linkage does not change:

- **It does not unify the two tokens.** They remain independent objects. Adjustments to one have no effect on the other. Destroying one does not destroy the other.
- **It does not change access decisions.** AccessCheck reads whichever token is currently in effect on the thread. The fact that a token has a partner is invisible to the access check — only the broker that calls `KACS_IOC_GET_LINKED_TOKEN` sees the pair.
- **It does not require either token to be installed.** A pair can exist on a session that has not yet attached either of its tokens to any process. (Unusual but valid.)
- **It does not change `auth_id`.** Both tokens still report the same session ID. The session is the level at which they are paired.

## Teardown

A pair is dissolved when the underlying logon session is destroyed. At that point:

- The pair association is removed from the session.
- Subsequent calls to `KACS_IOC_GET_LINKED_TOKEN` on either token return `-ENOENT`.
- The individual tokens themselves continue to exist as long as their reference counts hold. Their `elevation_type` values are unchanged but no longer meaningful.

A session is destroyed when its last token reference drops (see [Logon sessions](/peios/security-fundamentals/logon-sessions/overview.md)). For a linked pair, that means: both tokens must lose all their attachments — every process running on them must exit, every fd open on them must be closed, every impersonation token derived from them must be reverted.

For the Limited token this happens naturally when the user logs out. For the Full token, the authority broker is responsible for releasing it when the session ends — typically by holding it in a process that exits when the user logs out.

## Common patterns

**Default-Limited login.** Every interactive login establishes a pair where the user's shell runs as Limited. The Full token is held by the authority broker.

**Elevation on demand.** When the user invokes an administrative action (a control panel, a `sudo`-equivalent), the broker prompts for consent, then uses `KACS_IOC_GET_LINKED_TOKEN` to fetch the Full token and `KACS_IOC_INSTALL` to install it on the privileged child process.

**Service accounts (no pair).** Services do not generally need elevation; their tokens are unpaired and `elevation_type = Default`. A service that needs occasional elevated work is a different pattern — usually IPC to an already-elevated service rather than a linked pair within the same service.

**Diagnostics ("am I elevated?").** A process can call `KACS_IOC_GET_LINKED_TOKEN` on its own primary token (which it always has at least `TOKEN_QUERY` on). Without `SeTcb` it gets an Identification-level clone — enough to read `elevation_type` and `groups` on the partner and display "elevated rights available". The clone itself cannot be used for anything but inspection.

## Where to go next

For the session object that holds the pair together — and what happens to the pair when it dies — read [Logon sessions](/peios/security-fundamentals/logon-sessions/overview.md).

To inspect a token's elevation type and its partner from a shell, read [The token command](/peios/security-fundamentals/tokens/token-command.md).

---

# The token command

_Peios / Peios Security Fundamentals / Tokens_

> The token command inspects and manipulates tokens — reading a token's contents, adjusting it, duplicating and restricting it, and driving impersonation.

`token` is the command-line tool for working with **tokens** directly. It reads a token's contents, adjusts it, produces derived tokens, and drives impersonation — the low-level operations this topic describes, exposed at a shell.

```
token subcommand [target] [arguments]
```

```
$ token                       # one-line summary of your own token
$ token show --all             # every field of your own token
$ token privs --pid 4821       # the privileges on process 4821's token
```

`token` is a direct, debug-level tool. Day to day you do not inspect tokens by hand — the system does. `token` is for diagnosing an access problem, for understanding what identity a process is really running under, and for building and testing identity setups. Run with no subcommand, it prints a one-line summary of your own token.

## Choosing which token

Almost every subcommand operates on a token, and these flags choose which one. With none, the target is your own.

| Flag | Target |
|---|---|
| `--self` | Your own token. The default. |
| `--real` | Your **primary** token specifically, rather than the effective one — relevant when your thread is impersonating. |
| `--pid PID` | The primary token of process `PID`. |
| `--tid TID` | The impersonation token of thread `TID` (used with `--pid`). |
| `--peer SOCK_FD` | The peer's captured token on a connected socket — see [Peer tokens](/peios/security-fundamentals/impersonation/peer-tokens.md). |

Reading another process's token is itself access-controlled: it succeeds only with the right authority over that process.

## Inspecting a token

### `show`

`token show` prints a token's contents. It is the default — bare `token` is `token show --short`.

| Flag | Effect |
|---|---|
| `--short` | A one-line summary. |
| `--all` | Every query class — the fullest dump. |

### Field accessors

Each of these prints one part of a token, for when you want just that piece:

| Subcommand | Prints |
|---|---|
| `user` | The user SID — who the token is. |
| `owner` | The default owner SID. |
| `group` | The primary group SID. |
| `groups` | The group list. |
| `privs` | The privileges, with their enabled state. |
| `caps` | The capabilities. |
| `claims` | The user and device claims. |
| `integrity` | The integrity level. |
| `logon` | The logon type and logon SID. |
| `source` | What minted the token. |
| `origin` | The originating session for a derived token. |
| `stats` | Token statistics — IDs, timestamps, the modification counter. |
| `default-dacl` | The token's default DACL. |

### `query`

`token query CLASS` performs a raw read of a single named token-info class and prints the result as JSON — the lowest-level inspection route, for tooling.

## Changing a token

### `adjust`

`token adjust` mutates a token in place:

| Form | Changes |
|---|---|
| `adjust privs NAME=STATE …` | Enable, disable, or remove privileges. `STATE` is `enabled`, `disabled`, or `removed`. |
| `adjust groups IDX=STATE …` | Enable or disable groups by their list index. |
| `adjust default --dacl SDDL` | Replace the token's default DACL. Also `--owner-idx` / `--group-idx`. |
| `adjust session ID` | Replace the token's session id. |

### `restrict`

`token restrict` produces a [restricted token](/peios/security-fundamentals/tokens/restricted-tokens.md) — a more limited variant of a token.

| Flag | Effect |
|---|---|
| `--drop-privs MASK\|NAMES` | Privileges to drop. |
| `--deny IDX,…` | Group indices to mark deny-only. |
| `--restrict SID,…` | The restricting SIDs to apply. |

### `duplicate`

`token duplicate` (alias `dup`) copies a token, optionally changing its `--type` (primary or impersonation), its impersonation `--level`, or its `--access` mask.

### `link` and `linked`

`token link` joins two tokens as an elevation pair — a full token and its filtered counterpart — given their file descriptors and a session id. `token linked` shows a token's elevation-linked counterpart, if it has one. See [Elevation](/peios/security-fundamentals/tokens/elevation.md).

## Impersonation

| Subcommand | Effect |
|---|---|
| `impersonate` | Begin impersonating the target token on the calling thread. With a trailing `-- command …`, run that command under the impersonating token. |
| `revert` | Drop any active impersonation on the calling thread. |

See [Impersonation](/peios/security-fundamentals/impersonation/overview.md) for the model these drive.

## Creating tokens

| Subcommand | Effect |
|---|---|
| `create SPEC` | Create a token from a binary token-spec (`SPEC` is a file, or `-` for standard input). |
| `install SPEC` | Create a token from a spec and install it as the caller's primary token. |

Creating and installing tokens is a privileged operation, reserved for the components that legitimately mint identity.

## Output options

| Flag | Effect |
|---|---|
| `--raw` | Render SIDs in raw `S-1-…` form only. |
| `--label` | Render SIDs as their labels where known, falling back to raw. |
| `--json` | Emit JSON instead of human-readable output. |

## `token` and the inspection surfaces

`token` is the convenient front-end. Underneath, it reads the same kernel surfaces and rules described in [Inspecting tokens](/peios/security-fundamentals/inspecting/tokens.md) — that page covers the query mechanism, the access rules for reading another process's token, and what cannot be inspected.

## Exit status

| Code | Meaning |
|---|---|
| `0` | The operation succeeded. |
| `1` | A usage error. |
| non-zero | The operation failed — no such target, an access denial, or a bad spec. |
