# Security diagnostics reference

---

# Inspecting sessions

_Peios / Peios technical reference / Security diagnostics reference_

> Active logon sessions are listed at /sys/kernel/security/kacs/sessions. The listing format, the token-to-session lookup pattern, and lifecycle tracking.

Logon sessions are the kernel's records of authentication events. Every token belongs to one session; every running thread is acting under a token; therefore every running thread is associated with a session. Inspecting the system's active sessions tells you who is currently signed in, in what mode, when, and which authentication mechanism brought them in.

The primary inspection surface is **`/sys/kernel/security/kacs/sessions`** — a text-format pseudo-file that lists every active session. This page covers the listing format, the standard pattern for finding a session from a token, and how to track session lifecycle through the audit stream.

## The sessions pseudo-file

`/sys/kernel/security/kacs/sessions` is a text file produced by the kernel on each read. Each line describes one active session:

```
logon_session_id=<decimal-u64> user_sid=<lowercase-hex-sid> logon_type=<decimal-u32> auth_package=<lowercase-hex-utf8> created_at=<decimal-u64>
```

Fields are space-separated, in `key=value` form. The format is stable for the listed fields; consumers should **ignore unknown additional fields**, which future versions may append.

| Field | Type | Meaning |
|---|---|---|
| `logon_session_id` | decimal u64 | The session's LUID. Same as the `auth_id` recorded on every token belonging to this session. |
| `user_sid` | lowercase hex | The SID of the principal who signed in. |
| `logon_type` | decimal u32 | The logon type. See [Logon types](/peios/security-fundamentals/logon-sessions/logon-types.md). |
| `auth_package` | lowercase hex of UTF-8 | The auth-package name (e.g. "Kerberos", "NTLM", "local") encoded as lowercase hex of the UTF-8 bytes. |
| `created_at` | decimal u64 | When the session was made, in seconds since the Unix epoch (wall-clock time). |

The `user_sid` and `auth_package` fields are hex-encoded for parser stability — the SID is a binary structure, and the auth-package name could in principle contain characters that complicate text parsing. Hex encoding is uniform.

### Access rule

The kernel checks every read of the file against a fixed descriptor that grants read to `BUILTIN\Administrators` and `SYSTEM` only. Anyone signed in may open it; a non-administrative caller gets `EACCES` when it reads. This is intentional: the listing reveals every active session on the machine, including their identities and timestamps, which is information you do not want a low-privileged process to read.

The descriptor is built into the kernel, not stored on the file, so it cannot be widened. A service that needs to list sessions runs as SYSTEM or in Administrators; anything else can still find the sessions of the processes it may inspect, through their tokens (below).

securityfs itself cannot store descriptors, so peinit gives the mount a synthesised one at boot that lets Authenticated Users open and traverse it. Before that the file cannot be opened at all.

### Bootstrap sessions

Two sessions exist before authd is up:

| Session ID | Use |
|---|---|
| 999 | The SYSTEM session. Attached to init, inherited by every early-boot process. Stays present for the lifetime of the system. |
| 998 | The Anonymous session. Backs the singleton Anonymous token. |

Both appear in the listing. They are not bugs; their presence is the normal state of any running system.

## Finding a session from a token

The standard pattern: given a thread or process, find its session.

1. **Open the token.** For a thread, use `/proc/<pid>/task/<tid>/token`. For a process's primary, use `/proc/<pid>/token`. For yourself, use `kacs_open_self_token` or `/sys/kernel/security/kacs/self`.
2. **Query `TokenStatistics`** via `KACS_IOC_QUERY`. The response includes `auth_id` (the session's LUID).
3. **Look up `auth_id`** in `/sys/kernel/security/kacs/sessions` to find the matching `logon_session_id`. The line gives you the session's full details.

This is the standard "which session is this process in" query. The session ID is the key; the listing has the rest.

For programmatic enumeration ("for each running process, which session is it in"):

```
for each pid in /proc/*:
  for each tid in /proc/<pid>/task/*:
    open /proc/<pid>/task/<tid>/token
    query TokenStatistics, get auth_id
    cross-reference with the sessions listing
```

This produces a complete picture of which thread is in which session. The [`logonse`](/peios/using-peios/peiosutils/system-and-processes/logonse.md) command uses exactly this pattern — it walks the running processes to render which processes belong to which session.

## Tracking session lifecycle

Sessions are created and destroyed dynamically. The listing always reflects the *current* state; to track changes over time you need to either poll the listing or subscribe to session lifecycle events.

### Session creation

The kernel does not emit a kernel-level event when a session is created — there is no `logon-session-created` event in v0.20 audit. Tracking creations requires either:

- Periodic polling of `/sys/kernel/security/kacs/sessions` and comparing against the previous snapshot.
- Hooking into authd, which is the only thing that creates sessions and could in principle emit a higher-level event. (This is an authd integration concern, not a kernel one.)
- Listening for the audit events that fire on successful authentications — these are emitted by authd and consumed via KMES.

For most monitoring purposes, the audit stream from authd is the right source. The kernel-level sessions listing tells you "what is right now"; the audit stream tells you "what happened recently".

### Session destruction

The kernel **does** emit a `kacs.session.destroyed` event when a session loses its last token reference. The event includes the session ID, the user SID, the logon type, the auth-package name, and the creation time — enough to reconstruct what the session was. The session ID is the same value every audit record made under the session carries as `subject.token.auth-id`, so the two join.

The event is documented in [Events and transport](/peios/security-fundamentals/auditing/events-and-transport.md). Tools that want to track session lifecycle subscribe to this event via KMES and write a record on each occurrence.

The pattern: at session creation (detected via authd or via polling), record the start; at the `kacs.session.destroyed` event, record the end. The two together give you a complete session log.

## Inspecting an individual session

A session ID is just a u64, but the listing line gives you everything currently knowable about the session from the kernel's perspective. There is no separate "session detail" query that returns more than what the listing provides.

If you need detail beyond what the listing offers (the privileges granted at sign-in, the policy that applied, the auth-package's specific authentication flow), the source is authd's own state, accessible via authd's APIs. The kernel records the session existence and minimal metadata; authd records the rest.

This separation is the standard kernel/userspace split. The kernel knows the session exists, who it is for, when it was created, and what auth-package created it. authd knows what happened during authentication.

## Counting tokens per session

A session can have many tokens. The number of tokens belonging to a session is the count of:

- Primary tokens attached to processes whose `auth_id` matches.
- Impersonation tokens currently installed on threads whose `auth_id` matches.
- Token fds open against tokens with that `auth_id`.

Counting these from the outside is awkward — there is no "tokens per session" query. The standard way is to walk `/proc/*/token` and `/proc/*/task/*/token`, query `TokenStatistics` on each, and count matches.

authd's forced sign-out walks only the first half of that: it looks at each process's **primary** token and ends the processes whose `auth_id` matches. It deliberately does not count a thread impersonating a token of the session, which is usually a service answering one request for the person, and ending it would end the service for everybody.

The reason for the awkward enumeration: each token is a separate kernel object, and there is no per-session index. The kernel knows tokens reference sessions (via `auth_id`); it does not maintain a reverse index of which tokens reference which session. Walking the running processes is the way to find tokens that exist.

A token held only by a file descriptor with no associated running process — e.g., a token fd passed via SCM_RIGHTS to a recipient that hasn't installed it — is not enumerable by walking `/proc`. Such tokens still keep the session alive (refcount), but their existence is not visible to a session-enumeration tool. They will reveal themselves only when the holding process tries to use them.

## Session expiry

A session's `created_at` is set at creation; there is no `expires_at` in the listing. Sessions do not expire on a kernel timer. A session lives as long as its tokens have references; it ends when the last token reference drops.

> [!WARNING]
> Token `expiration` (a field on each token) is set by authd but not enforced by the kernel in v0.20. A token can have an expiration time in the past and still be valid for AccessCheck.

The token's session continues to exist regardless of any token's expiration value.

If a deployment needs strict session timeouts, the enforcement is in userspace, and nothing ships it today: authd does not watch session ages. What authd does provide is the means: a program with the right to sign people out (by default SYSTEM and Administrators) can compare `created_at` against its own maximum and ask authd to end a session that has run too long, with a `SessionEnd` request on `/run/logon.sock` ([PGSS §2.22](/peios/advanced-peios/pgss/logon/ending-a-session.md)). authd then ends every process whose primary token belongs to the session — `SIGTERM`, a grace period, `SIGKILL` — as described in [Session lifecycle](/peios/security-fundamentals/logon-sessions/lifecycle.md).

The lack of kernel-side timer enforcement is a deliberate simplification. Adding kernel timers for session expiry would push expiry policy into the kernel; keeping it in userspace lets administrators define their own rules.

## Where to go next

For the rest of a process's inspectable state — its PSB and its process SD — read [Inspecting processes](/peios/advanced-peios/security-diagnostics/processes.md).

For what sessions are, how they are created, and how revocation actually works, read [Session lifecycle](/peios/security-fundamentals/logon-sessions/lifecycle.md).

---

# Inspecting processes

_Peios / Peios technical reference / Security diagnostics reference_

> Inspecting a process's PSB (PIP, mitigations) through /proc/<pid>/psb, and its process SD. Your own state is free; another process's PSB needs PROCESS_QUERY_LIMITED, and its SD needs READ_CONTROL plus PIP dominance.

A process's inspectable state spans three things: its **token** (its identity), its **PSB** (its PIP labels and mitigation flags), and its **process SD** (the policy on the process as an object). Each is read through its own surface. Your own state is always readable; another process's needs a right on its process SD, and for everything but the PSB, PIP dominance too.

This page covers the per-process inspection surfaces beyond the token. Tokens are covered in [Inspecting tokens](/peios/using-peios/inspecting/tokens.md); this page is about the PSB and the process SD.

## What the PSB holds

The Process Security Block is the per-process kernel structure with:

| Field | Meaning |
|---|---|
| `pip_type` | The process's PIP type: 0 for none, 512 for Protected. |
| `pip_trust` | The PIP trust level within the type: 8192 for `PeiosTcb`. |
| Mitigation flags | The bitfield of enabled mitigations (WXP, LSV, TLP, CFIF, CFIB, PIE, SML, NO_CHILD, etc.). |
| `process_guid` | The process's identity for its whole life, unlike its PID, which is reused. Events carry it. |

The process SD sits beside the PSB and is read separately (below). Internal fields (refcounts, lock state) are not exposed.

## Reading a PSB

`/proc/<pid>/psb` is one line of text:

```
pip_type=512 pip_trust=8192 mitigations=0x105 process_guid=3f2c9a1e-6b0d-4c8e-9a41-2d7e5f10b6c3
```

`mitigations` is the bitfield below, in hexadecimal.

- **Your own** is always readable.
- **Another process's** needs `PROCESS_QUERY_LIMITED` on its process SD — the right `ps` needs for a process's name and CPU use, which the default process SD gives Everyone.

PIP dominance is **not** required here, and this is the only inspection surface where it is not. That a process is protected, and at what trust, is not a secret the kernel keeps: every other refusal already reveals it. Reading it is what lets a tool such as Task Manager say *why* the rest of a process is closed — "protected: signed Peios TCB" rather than an unexplained "access denied".

## Inspecting another process

Everything beyond the PSB needs two things:

- **The right on the target's process SD**: `PROCESS_QUERY_INFORMATION` for its token and detailed `/proc` entries, `READ_CONTROL` for its process SD.
- **PIP dominance** over the target (the caller's PIP must dominate the target's, per the [two-check rule](/peios/security-fundamentals/process-integrity-protection/the-two-check-rule.md)).

Both requirements apply. A principal granted `PROCESS_QUERY_INFORMATION` cannot inspect a higher-PIP process even with the SD grant — the PIP check is independent.

Once both checks pass, open the target's primary token (via `/proc/<pid>/token` or `kacs_open_process_token`), query through `KACS_IOC_QUERY`, and read the process SD via `kacs_get_sd`. Reading the token needs `TOKEN_QUERY` on the token's own descriptor as well, which by default its user, its creator, SYSTEM and Administrators have.

A SeDebugPrivilege-holder can bypass the process SD check (`PROCESS_QUERY_INFORMATION` becomes trivially granted) but does not bypass PIP — a privileged debugger still cannot inspect TCB processes.

In practice, only peinit and processes signed at the same PIP level as the target can inspect TCB processes. Ordinary administrators with `SeDebugPrivilege` are blocked at the PIP layer, and see such a process's PSB and nothing else.

## Reading the process SD

A process's SD is read via `kacs_get_sd` with the appropriate process-targeted flags. The call:

```
kacs_get_sd(target_pidfd, security_information, buf, buf_len, flags)
```

Returns the SD components requested (per the security_information mask). Self-targeted queries are always allowed; cross-process queries require `READ_CONTROL` on the target's process SD plus PIP dominance.

`READ_CONTROL` is one of the standard rights every SD-bearing object exposes; it appears in the DACL like any other right. By default the owner of an object has it implicitly (see [Ownership](/peios/security-fundamentals/security-descriptors/ownership.md)).

For inspecting the SACL specifically — to see audit ACEs, mandatory labels, PIP trust labels, scoped policy references — `ACCESS_SYSTEM_SECURITY` is the right needed, not `READ_CONTROL`. That right is gated by `SeSecurityPrivilege`. So:

- DACL: needs `READ_CONTROL` (typically held by the owner).
- SACL: needs `ACCESS_SYSTEM_SECURITY` (typically held only by administrators with `SeSecurityPrivilege`).
- Owner / primary group SIDs: need `READ_CONTROL`.

A non-privileged caller can read a process's DACL (if granted) but not its SACL. For administrative inspection of the full SD including SACL, `SeSecurityPrivilege` is the lever.

## Reading mitigation flags

The mitigation bitfield is the `mitigations` value in `/proc/<pid>/psb`, under the rule in [Reading a PSB](#reading-a-psb). It is the same one the kernel uses internally:

| Flag | Bit | Meaning |
|---|---|---|
| WXP | 0x001 | Write-XOR-Execute enabled |
| TLP | 0x002 | Trusted Library Paths enabled |
| LSV | 0x004 | Library Signature Verification enabled |
| CFI (legacy) | 0x008 | CFIF + CFIB combined alias |
| UI_ACCESS | 0x010 | Reserved |
| NO_CHILD | 0x020 | Forbid fork/clone-new-process |
| CFIF | 0x040 | Forward CFI |
| CFIB | 0x080 | Backward CFI |
| PIE | 0x100 | PIE-only exec |
| SML | 0x200 | Speculation mitigation lock |

A process's mitigation flags tell you what hardening it has enabled. Comparing this against the process's binary lets you reason about which exploitation paths are closed — a TCB-signed binary running with WXP, LSV, TLP, CFIF, CFIB, and PIE is comprehensively hardened; one with only PIE has minimal hardening.

The flags are one-way — once set, they cannot be cleared. So the snapshot you read now is also the snapshot for the rest of the process's life (except that new flags may be set). Re-reading produces the same or stricter result.

## Cross-referencing process and token

A common diagnostic pattern: given a process, know which session it is in, which user it acts as, what its PIP is, and what mitigations are active.

The sequence:

1. **Open the process's primary token** via `/proc/<pid>/token` or `kacs_open_process_token`.
2. **Query `TokenUser`** for the user SID.
3. **Query `TokenStatistics`** for `auth_id`. Cross-reference with `/sys/kernel/security/kacs/sessions` for session details.
4. **Read `/proc/<pid>/psb`** for PIP and mitigations.
5. **Read the process SD** for who can act on this process.

Each step requires the appropriate access, and each fails closed if the caller lacks authority over the target. For self-targeted queries everything succeeds.

For a debugger or monitoring tool, this is the standard "tell me everything about this process" workflow. The pieces are independent (each query is its own ioctl), but they combine to give a complete picture.

## Live vs static state

A process's state changes over time. The current state is what the inspection surfaces return; previous state is not retrievable.

The **changing** parts of a process's state:

- **Threads come and go.** A process's set of threads is dynamic. Re-running per-thread inspection picks up the current set.
- **The thread's effective token may change** (impersonation install/revert). Re-querying gets the current value.
- **The primary token's adjustable fields** (privileges enabled state, groups enabled state, default DACL) can change. The `modified_id` counter on the token tracks how many changes have happened.
- **The process SD can be modified** by anyone with `WRITE_DAC` on the process. New ACEs appear; old ACEs disappear.

The **immutable** parts of a process's state (once set):

- **PIP fields.** Set at exec; never change for the lifetime of the process.
- **Mitigation flags** (including `NO_CHILD`). One-way; can be tightened but never relaxed.
- **Token identity fields** (user_sid, groups[].sid, restricted_sids, logon_sid). Set at token creation; the token can be replaced but never have its identity adjusted.

Knowing which fields are immutable helps with monitoring. A monitor that has already read the PIP fields once does not need to re-read them; they will not change. A monitor watching for privilege state changes needs to poll or subscribe to events — they can change at any time.

## What inspection cannot tell you

A few clarifications:

- **It cannot tell you what access a process has.** Inspection gives you the inputs to AccessCheck (the token, the object's SD); it does not compute the access. To know what a process can do to a specific object, call AccessCheck.
- **It cannot give you a tamper-evident snapshot.** The kernel may modify state between two reads; there is no "atomic snapshot" surface. Tools that need consistency should use the `modified_id` counter to detect changes.
- **It cannot reveal the contents of the target process's memory.** Inspecting the PSB and the process SD shows you the kernel's metadata about the process. To read the process's *memory* you need `PROCESS_VM_READ` on the process SD plus PIP dominance plus a ptrace-like syscall. That is a different topic.
- **It cannot show you removed history.** A token whose privileges were once enabled but have since been removed shows the current state, not the history. The `used` bit on a privilege is a sticky record of "this privilege has been exercised at some point", but specific timestamps are an audit-log concern, not an inspection concern.

The inspection surfaces are for the present moment. For historical questions, the audit log is the right source.

## Where to go next

For inspecting the live flow of audit events rather than current state, read [The event stream](/peios/advanced-peios/security-diagnostics/the-event-stream.md).

For the identity half of a process's state — obtaining and querying token fds — read [Inspecting tokens](/peios/using-peios/inspecting/tokens.md).

For the dominance rule that gates every cross-process inspection, read [The two-check rule](/peios/security-fundamentals/process-integrity-protection/the-two-check-rule.md).

---

# The event stream

_Peios / Peios technical reference / Security diagnostics reference_

> revstrm is a diagnostic probe that attaches directly to the KMES ring buffers and prints every audit event it drains. Options, output format, and caveats.

The other pages in this topic inspect *state* — what a token holds, which session a thread belongs to, what a process is authorised for. This page inspects *flow*: the live stream of audit events the kernel produces as access checks fire. State tells you what is true right now; the event stream tells you what is happening, event by event, as it happens.

The tool for reading that stream raw is **`revstrm`**. It attaches directly to the [KMES](/peios/security-fundamentals/auditing/events-and-transport.md) per-CPU ring buffers and prints every event it drains. It is deliberately oblivious to the rest of the audit pipeline: it knows nothing about eventd, applies no persistence, and makes no attempt at reliable delivery. It is a debugging probe for the transport layer itself.

## What revstrm is (and is not)

`revstrm` is not the normal way to look at audit events. The normal way is to query **eventd**, the userspace audit daemon that subscribes to KMES, persists events, and serves them to consumers. eventd is the durable, deployment-facing surface. `revstrm` sits *underneath* it, tapping the same kernel stream directly.

Two things make it worth having:

- **It is a diagnostic.** When events are not reaching eventd, or you suspect the kernel is not emitting what you expect, `revstrm` lets you see the raw KMES stream with no daemon in the path. It is the "is the wire live?" probe for the audit pipeline.
- **It is the reference consumer.** `revstrm` exercises the full KMES consumption protocol of [PSPK §2.4](/peios/advanced-peios/pspk/kmes-event-stream/consuming.md) — one drain thread per CPU ring, the futex notification wait, generation-change re-attach, and lapping/gap detection. It is the worked example that proves the consumption path before a production consumer like eventd relies on it.

Because it taps KMES directly rather than through eventd, `revstrm` is subject to the raw transport's limits: it is its *own* KMES subscriber, it can fall behind, and when it does its ring laps and it loses events — visibly (see [Lapping and gaps](#lapping-and-gaps)). It is not a lossless audit sink and must never be relied on as one. For durable audit, that is eventd's job.

## Access requirement

Attaching to the KMES ring buffers requires **`SeSecurityPrivilege`**. Without it, the attach fails at the first ring with a clear hint rather than a silent empty stream:

```
revstrm: cannot attach to the KMES ring buffers (SeSecurityPrivilege required)
```

This is the same privilege class that gates the rest of the security-sensitive surfaces: reading the audit stream reveals every access decision on the machine, so it is not something a low-privileged process can do. An administrator (whose token holds `SeSecurityPrivilege`) can run it; an ordinary user cannot.

## Synopsis

```
revstrm [OPTION]...
```

With no options, `revstrm` **follows** the live stream: it attaches to every per-CPU ring and prints each event as it arrives, oldest surviving event first, until you interrupt it (Ctrl-C) or the output pipe closes. There is no target to name and no subscription to configure — it drains whatever the kernel is currently writing to KMES.

## Options

The option surface is small and entirely about *what to show* and *how to show it* — there is nothing to configure about the subscription itself.

| Option | Description |
|---|---|
| `-t, --type GLOB` | Only show events whose event-type string matches `GLOB`. Repeatable; an event is shown if it matches **any** supplied pattern (OR). Uses shell-glob syntax (e.g. `--type 'access-*'`). |
| `-o, --origin CLASS` | Only show events from origin `CLASS`, one of `userspace`, `kmes`, `kacs`, or `lcs` (case-insensitive; `user`/`usr` alias `userspace`). Repeatable; an event is shown if its origin matches any supplied class. |
| `-p, --pretty` | Expand the msgpack payload across multiple indented lines instead of the compact one-line form. |
| `-s, --snapshot` | Drain the events currently buffered across all rings and exit, instead of following the live stream. Single-threaded, in CPU order; there is nothing to wait for. |
| `--help` | Print usage and exit. |
| `--version` | Print version and exit. |

Long options may be abbreviated as long as the prefix is unambiguous (e.g. `--sn` for `--snapshot`).

The `--type` and `--origin` filters are applied by `revstrm` after draining, purely to reduce what is printed — they are *display* filters, not a kernel-side subscription. The kernel still writes every event into the ring, and `revstrm` still drains every event; filtered-out events are simply not printed. This matters for lapping: filtering does not reduce the drain load, so it does not make `revstrm` less likely to fall behind.

## Output format

Each event prints as a single header line (followed, under `--pretty`, by an indented payload block):

```
TIME  cpuN  #SEQUENCE  ORIGIN  event.type  payload
```

| Field | Meaning |
|---|---|
| `TIME` | UTC time-of-day with microsecond precision, `HH:MM:SS.uuuuuu`. The calendar date is dropped — a live tail cares about wall-clock time of day, not the day. |
| `cpuN` | The per-CPU ring the event was drained from. Events are sharded per CPU all the way down; `revstrm` prints the CPU rather than merging into a single ordered stream. |
| `#SEQUENCE` | The event's per-ring sequence number. Gaps in the sequence on a given CPU indicate lost events. |
| `ORIGIN` | The origin class: one of `USR`, `KMES`, `KACS`, `LCS` (or the catalogue's name for the class, e.g. `NTFE`, else `cN`). |
| `event.type` | The event-type string (e.g. `kacs.audit.access.checked`, `kacs.session.destroyed`). |
| `payload` | The msgpack payload, rendered as described below. |

### Payload rendering

By default the payload is rendered **compactly** on the header line, truncated if long. `revstrm` decodes the msgpack and formats each value **by the type the event catalogue declares for it**.

Payloads are nested maps, so `revstrm` follows the nesting and looks up each value by its flattened dotted path: the `sid` inside `subject` → `token` is `subject.token.sid`. It reads the catalogue from `/usr/share/evman` (or the directory named by `EVMAN_DIR`), the same one [`evman`](/peios/using-peios/peiosutils/event-tools/evman.md) shows. The nesting itself is kept in the output — compact form shows nested braces, `--pretty` shows indented blocks. The declared type picks the rendering:

| Declared type | Rendered as |
|---|---|
| `bin.sid`, `bin.sid[]` | Canonical string SIDs (`S-1-5-18`), one or an array of them. |
| `bin.guid` | The canonical GUID string. |
| `uint.time` | A UTC time with nanosecond precision (`2026-10-06T12:34:56.123456789Z`). |
| `uint.duration`, `uint.bytes` | The number with a scaled reading (`1500000ns (1.5ms)`, `8192 (8KiB)`). |
| `uint.mask` | `\|`-joined access-right names, with any unrecognised bits shown as a hex remainder so nothing is hidden. The names come from the table for the record's `object.kind`; for a kind `revstrm` has no table for, only the standard and generic rights are named and the object-specific bits are left as hex. |
| `uint.enum`, `uint.flags` | The name or names from the field's `values:` line (`UDP (17)`, `PROT_READ\|PROT_WRITE`). A value the catalogue does not list is shown as a number. |
| `uint.luid`, `uint.integrity` | Hexadecimal; the integrity level by name (`high (0x3000)`). |
| `int.errno` | The negative error number with its message (`-13 (Permission denied)`). |

A value of the wrong shape for its declared type is shown as it is on the wire rather than guessed at. The header's origin class is named from the catalogue's `emitter.class` enumeration when it lists a class `revstrm` does not know.

If no catalogue is installed, or a path is not in it (a third-party or older event), `revstrm` falls back to field-name conventions: keys ending in `sid` or `sids` render as string SIDs, and keys ending in `access` decode as file access rights.

A payload that will not decode as msgpack falls back to a hex preview rather than being dropped. With `--pretty`, the same payload is expanded into an aligned, indented block — one key per line, nested maps and arrays expanded beneath their key. Use `--pretty` when you are reading individual events closely; leave it off when tailing a busy stream.

The event schemas themselves — `kacs.audit.access.checked`, `kacs.audit.handle.used`, `kacs.audit.privilege.used`, `kacs.session.destroyed` and the rest — are documented in [Events and transport](/peios/security-fundamentals/auditing/events-and-transport.md), and `evman <event-type>` describes any of them. `revstrm` does not interpret them beyond formatting values by their declared types; it is a stream printer, not an event analyser.

### Example

A short follow session might look like:

```
14:22:07.481923  cpu0  #10432    KACS  kacs.audit.access.checked  {subject: {token: {sid: S-1-5-21-…, integrity: 12288, …}, …}, access: {requested: 129, granted: 129}, outcome: {success: true}, …}
14:22:07.492010  cpu3  #8871     KACS  kacs.audit.privilege.used  {privilege: {name: "SeBackupPrivilege", contributed: 1, surviving: 1}, outcome: {success: true}, …}
14:22:08.003114  cpu0  #10433    KACS  kacs.session.destroyed  {object: {session: {id: 4051, user: {sid: S-1-5-21-…}, logon-type: "interactive", …}}}
```

## Lapping and gaps

`revstrm` is one KMES subscriber among possibly several, with its own per-CPU rings. If it cannot drain a ring fast enough — a burst of audit events, a slow terminal, a `--pretty` render on a busy stream — that ring **laps**: the kernel overwrites the oldest un-drained events with new ones. This is the flow-control behaviour of KMES, not a bug in `revstrm`.

When a ring laps, `revstrm` does not hide it. It prints a visible marker naming the CPU and the count lost:

```
--- cpu2: lost 37 event(s) (ring lapped) ---
```

A dropped event a debugger cannot see is the worst possible outcome, so lapping is always surfaced. If you see these markers, `revstrm` is not keeping up — narrowing the output with `--type`/`--origin` won't help (the drain still happens), but redirecting to a file, dropping `--pretty`, or reducing the event rate will. Sustained lapping is also a signal in its own right: the kernel is producing events faster than a single un-buffered consumer can drain them.

Gaps are also visible directly in the `#SEQUENCE` column: a jump in the per-CPU sequence number is a run of events that this subscriber never saw.

## Exit behaviour

- In **follow** mode (the default), `revstrm` runs until interrupted or until stdout closes. A downstream `head` (or any reader) closing the pipe shuts `revstrm` down cleanly — it treats the broken pipe as "nothing left to print to" and exits, rather than being killed by `SIGPIPE`. If every per-CPU ring hits a fatal error, all drain threads exit and the process ends.
- In **snapshot** mode (`--snapshot`), it drains what is currently buffered across all rings and exits immediately.

## When to use it

Use `revstrm` when you are debugging the audit *pipeline* — "are events being emitted at all?", "is eventd's problem upstream or downstream of KMES?", "what exactly is the kernel putting on the wire?". Use eventd (or whatever consumes it in your deployment) for everything else: durable audit, historical queries, and any consumption that must not lose events.

## See also

- [Inspecting security state](/peios/using-peios/inspecting/overview.md) — the state-inspection counterparts: tokens, sessions, and processes.
- [Events and transport](/peios/security-fundamentals/auditing/events-and-transport.md) — the schemas of the events revstrm prints.
- [Auditing](/peios/security-fundamentals/auditing/overview.md) — where the events come from.
