linux accounts use the chosen username, and refuse to take one over
Two changes, and the second is what makes the first safe. The officer_ prefix is gone: a member's account is the username the owner typed, so whoami says who they are and a commit from their checkout is attributed to something recognisable. Measured first — useradd on this host accepts everything validateUsername permits, including dots, hyphens, underscores and uppercase. The prefix was also load-bearing, though, and not for looks. ensureOsUser REUSES an existing account, which is what makes it re-runnable, and that was safe by construction while only we created officer_* names. Unprefixed, adoption becomes the dangerous path: a platform account named root would have found root in passwd, and every runAs for that member would have been a root shell. So adoption now requires the existing account's passwd home to be exactly the home we are about to confine — that is what makes it ours — and any uid below 1000 is refused outright. Verified: root and daemon refused as system accounts, and the owner's own username refused by name with its real home quoted back. Also, the ancestor trap from the first real install. A member's home is under DATA_PATH, which is under the OWNER'S home, and /home/<owner> is 750 on Debian and Ubuntu — so every mode bit on the account tree was right, the directory existed, and the member still could not reach it for want of x four levels up. It surfaced as "ssh-keygen: Could not stat …/.ssh: Permission denied", which points at the wrong thing entirely. firstUntraversableAncestor now walks the chain as the member before anything uses the home, and the error names the directory and the chmod. The dev machine was already 751, and the probe used /tmp, so it never crossed the ancestor that mattered. Worth remembering as a shape of mistake. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -164,18 +164,60 @@ requests over a socket is a bigger promise than this feature needs to make.
|
||||
|
||||
## Naming
|
||||
|
||||
`officer_<shellname>`, where `<shellname>` is `toShellUsername(username, email)` — the existing
|
||||
sanitiser, which already lowercases, strips `@…`, replaces illegal characters and truncates to 32. The
|
||||
combined name is truncated to 32 again.
|
||||
**The username the owner chose, verbatim.** `whoami` in a member's terminal says who they are, their
|
||||
prompt is their name, and a commit from their edge checkout is attributed to something recognisable.
|
||||
|
||||
The prefix earns its ugliness three times: it cannot collide with a system account, it makes every
|
||||
account this feature created greppable in `/etc/passwd`, and it means a member cannot pick a username
|
||||
that shadows something real.
|
||||
This carried an `officer_` prefix for about an hour. The prefix bought three things — no collision with a
|
||||
system account, a greppable record of what the feature created, and a member unable to pick a name that
|
||||
shadows something real — and cost the only thing anyone would notice. Measured before removing it:
|
||||
`useradd` on this host accepts everything `validateUsername` already permits, including dots, hyphens,
|
||||
underscores and uppercase.
|
||||
|
||||
**What replaced the prefix's safety is the adoption rule, and it had to.** `ensureOsUser` reuses an
|
||||
existing Linux account, which is what makes it re-runnable. That was safe by construction while only we
|
||||
created `officer_*` names. With the name being whatever was typed, adoption became the dangerous path: a
|
||||
platform account named `root` would have found root in passwd, and every `runAs` for that member would
|
||||
have been a root shell. So an existing account is adopted **only when its passwd home is already exactly
|
||||
the home we are about to confine** — that is what makes it ours — and any uid below 1000 is refused
|
||||
outright as belt and braces.
|
||||
|
||||
Verified:
|
||||
|
||||
```
|
||||
username "root" -> refused: 'root' is a system account on this machine.
|
||||
username "daemon" -> refused: 'daemon' is a system account on this machine.
|
||||
the owner's own account -> refused: 'pastilhas' is already a user on this machine, with its
|
||||
home at /home/pastilhas. Refusing to take it over.
|
||||
```
|
||||
|
||||
The resolved name is **stored** on the user row (`users.os_user`) rather than re-derived. `useradd` can
|
||||
adjust or refuse a name, and re-deriving would mean the platform's idea of who a member is could drift
|
||||
from what is actually in `/etc/passwd`.
|
||||
|
||||
## The ancestor trap
|
||||
|
||||
A member's home sits under `DATA_PATH`, which on a normal install sits under the **owner's** home — and
|
||||
`/home/<owner>` is `750` on Debian and Ubuntu. Every mode bit on the account tree can be correct, the
|
||||
directory can exist, and the member still cannot reach it, because they have no `x` on an ancestor four
|
||||
levels up.
|
||||
|
||||
What that surfaced as, on the first real install:
|
||||
|
||||
```
|
||||
ssh-keygen failed: Could not stat …/data/jg@pertento.ai/home/.ssh: Permission denied
|
||||
```
|
||||
|
||||
Which points at exactly the wrong thing. `.ssh` was there and correctly owned; the account could not
|
||||
traverse `/home/pastilhas`.
|
||||
|
||||
`firstUntraversableAncestor` now walks the chain **as the member** before anything tries to use the home,
|
||||
and the error names the directory and the fix (`chmod o+x <dir>`). `x` without `r` is the ask throughout:
|
||||
traversal, not listing — nobody gains the ability to enumerate the owner's home.
|
||||
|
||||
The development machine happened to be `751` already, which is exactly why the probe passed there and
|
||||
failed on a fresh install. Worth remembering as a shape of mistake: the probe used `/tmp`, so it never
|
||||
crossed the ancestor that mattered.
|
||||
|
||||
## Out of scope, and honest about it
|
||||
|
||||
- **This is not a sandbox.** A member with a shell is on the machine. They cannot read the owner's files
|
||||
|
||||
Reference in New Issue
Block a user