Peios Learn
Products
PePeios pkpekit PvProvium UDUniversal Directory TrTrail Ckclackatoo PrProject WiWispist
Using Peios Security Basics Technical Documentation Source
Using Peios Security Basics Technical Documentation Source
Peios

Manage local accounts and groups

Single-page view · as markdown

Manage local accounts and groups

Peios / Using Peios / Manage local accounts and groups

Local accounts and groups are held by lpsd, the local principal source. Use Principals Manager on the desktop or the lps command in a terminal to manage them. The guides distinguish changes you can make to your own account from administrator operations.

Choose a task #

  • Create an account: Creating accounts covers credentials, membership and the different first-account provisioning routes. The desktop equivalent is Making a user.
  • Change an account or its sign-in policy: Changing a user or the lps reference. A password being set does not by itself require that password at sign-in.
  • Create a group or change membership: Groups in Principals Manager or Grouping people together.
  • Inspect or change privileges and integrity policy: Assigning privileges. This is the machine's local policy, separate from the account's identity. A policy change affects newly minted sign-in tokens, not existing tokens.
  • Change your own password, SSH keys or display name: Sign in links the self-service procedures.

Keep a usable administrator route #

Before changing an administrator's credentials, membership or sign-in access, keep a working administrator session and verify the replacement route. The last-administrator guard checks account state; it does not prove that you can actually sign in through a usable route. SSH configuration changes can end every SSH session, so follow the separate-access precautions there.

Read Disabling rather than deleting before removing an account. Recreating a name does not restore the old SID or the access granted to it.

Understand the model when needed #

Security Fundamentals explains authorities and principal sources, local identity storage, name resolution and privileges. Tool authors and administrators investigating a refused request can consult the local principal interfaces in the technical reference.

Creating accounts

Peios / Using Peios / Manage local accounts and groups

Creating an account #

Run lps add with no arguments and it asks for what it needs:

$ lps add
Name: alice
Full name [optional]: Alice Chen
Additional groups [comma-separated, optional]: Administrators
Primary group [the daemon's default]:
Home directory [the daemon's default]:
Shell [the daemon's default]:
Password for alice:
Again:

  name           alice
  full name      Alice Chen
  primary group  (the daemon's default)
  home           (the daemon's default)
  shell          (the daemon's default)
  groups         Administrators

Create this principal? [Y/n]:
created alice with RID 1002

An empty answer takes the default, and nothing is sent until you confirm. Or say it all on one line:

$ lps add alice --group Administrators

Anything you supply is not asked for, so either style works and they mix freely.

Groups are named however you like — Administrators, a local group's name, or a literal SID such as S-1-5-32-544. lpsd resolves it.

You need to be an administrator to do this. See the lps command for the full surface.

What a new account gets #

RIDThe next one. Never chosen, never reused.
uidThe RID, plus this machine's base — so RID 1000 signs in as 1001000.
Primary groupAuthenticated Users, unless you say otherwise.
Home/home/<name>
Shell/bin/sh

No per-user group is created. That Linux convention exists so a file's group ownership means something, and under KACS it means nothing — access is decided by the token, not by the mode bits. A group per user would be ceremony with a RID attached.

The home directory is not created here. lps records the path and nothing more. The directory appears at the principal's first sign-in, made by the authority when it grants the logon — and only when the account has a home recorded, which is why a service principal never grows one.

The shipped authd service explicitly retains SeRestorePrivilege so it can assign the new directory's owner and group to that principal. Removing it from RequiredPrivileges can leave a newly created home accessible only to SYSTEM and Administrators, preventing an ordinary user's session from starting there. Authd reports this failure and leaves existing directories alone; an administrator must repair their descriptors deliberately.

It is created owned by the principal, with a protected DACL: full control for its owner, for SYSTEM and for Administrators, and nothing for anybody else. Protected matters — without it the root's inheritable "Everyone may read" ACE would apply and every account could read every other account's files.

A principal whose home could not be created still signs in, starting in /, and login says so. A directory is not worth failing a logon over.

An existing directory is left exactly as it is, and one owned by somebody else is reported rather than taken over. So pointing a new account's home at another principal's directory does not hand it over — it produces a warning in the log and an account that starts somewhere it does not own.

Accounts with no password #

lps add <name> --no-password creates a principal whose credential policy is none. It signs in without being asked for anything where the client and authority permit credential-free sign-in.

The sign-in is otherwise completely ordinary. lpsd still vouches for the principal, authd still mints the token and still applies this machine's privilege and integrity policy to it, and the session that results is indistinguishable from one reached by typing a password. Nothing is bypassed; there is simply nothing to collect.

It is a property of the account. Nothing ties it to a particular terminal: any client that permits credential-free sign-in can use it, subject to the account's logon-type restrictions and the authority's policy for that client. SSH does not permit it; SSH requires an actual password or an enrolled key and a policy that allows that credential. A passwordless account enables console autologon, but do not assume that its use is limited to that console.

An empty password is not the same thing and is refused. See the lps command.

Make the first account an administrator #

An ordinary account is usable on a stock image. The descriptor the system tree ships with grants Everyone read and execute — and on Peios execute is also traverse — so any principal can list directories, read the system tree and run what is in it. What it cannot do is write there. A non-administrator writes only where something grants it: its own home directory, which is created at first sign-in with a protected DACL admitting the owner, LocalSystem and BUILTIN\Administrators and nobody else, and /tmp, where anyone may create files and only their owner may remove them. See Installing to disk for the root descriptor itself.

What an ordinary account cannot do is administer the machine. lps refuses it, so it cannot create the next account; it cannot mount a filesystem, install a package or change a service definition either. The first account is therefore an administrator because nothing else on the machine can make one, and it stays one because lps will not leave the machine without an enabled administrator (see The last-administrator guard below). The accounts after it need Administrators only if they are going to administer.

Where the first account comes from #

A machine with no store provisions one at first boot, and it is empty — lpsd generates the domain, because a source with no domain can answer nothing, but it does not invent accounts.

So something has to create the first one, and there is a chicken-and-egg problem: lps talks to lpsd, so lpsd must already be running, which means this cannot be done by an early-boot script. Autorun scripts run before any service has started.

The provisioning path depends on the edition. An installed edition can enable first-boot setup; a development image can instead ship a oneshot service, ordered after lpsd, that runs lps to create a known account. An image with no first-account provisioning path needs an already-authorised way to create the first account.

On an installed edition with first-boot setup #

Complete first-boot setup creates the first administrator after installation. Use the console form, or the browser form when the image includes oobe-gxwi, then verify sign-in with the new account. Use lps add as that administrator for further accounts.

This path is enabled only when the edition includes dev.peios.oobe and opts into its oobe-service seed through install_autoapply. The oobed service runs as SYSTEM to create the account; the user-facing form does not need an existing administrator sign-in. Do not assume every custom image includes or enables it. The development-account seed below is a separate provisioning choice.

On a development image #

Images built for development ship two things: a script that creates a known account, and a registry seed defining the oneshot service that runs it. If your image has them, it boots with an account already present, and lps list will show it.

On a live image that account has no credential at all, and it is an administrator. The script creates it with lps add peios --group Administrators --no-password, so anyone who reaches a prompt that permits credential-free sign-in on that machine can become it — and the console signs in as it automatically, without asking anything.

That is deliberate rather than an oversight. A live ISO is an unauthenticated medium: anyone holding it can boot it and read everything on it, so a password would be a formality rather than a boundary, and one printed in the image at that. What it is not is a posture to carry anywhere else.

Give it a password the moment the machine becomes something you care about, then say it signs in with that password. The two together turn it into an ordinary account and stop the console signing in on its own:

$ lps password peios
New password for peios:
Again:
set the password for peios
$ lps policy peios password
credential policy updated

The password alone changes nothing: until lps policy says otherwise, the account still signs in with nothing. Set the password first, since a policy of password is refused for the last administrator while they have none.

Better still, build an image without the development-account seed, with first-boot setup or another deliberate account-provisioning path. Omitting the development seed alone does not create an account; see On an image with no account provisioning below.

On an image with no account provisioning #

On a fresh image with no accounts, no enabled first-boot setup and no account-provisioning seed, lpsd starts, provisions an empty store, and logs that no principals exist and no logon can succeed until one is created. The login prompt will appear and nothing will satisfy it.

That is the correct behaviour rather than a fault, but it does mean you need a way in. Create the first account from a console session that is already SYSTEM — lps accepts LocalSystem as well as administrators, precisely for this case.

Disabling rather than deleting #

$ lps disable guest
disabled guest

A disabled account keeps its RID, its SID, and its group memberships. It cannot start a new sign-in. Re-enable it with lps enable.

Disabling an account, changing its password or removing a key does not revoke already issued tokens or end its existing sessions. Use Task Manager's sign-out action when existing sessions must end. Signing out ends processes and can lose unsaved work; session lifecycle explains its permissions and limits, including token references held outside those processes.

Prefer this to lps remove. A removed principal's SID keeps appearing in the security descriptors of everything they owned, and because RIDs are never reused nothing will ever hold that SID again — the files become owned by a principal that no longer resolves.

The last-administrator guard #

This is a guard on the local account store, not a guarantee that a usable sign-in path is reachable.

lps refuses to remove the last enabled administrator, to disable them, or to take Administrators away from them. It refuses as firmly to leave them no way to sign in: no kind of sign-in at the machine, remotely or over the network (lps logon-types), or nothing to sign in with (lps policy and lps key remove).

$ lps disable jack
lps: jack is the only principal who can administer this machine; disabling them would leave no way back in

The check counts only enabled administrators, so a disabled standby account does not license removing the working one.

The guard does not check that a console, remote service or network route is available. After changing credentials or sign-in policy, inspect the account with lps show <name> and verify a fresh sign-in through the intended path before closing your working administrator session. A stored credential and a permitted logon type are not evidence that the whole path works.

This guard exists because there is currently no offline repair. lps reaches the store only through lpsd, and lpsd only authenticates — so a machine with no enabled administrator has no way back short of editing its disk from another system.

Grouping people together #

For anything beyond the built-in groups, create one:

$ lps group create developers
created the group developers with RID 1001
$ lps group add alice developers
added alice to developers

A local group is a real object with its own SID and gid, so it can be named in a security descriptor and it shows up in getgroups. Deleting one is refused while anybody is still in it.

Changing your own password #

Run passwd. It asks for your current password and then the new one:

$ passwd
Changing the password for alice
Current password:
New password:
Retype new password:
password changed

It goes over PGSS Logon rather than through lps, so it works the same way whichever source holds your account — a domain principal changes their password exactly as a local one does. lps password is the other operation: an administrator setting somebody else's.

An account created with --no-password has no current password to prove, so it cannot give itself one. An administrator sets one with lps password, and lps policy <name> password to have it used, or with Principals Manager's Set password, which does both.

Where to go next #

For the full command surface — every flag, the group and claim subcommands, and the exit statuses — read The lps command.

For what the store holds and why RIDs are never reused, read The local store.

The lps command

Peios / Using Peios / Manage local accounts and groups

lps administers the local principal store — this machine's own accounts and groups, held by lpsd.

lps subcommand [arguments]

It is a client and nothing more. It holds no state, opens no store, and has no privilege of its own: every command is a request over /run/lpsd/admin.sock that lpsd decides whether to honour. lpsd must be running.

You must be a member of BUILTIN\Administrators — enabled, not deny-only — or be LocalSystem.

Naming a group #

Anywhere lps takes a group, you may write it three ways:

lps group add jack Administrators              # a well-known name
lps group add jack BUILTIN\\Administrators      # the same, qualified
lps group add jack developers                  # a local group
lps group add jack S-1-5-32-544                # a literal SID

Names are matched case-insensitively, and lpsd resolves them, not lps. The tool carries no copy of the well-known table and does not know this machine's domain — a second copy of the machine's identity scheme would be free to disagree with the first.

A well-known name always wins over a local group of the same name, which is why you cannot create a local group called Administrators.

Inspecting #

lps list #

Every principal, with RID, uid, state, and how many groups each is in.

$ lps list
NAME     RID       UID  STATE     GROUPS
jack    1000   1001000  enabled   1
guest   1001   1001001  disabled  0

A - in the UID column means nothing numbers that principal — authd has assigned this machine no identifier range, and they will sign in as nobody. See the local store.

lps show <name> #

One principal in full.

$ lps show jack
name           jack
display name   Jack Palfrey
rid            1000
sid            S-1-5-21-2847362817-1094533892-3310298447-1000
uid            1001000
state          enabled
primary group  Authenticated Users [S-1-5-11]
home           /home/jack
shell          /bin/sh
logon types    default (interactive, remote-interactive, network, network-cleartext, batch, new-credentials)
credential     password
groups         Administrators [S-1-5-32-544]
               developers [S-1-5-21-2847362817-1094533892-3310298447-1001] (gid 1001001)
claims         Department (string) = "Engineering"

logon types are the kinds of sign-in the principal may be used for (see lps logon-types below); default means none were chosen, and the machine's default applies. credential is what it signs in with: password, key (an SSH key), either, none (nothing is asked for), or denied.

Groups show a name where this machine knows one, and always show the SID. The two are kept side by side deliberately: the name is what you recognise, the SID is what a security descriptor actually holds, and the moment they disagree is exactly when you need to see both.

Only local groups show a gid. lpsd numbers the groups it owns and nothing else — Administrators and Authenticated Users are numbered by authd, from a table below every source's range, so lpsd genuinely does not know their gid and does not guess. To see the numbers a running session actually holds, read Groups: in /proc/self/status.

lps domain #

This machine's domain SID — the namespace every local principal and local group is numbered under.

$ lps domain
S-1-5-21-2847362817-1094533892-3310298447

Generated at first boot and unique to this machine.

Creating and removing principals #

lps add [name] [options] #

Creates a principal. Run with no arguments, it asks for what it needs:

$ lps add
Name: alice
Full name [optional]: Alice Chen
Additional groups [comma-separated, optional]: Administrators, developers
Primary group [the daemon's default]:
Home directory [the daemon's default]:
Shell [the daemon's default]:
Password for alice:
Again:

  name           alice
  full name      Alice Chen
  primary group  (the daemon's default)
  home           (the daemon's default)
  shell          (the daemon's default)
  groups         Administrators, developers

Create this principal? [Y/n]:
created alice with RID 1002

Any option you supply is not asked for, so scripted invocations keep working unchanged:

OptionEffect
--group <group>A membership. Repeatable.
--primary-group <group>The group that becomes the POSIX gid.
--home <path>Home directory. Must be absolute.
--shell <path>Login shell. Must be absolute.
--display-name <text>A human's name for a human to read.
--disabledCreate it unable to sign in.
--no-passwordCreate a principal that authenticates with no credential at all. See below.
--serviceCreate a principal that exists to run a service, and can do nothing else. See below.
--no-promptFail rather than ask for anything missing.

Nothing is sent until you confirm, so answering n creates nothing. Everything is sent in one request, so a principal is created whole or not at all: if lpsd refuses the shell, say, no principal is made.

lps never prompts when standard input is not a terminal. A prompt down a pipe would consume the next line of whatever is driving the tool. See From a script below.

The RID is allocated by lpsd and reported back. You do not choose it, and the uid follows from it.

--service #

Creates a principal that may be used only to run a service, and for nothing else. It cannot sign in at a console, over the network, or as a batch job — a sign-on of any other kind is refused even with the correct credential.

Most services do not need one. A service already gets an identity without an account: it runs as LocalService and carries a per-service SID derived from its name, which you can name in a security descriptor like any other principal. Reach for --service when a service needs something a bare SID cannot be — membership of a group, a home directory, or a profile.

A service principal has no credential and cannot be given one. The authority mints its token on the service manager's attestation instead, and only the service manager may ask: nothing else on the system can obtain a token for it, with or without a password.

The restriction is a property of the account rather than of this machine, so it travels with the principal. It is also the reason the flag exists at all: without it, an ordinary account could be named as a service's identity, and the machine would then be able to obtain a token for that account with no credential.

Every principal created without this flag permits every kind of sign-on a person uses and never a service logon, so an existing account cannot become a service identity by accident.

--no-password #

Creates a principal with credential policy none, which signs in without being asked for anything where the client and authority permit credential-free sign-in. No password is collected at creation, including in the interactive flow — an operator who has said the account needs no credential is not then asked to invent one.

$ lps add kiosk --group Administrators --no-password --no-prompt
created kiosk with RID 1003

This is a property of the account, not of a terminal. Nothing scopes it to one console: any client that permits credential-free sign-in can use it, subject to logon-type and originator policy. SSH requires an actual password or an enrolled key and a policy allowing that credential; it refuses none. Credential-free sign-in is the right posture for a live image, where the medium is unauthenticated anyway and anyone holding it can read everything on it. It is the wrong posture for almost anything else.

An empty password is not a way to spell this, and lps refuses one:

$ lps add alice
...
Password for alice:
Again:
lps: an empty password is not a password: pass --no-password to create a
principal that authenticates without one

The two produce accounts that behave differently — an empty password is still collected, still prompted for, and still has to be answered — so having both spellings would mean two things that look identical at creation and diverge at every sign-in. lpsd refuses an empty password too, so the rule holds however the request arrives.

lps remove <name> #

Deletes a principal.

The RID is not reclaimed. Files the principal owned keep naming a SID that now resolves to nobody, and nothing will ever hold that SID again. lps disable is usually what you wanted — see creating accounts.

lps rename <name> <new-name> #

Renames a principal.

$ lps rename erin erin.k
renamed erin to erin.k

Its SID stays, so every file, permission and group membership that names it still does, and it signs in by the new name from now on. Its home directory stays where it was. lps set --home changes the recorded path only; it does not move the directory or its files.

A name held by another principal or by a local group is refused. Changing only the case, erin to Erin, is allowed.

Enabling and disabling #

lps enable <name> / lps disable <name> #

A disabled principal keeps everything except the ability to start a new sign-in. It does not revoke already issued tokens or end existing sessions. Use Task Manager’s sign-out action when existing sessions must end. Signing out can lose unsaved work; session lifecycle explains its permissions and limits, including token references held outside the session's processes.

Both are refused if they would leave the machine with no enabled administrator.

lps logon-types <name> <type>... #

Sets the kinds of sign-in a principal may be used for. Any other kind is refused, even with the right credential.

$ lps logon-types backup network batch
set the logon types for backup
$ lps logon-types backup default
set the logon types for backup
TypeSigning in
interactiveat this machine: its console or desktop
remote-interactiveto a remote desktop or over SSH, including commands without a PTY and SFTP
networkto a network resource, such as a file share
network-cleartextover the network, with the password sent to this machine
batchas a scheduled job
new-credentialsas a second identity for outgoing connections
serviceas a service, started by the service manager

The SSH mapping here follows the current PGSS SSH implementation contract, including its release-qualification and compatible-build limits.

default returns the principal to the machine's default: every kind a person uses, and never service. There is no setting for "no sign-in at all"; lps disable is that.

The last enabled administrator must keep interactive, remote-interactive or network. This is an account-store guard; it does not check whether the corresponding console, service or network path is reachable.

Passwords #

lps password <name> #

Sets a principal's password, prompting twice.

$ lps password jack
New password for jack:
Again:
set the password for jack

This is an administrator resetting somebody else's password. A principal changes their own with passwd, which goes over PGSS Logon instead, so that it works identically whichever source holds the account.

An empty password is refused here for the same reason it is refused at creation. A password doesn't change what a principal signs in with: one created with --no-password keeps signing in with nothing until lps policy <name> password says otherwise (see credentials below). There is no command that deletes a password; lps policy decides whether it is used.

From a script, lps reads a single line from standard input when it is not attached to a terminal, and does not ask for confirmation:

printf '%s\n' "$password" | lps add alice --group Administrators --no-prompt

Credentials and SSH keys #

A principal's credential policy says what it signs in with. Its SSH keys are the public keys it may sign in with over SSH. The two are kept apart: adding a key, or setting a password, never changes the policy, and changing the policy never adds or removes either. Enrolling a key and letting it be used are two decisions.

lps policy <name> <policy> #

$ lps policy erin either
credential policy updated
PolicySigns in with
passwordtheir password
keyone of their SSH keys
eithertheir password or one of their SSH keys
nonenothing, where credential-free sign-in is permitted; SSH refuses this policy
deniednothing: every sign-in is refused, whatever is offered

none is what lps add --no-password gives, and carries the same warning. denied stops a principal signing in and keeps everything else, as lps disable does, but nothing shows it as disabled, so lps disable is usually clearer.

It applies from their next sign-in. lps show and lps key list both say what it is now.

It is refused if it would leave the last enabled administrator nothing to sign in with: denied, key when they have no key, or password when they have no password.

lps key list <name> #

$ lps key list erin
credential  key
ID                                FINGERPRINT                                         ADDED       LABEL
893dab4342ba215def78c4036b598b65  SHA256:QDzrP/cBqV7YSxqoCgCJQqKJlo6ijyJ8U05BIEKmYnI  2026-10-04  laptop

The first line is the credential policy. ID is what lps key remove takes, FINGERPRINT is as ssh-keygen -l prints it, and ADDED is the day the key was added, in UTC.

lps key add <name> <file> [label] #

$ lps key add erin erin.pub laptop
key enrolled; credential policy unchanged

The file is one line of an OpenSSH public key file, such as ~/.ssh/id_ed25519.pub. Ed25519 keys are accepted, and RSA keys of 3072 to 8192 bits; nothing else is. Without a label, the key's own comment is its label.

A key the principal already has is refused, as is a 33rd.

lps key remove <name> <id> #

$ lps key remove erin 893dab4342ba215def78c4036b598b65
key removed

Removing the last key of the last enabled administrator, when a key is all they may sign in with, is refused.

Changing your own keys #

lps is for administrators, but a principal does not need one to manage their own SSH keys. Anyone signed in to an account this machine holds can add a key to it or remove one, through a program that asks on their behalf, such as a desktop settings app. Nothing is changed for anybody else's account: the account is always the one the program's token belongs to.

Three things differ from lps key:

  • The current password is asked for, every time. Being signed in shows that someone signed in as you, not that you are at the keyboard now, and a key added from an unattended session would be a way back into the account. A wrong password changes nothing and says Authentication failed.
  • An account with no password cannot do it. That is an account whose policy is key or none, or one whose policy is password but that has no password set. There is nothing to prove who you are with, so the change is refused and you are told to ask an administrator, who can make it with lps key.
  • A key is removed by its fingerprint, the SHA256: value ssh-keygen -l prints for your .pub file, rather than by lps key list's ID.

Everything else is as lps key add and lps key remove: the same key types and sizes, a key you already have or a 33rd refused, the key's comment as its label. Adding a key does not let you sign in with it unless your credential policy is key or either, which only an administrator can set.

Each change, and each refusal once the account is known, is recorded as an lpsd.credential.changed event, as a change of your own password is. operation.name is ssh-key-added or ssh-key-removed, and outcome.reason says what stopped a refused one, such as wrong-credential. A change an administrator makes with lps key is recorded as lpsd.account.modified instead: see what is recorded.

On the desktop, the program is My Settings, the app for your own account; type my in the launcher to open it. Its SSH Keys section lists your keys by label, fingerprint, type and the day each was added. Add Key… opens a form under the list for the one line of your .pub file and your password. Remove asks for your password under the key before it is taken away, and asks again if it was wrong. An account with no password sees why its keys are an administrator's to change, in place of these forms.

Your password is changed with passwd, or in My Settings.

Changing your own display name #

Programs acting on your behalf can show you your own account as lpsd holds it — your name, display name, credential policy, whether you have a password, and your keys — and set your display name, the human name shown beside your account name. Setting a display name asks for no password, since it is not something you sign in with. Both are served on /run/lpsd/self.sock; see the administrative socket.

Open My Settings (type my in the launcher). In Account, Display Name sets your display name, with Apply once it is changed. The desktop reads it when you sign in, so it shows from your next session.

Profiles #

lps set <name> [options] #

Changes part of a profile, leaving the rest alone.

$ lps set jack --shell /bin/bash --display-name "Jack Palfrey"
updated jack
OptionEffect
--home <path>Home directory. Absolute.
--shell <path>Login shell. Absolute.
--display-name <text>Empty clears it.
--primary-group <group>Which group projects to the POSIX gid.

None of this is identity — no security descriptor names a home directory, and the token carries none of it. It is what login needs in order to start a session, and it reaches login on the logon reply itself.

lps does not create the home directory. It records the path. A principal whose home does not exist still signs in, starting in /, and login says so.

The primary group need not be a membership: authd adds it to the token if it is missing, so the order of two commands does not matter.

Groups #

Local groups are objects with a name, a RID, a gid and a description — unlike well-known groups, which exist on every Peios machine and are not stored here.

lps group list #

$ lps group list
NAME          RID       GID  MEMBERS  SID                                             DESCRIPTION
developers   1001   1001001        2  S-1-5-21-2847362817-1094533892-3310298447-1001  Builds the software

MEMBERS counts principals in this store, which is the only count lpsd can answer for. DESCRIPTION appears when any group has one.

lps group create <name> [description] / lps group delete <name> #

$ lps group create developers "Builds the software"
created the group developers with RID 1001

A group's description says what it is for. Anyone may read it — getent doesn't show it, but Principals Manager does, to everyone.

Deleting is refused while anyone is still a member, or while the group is anybody's primary group. Either would leave a record pointing at something that no longer exists.

lps group rename <name> <new-name> #

$ lps group rename developers engineers
renamed the group developers to engineers

It keeps its SID, so its members, and every permission that names it, stay as they are. A name held by a principal or another group is refused.

lps group describe <name> <description> #

$ lps group describe engineers "Builds and ships the software"
set the description of the group engineers

An empty description, lps group describe engineers "", clears it.

lps group add <name> <group> / lps group remove <name> <group> #

$ lps group add alice Administrators
added alice to Administrators

Removing BUILTIN\Administrators is refused if it would leave no enabled administrator.

Claims #

Claims are named, typed attributes carried on the token and read by conditional ACEs. They are how a security descriptor can say anyone in Engineering rather than naming principals one by one.

lps claim set <name> <claim> <type> [value]... #

$ lps claim set jack Department string Engineering
set the claim Department on jack
$ lps claim set jack Level int64 7
set the claim Level on jack
TypeValues
int64Signed integers
uint64Unsigned integers
booleantrue/yes/1 or false/no/0
stringText
sidSIDs, in S-1-… form
octetHexadecimal, even length

A claim may hold several values, and may hold none — lps claim set jack Department string empties it, which is different from removing it.

Claim names are matched case-insensitively, so setting department replaces Department.

lps claim remove <name> <claim> #

$ lps claim remove jack Department
removed the claim Department from jack

A claim reaches a principal at their next sign-in. A token's claims are fixed when it is minted, so setting one changes nothing for a session already running.

Exit status #

CodeMeaning
0Succeeded
1lpsd refused the request, or could not be reached
2The command line was wrong

The split lets a script distinguish "I called this incorrectly" from "it was refused".

Common failures #

cannot reach lpsd … it does not appear to be running — lpsd is not up. It exits if it cannot reach authd, or if the store will not read; check its logs before restarting it.

permission denied — you are not an administrator, or the socket's descriptor does not admit you. See the administrative socket.

there is no principal named … — names are case-insensitive, so this means the account genuinely is not there. lps list will show what is.

there is no group named … and it is not a SID — the name matched no well-known group and no local one, and does not parse as a SID. lps group list shows the local ones.

Principals Manager

Peios / Using Peios / Manage local accounts and groups

Principals Manager shows this machine's principals, the users and groups it knows, and lets an administrator manage the local ones. A local principal is one this machine holds itself, in lpsd's store; a built-in one, such as SYSTEM or Administrators, is the same on every Peios machine and is defined by authd.

Start it from the launcher: Principals Manager.

Users and groups #

Users and Groups, in the bar, switch between the two lists.

  • A user is listed with their full name, if they have one, whether they are local or built-in, and whether they are enabled. A disabled user keeps everything but can't sign in, and is greyed.
  • A group is listed with whether it is local or built-in, how many members it has, and its description: what it is for. Some built-in groups, such as Everyone, have no list of members at all: who is in them is decided by a rule as each person signs in, and they say By rule.

Type in Find to narrow the list to names, full names, descriptions or SIDs containing what you type.

A user or group in full #

Select one to see it in full on the right.

For a user, that is their user ID, primary group, home folder, shell and SID; the kinds of sign-in they may use, such as at the machine or over the network; the groups they are in; and their claims, the named values that permissions written with conditions are checked against. Copy SID copies their SID.

An administrator also sees what a local user signs in with, such as a password or an SSH key, and their SSH keys: the public keys they may sign in with over SSH, each with its label, its fingerprint and the day it was added.

For a group, it is its description, group ID and SID, and who is in it. Who is in a built-in group such as Administrators is recorded on this machine, as a local group's is, so it is listed too. Someone whose primary group it is, is in it.

Select a group a user is in, or a member of a group, to go to it.

Making a user #

New user, in the bar, or Ctrl+N, asks for:

  • a name, which they sign in with;
  • a full name, if you want one shown;
  • a password, typed twice; or tick Signs in without a password, and nothing is asked for on sign-in paths that permit credential-free access; SSH does not permit this;
  • whether they are an Administrator, who may change the machine and its users and groups;
  • a home folder and a shell, if not /home/<name> and /bin/sh.

Create makes the user with all of it, or not at all: if anything is refused, such as a name already taken, nothing is made and the window says why.

Changing a user #

Select a local user, and their buttons are below their details:

  • Edit changes their full name, home, shell and primary group. Their home folder is made at their first sign-in, and is not moved if you change it. Their primary group is the one their new files belong to, and they are in it whatever their other groups.
  • Rename changes the name they sign in with. They keep their SID, so their files, permissions and groups stay theirs. Their home stays where it is; change it under Edit if it should follow.
  • Set password gives them a new password. A user who signed in without one needs it from then on. Their sessions already signed in carry on.
  • Sign-in chooses what they sign in with, and the ways they may sign in.
    • Signs in with: a password, an SSH key, either, nothing (no credential is asked for where credential-free sign-in is permitted, excluding SSH), or nothing accepted (every sign-in is refused, though nothing shows them as disabled). Adding a key doesn't change this, so a key added isn't used until this allows one.
    • May sign in: as the machine's default allows (at the machine, on a remote desktop, over the network, and the rest a person uses), or only the ways you tick. To run a service lets the service manager start a service as them without any password, and is never part of the default.
  • Add to group puts them in a local group, or in a built-in one whose members are recorded here, such as Administrators or Users. Remove, beside a group they are in, takes them out of it.
  • Add, beside SSH keys, adds a public key: paste one line of their .pub file, such as ~/.ssh/id_ed25519.pub. Ed25519 keys are accepted, and RSA keys of 3072 to 8192 bits. Without a label, the key's own comment is its label. Remove, beside a key, removes it.
  • Add, beside Claims, gives them a claim: its name, its type, and its values, one to a line. Each value is checked against the type, and one that doesn't fit is named. A claim of SIDs may name a user or group instead of giving a SID. Edit changes a claim's type and values, and Remove takes it away. A claim with no values is still a claim they have, which conditions treat differently from one they don't.
  • Disable stops them signing in and keeps everything else; Enable lets them again.
  • Delete removes them, after asking. Their SID is never given to anyone again, so files and permissions that name it name nobody; Disable instead is offered, and is usually what you want.

Changes to how a user signs in, to their keys, to the groups they are in and to their claims apply from their next sign-in. Disabling an account does not end existing sessions. Use Task Manager’s sign-out action when those must end; it can lose unsaved work. Session lifecycle explains the permissions and limits.

Groups #

In Groups, New group (or Ctrl+N) makes a local group, with a name and a description of what it is for. Anyone may read the description.

Select a local group, and its buttons are below its details:

  • Edit changes its description; empty, it has none.
  • Rename changes its name. It keeps its SID, so its members, and the permissions that name it, stay as they are.
  • Add member puts a local user in it. Remove, beside a member, takes them out; a member whose primary group it is has to be given another under Edit first.
  • Delete removes it, after asking, once nobody is in it. Its SID is never given to anything again.

A built-in group whose members are recorded here, such as Administrators, has Add member and Remove too, but can't be renamed, described or deleted: authd defines it, the same on every Peios machine.

The local account store protects the last enabled administrator under its sign-in checks. Disabling or deleting the last one, taking away every way they could sign in to administer, or leaving them nothing to sign in with, such as removing the only key of one who signs in only with a key, is refused, and the window says why.

This last-administrator guard does not check whether a console, remote service or network path is reachable. Verify fresh access before closing a working administrator session.

Privileges #

What a person may do beyond what permissions allow them, such as back up any file or shut the machine down, is this machine's local policy: one record per user or group in the registry, which authd reads at every sign-in. Each record may give privileges, an integrity level, an owner for what they make, and a default DACL, the permissions an object they make gets when it has no parent to inherit from. See Assigning privileges for what each does and how records combine.

Privileges, in the bar, lists the records: who each is for, how many privileges it gives, and its integrity. A service's record is named by the service. Select one to see it in full, each privilege with what it lets its holder do. Denied to everyone, the first row, is the privileges no record can give.

A user's page says what they would get in all if they signed in now: the privileges, integrity, owner and default DACL that their own record, their groups' records and Everyone's come to, and which records those are. Signed in chooses the kind of sign-in, such as at the machine or over the network, since a record may be for one kind of sign-in. A group's page says what its record gives its members.

Changing them:

  • New record (or Ctrl+N) makes a record, for a user or group here, a well-known name such as Everyone or Network, or a SID. Give them their own record, on a user's page, and Give it a record, on a group's, do the same for them.
  • Edit changes what a record gives: tick its privileges, choose its integrity, and type an owner or a default DACL, in SDDL. Anything left empty, the record says nothing about. A default DACL that isn't SDDL authd could use is refused.
  • Delete removes a record, after asking. What it gave is no longer given.
  • Edit, on Denied to everyone, chooses what no record may give.

Taking SeChangeNotifyPrivilege from Everyone or Administrators is asked about first: without it, nobody who has it only from there can pass through a folder, so no shell starts for them.

A record names a user or local group by its SID, since authd doesn't ask lpsd what a name means; the window does this for you. What authd warns about in the records is shown above the list, with why: a record it ignores, such as one named by a name it doesn't know, or two records for the same principal, such as Administrators and S-1-5-32-544, which both apply and should be one.

On a machine with no policy at all, authd gives Everyone SeChangeNotifyPrivilege and nothing more, and the list says so. The first record saved makes the policy, with that in it, since once there is a policy it is all there is.

Changes apply from each person's next sign-in.

Changes made elsewhere #

The lists are read again every few seconds, so a user, group or privileges record added, changed or removed with lps or reg, or by another administrator, appears without your doing anything. Refresh, or F5, reads everything at once.

What you may see and change #

Anyone may see the users and groups: the identity socket that Principals Manager reads them from answers everyone.

What a user signs in with, and their SSH keys, are shown here only to an administrator: lpsd holds them, and tells nobody else, except each user about their own (see Changing your own keys). The privileges records are shown to anyone signed in, and changed by whoever the registry key's permissions let, which as shipped is Administrators.

Changing them needs BUILTIN\Administrators, enabled in your token, as lps does. Without it, the window says You may look, but not change anything, and why, and offers no changes. Built-in principals and groups can't be renamed or deleted, and the window says so when one is selected.

In a terminal #

lps list lists the users, lps show NAME shows one in full, and lps group list lists the local groups. getent group Administrators lists who is in a group, and lps key list NAME lists a user's keys. lps add, lps set, lps rename, lps password, lps policy, lps key, lps logon-types, lps claim, lps group, lps disable and lps remove make the same changes as the window. See The lps command. The privileges records are registry keys under Machine\Generic\Authn\Policy, read and changed with reg, and token show --all lists what a session's token actually holds.

Assigning privileges

Peios / Using Peios / Manage local accounts and groups

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

That policy lives in the registry:

Machine\Generic\Authn\Policy

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

Principals Manager shows and edits it from the desktop. Its Privileges list is the records on this page, and each user's page says what they would get in all, worked out by the same code authd mints from.

One record per principal #

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

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

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

It is also the only shape that holds more than privileges. Integrity, the default owner, the default DACL and the logon types a peer may originate all live on the same record.

Naming a principal #

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

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

Names are matched case-insensitively. The recognised ones are SYSTEM, Everyone, Authenticated Users, Administrators, Users, Guests, Local Service, Network Service, Local (everyone signed in on this machine), and the logon types Interactive, Remote Interactive, Network, Batch, Service and Anonymous.

Two limits are worth knowing before you hit them:

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

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

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

Two subkeys naming one principal, such as \Administrators and \S-1-5-32-544, both apply, and authd warns about them. Their privileges add up like any two records', but for integrity, Owner and DefaultDacl whichever is read first wins, which is an order nobody chose. Merge them into one. Principals Manager shows the warning above its Privileges list.

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

This is the most important behaviour on the page.

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

StateWhat a principal gets
Key absentThe compiled floor — Everyone gets SeChangeNotifyPrivilege, and nothing else
Key presentExactly what the records say
Key present but unreadableNothing, and a loud log line

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

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

What a machine ships with #

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

A stock machine grants:

PrincipalPrivilegesIntegrity
EveryoneSeChangeNotifyPrivilege, SeCreateSymbolicLinkPrivilege(Medium, by default)
Administratorsthe operational set below, plus both of the aboveHigh

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

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

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

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

How the values compose #

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

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

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

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

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

Writing each value #

Privileges — REG_MULTI_SZ #

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

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

Integrity — REG_SZ or REG_DWORD #

A tier name, matched case-insensitively:

NameValue
Untrusted0
Low4096
Medium8192
High12288
System16384

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

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

Owner — REG_SZ #

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

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

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

DefaultDacl — REG_SZ #

The DACL an object gets when this token creates it with no parent to inherit from, written as SDDL. The shipped policy names it on the Everyone record:

D:(A;;GA;;;S-1-3-4)(A;;GA;;;SY)(A;;GA;;;BA)

That is the owner, LocalSystem and BUILTIN\Administrators, full control, and nobody else — the same shape authd stamps on a home directory. S-1-3-4 is OWNER RIGHTS, which the access check resolves against the object's owner at the time of the check. You can also write it as the alias OW. It has to be OWNER RIGHTS rather than CREATOR OWNER: the kernel copies a default DACL onto a new file verbatim and substitutes no placeholders, so a CREATOR OWNER ACE would name nobody.

The value is a fallback and is reached rarely. Whenever the parent has inheritable ACEs the child takes those and the default DACL is not consulted (see Inheritance), and every filesystem root on Peios carries inheritable ACEs. What reaches it is an object with no parent, such as an abstract socket, or a child of a container whose descriptor was written without inheritable ACEs. There is no kernel fallback beneath it: a token with no default DACL leaves such an object with a null DACL, which grants every caller everything.

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

A value that does not parse is dropped with a warning and the token is minted with no default DACL — a typo costs the customisation, not the session, but it does leave the null-DACL case open until it is corrected.

LogonTypes — REG_MULTI_SZ #

The logon types this principal may originate — request of authd over /run/logon.sock on somebody else's behalf. This is a statement about the asker, not about the account being signed in: login originates Interactive logons for whoever is at the console, a graphical greeter would too, and a web console would originate Network ones.

\S-1-5-80-…-…   (a greeter's service SID)
    LogonTypes  REG_MULTI_SZ  ["Interactive"]

The names are Interactive, Network, Batch, Service, NetworkCleartext, NewCredentials and RemoteInteractive, matched case-sensitively; an unrecognised name is dropped with a warning and the rest still applies.

The proposed logon type is not cosmetic. It selects the group SIDs the minted token carries — Interactive puts S-1-5-4 on it, RemoteInteractive puts S-1-5-4 and S-1-5-14 — and policy records can key on those SIDs, so the type must be what the peer is permitted, never merely what it proposed. A greeter granted ["Interactive"] cannot mint itself a network-shaped token, nor the reverse.

A principal with no LogonTypes originates nothing. SYSTEM is the exception and is permitted every type unconditionally — it administers this very key, so a registry constraint on it would enforce nothing, and authd does not pretend otherwise.

An empty list revokes: it is "may originate nothing", distinct from the value being absent only in that it says so on the record.

LogonSocketDescriptor — REG_SZ, on the Policy key itself #

The security descriptor /run/logon.sock carries, as SDDL, read once at authd startup. The built-in value gives SYSTEM and Administrators full access, and gives every authenticated principal connect access alone, so that anyone signed in can change their own password and sign themselves out:

O:SYG:SYD:P(A;;GA;;;SY)(A;;GA;;;BA)(A;;0x100082;;;AU)

Connect access is FILE_WRITE_DATA; 0x100082 grants it with stat and sync. Reaching the socket does not let a peer originate a logon: that is the LogonTypes record above, and a peer without one originates nothing however it connected. So admitting a new originator that is not already an authenticated principal takes both edits, and one that is takes only the record.

A replacement descriptor that leaves out the AU entry turns off self-service password change, and signing yourself out, for every principal it leaves out.

SessionEndSecurity — REG_SZ, on the Policy key itself #

Who may sign somebody else out: end a logon session that is not their own. authd does the work when asked over /run/logon.sock (Session lifecycle); this value, as SDDL, is the security descriptor it access-checks the caller's token against, for the one right 0x1 (every generic right maps to it). It is read on every request, so an edit applies to the next one. With the value absent, the descriptor is:

O:SYG:SYD:(A;;0x1;;;SY)(A;;0x1;;;BA)

— SYSTEM and Administrators. Granting 0x1 to a group lets its members sign anybody out.

Signing yourself out needs nothing here: a person may always end their own session. And nothing here lets anyone end SYSTEM's session, Anonymous's, or a service's — a service is stopped through the service manager.

A value that is present but unusable — not a REG_SZ, empty, or not valid SDDL — lets nobody sign anybody else out until it is fixed or removed, and authd logs why on each request. It does not fall back to the default, which might grant more than the site meant.

Locking a machine down #

To forbid a privilege regardless of what any record says:

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

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

What this cannot express yet #

Account sign-in restrictions are a separate control. These privilege records decide what a session gets. The account store also supports lps logon-types to limit which kinds of sign-in an account may use, and lps disable to prevent new sign-ins altogether. LogonTypes above constrains what the asking service may request; it does not replace the account restriction. Follow the account command reference for supported types and the last-administrator guard.

Privilege constants do not establish assignment support. Earlier privilege guidance described SeTakeOwnershipPrivilege, SeRelabelPrivilege and SeSystemProfilePrivilege as absent from the ABI and therefore impossible to assign. The current generated KACS ABI defines constants for all three. That contradicts the ABI-absence claim, but does not by itself establish the accepted privilege-name table in authd or a service configuration, or a release in which assignment became available. Confirm support for the deployed build and inspect the actual token rather than relying on either blanket claim. Even when take-ownership is available, changing the owner does not guarantee DACL repair.

Seeing what a token actually got #

$ token show --all

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

To see what a sign-in would get before anyone signs in, open the user in Principals Manager: it works the policy out for them, for each kind of sign-in, and says which records it came from.

See also #

  • Privileges — the model these records feed.
  • The token command — reading the privileges and integrity a live token actually carries.
  • Authorities and principal sources — the accounts these records apply to.

Peios Learn — documentation for the Peios project.

Built with Trail.