Authorizations
The authorization audit for the shell and web command surface: every registered
command, the module it lives in, and which rungs may run it — authorization is a
plain rank comparison on the ladder reader → contributor → maintainer →
admin.
The table below is generated from the live command registry by the
update-docs command — do not edit it by hand. It mirrors each command's
@register(requires='<profile>') declaration; run update-docs after changing
any command's requires, name, or module.
update-docs is admin-floored, and this table is why. Registration is itself
profile-filtered — a command above your floor is never registered — so the registry
a lesser profile sees omits the admin-floored commands, and regenerating from it
would drop those rows silently, in the very document whose value is being complete.
Only an admin's registry holds the whole command set, so the audit is never
truncated.
This is the audit view — who holds which profile is decided in the deployed
system of record at /etc/euler/authorizations.json (root:euler-authz 0640, outside
the repo, mutated only through the sudo-gated users command). There is no policy
file in the repo: absent a deployed one, the built-in default maps nobody and the
checkout owner floors to admin by uid. See the
web server guide for the model and how identity resolves to
a profile on each channel.
Columns
- Module — the Python module the command is defined in.
- Command — the registered command name.
- Reader · Contributor · Maintainer · Admin — a
✓on every rung that may run the command: those at or above its declaredrequiresfloor. Because the check is a rank comparison, each row is a run of ticks from its floor rightwards — a gap would be a bug.requiresis mandatory on the decorator, so every command has a floor; none can be exposed by omitting one.
Command authorization
| Module | Command | Reader | Contributor | Maintainer | Admin |
|---|---|---|---|---|---|
euler_webshell.msg.commands |
msg |
✓ | ✓ | ✓ | ✓ |
euler_webshell.shell.keys |
host-authorize |
✓ | |||
euler_webshell.shell.keys |
key-split |
✓ | ✓ | ||
euler_webshell.shell.keys |
user-authorize |
✓ | ✓ | ||
euler_webshell.shell.logout |
logout |
✓ | ✓ | ✓ | ✓ |
euler_webshell.users |
logout-all |
✓ | |||
euler_webshell.users |
users |
✓ | |||
solver.ai.api |
claude-api |
✓ | ✓ | ✓ | |
solver.ai.batch |
claude-batch |
✓ | ✓ | ||
solver.ai.models |
costs |
✓ | ✓ | ✓ | |
solver.ai.skill |
claude |
✓ | ✓ | ✓ | |
solver.ai.skill |
claude-blog |
✓ | ✓ | ||
solver.ai.skill |
claude-login |
✓ | ✓ | ✓ | |
solver.ai.skill |
claude-solve |
✓ | ✓ | ✓ | |
solver.ai.update_models |
update-models |
✓ | |||
solver.ai.update_usd_rate |
update-usd-rate |
✓ | ✓ | ||
solver.core.evaluate |
benchmark |
✓ | ✓ | ✓ | |
solver.core.evaluate |
compile-c |
✓ | ✓ | ✓ | |
solver.core.evaluate |
evaluate |
✓ | ✓ | ✓ | |
solver.core.git |
gh-merge |
✓ | ✓ | ||
solver.core.git |
git-audit |
✓ | ✓ | ✓ | |
solver.core.git |
git-commit |
✓ | ✓ | ✓ | |
solver.core.git |
git-filter |
✓ | ✓ | ✓ | ✓ |
solver.core.git |
git-hooks |
✓ | ✓ | ✓ | |
solver.core.git |
git-identity |
✓ | ✓ | ✓ | |
solver.core.git |
git-publish |
✓ | ✓ | ||
solver.core.git |
git-push |
✓ | ✓ | ✓ | |
solver.core.git |
git-reset |
✓ | ✓ | ✓ | ✓ |
solver.core.git |
git-status |
✓ | ✓ | ✓ | ✓ |
solver.core.git |
git-sync |
✓ | ✓ | ✓ | ✓ |
solver.core.list |
ls |
✓ | ✓ | ✓ | ✓ |
solver.core.new |
new |
✓ | ✓ | ✓ | |
solver.core.results |
results |
✓ | ✓ | ✓ | ✓ |
solver.core.summary |
mark |
✓ | ✓ | ✓ | |
solver.core.summary |
progress |
✓ | ✓ | ✓ | ✓ |
solver.core.summary |
summary |
✓ | ✓ | ||
solver.core.tags |
blog |
✓ | ✓ | ✓ | ✓ |
solver.core.tags |
blogs |
✓ | ✓ | ✓ | ✓ |
solver.core.tags |
create-blog |
✓ | ✓ | ||
solver.core.tags |
tags |
✓ | ✓ | ✓ | ✓ |
solver.core.tags |
update-tags |
✓ | ✓ | ✓ | |
solver.core.test_cases |
test-cases |
✓ | ✓ | ✓ | ✓ |
solver.core.viewer |
edit |
✓ | ✓ | ✓ | |
solver.core.viewer |
show |
✓ | ✓ | ✓ | ✓ |
solver.crypto.keys |
host-unlock |
✓ | |||
solver.crypto.keys |
key-export |
✓ | |||
solver.crypto.keys |
key-reconstruct |
✓ | ✓ | ✓ | ✓ |
solver.crypto.keys |
key-rekey |
✓ | |||
solver.crypto.keys |
user |
✓ | ✓ | ✓ | ✓ |
solver.crypto.keys |
vault |
✓ | ✓ | ✓ | ✓ |
solver.shell.bash |
! |
✓ | ✓ | ✓ | |
solver.shell.builtins |
? |
✓ | ✓ | ✓ | ✓ |
solver.shell.builtins |
clear |
✓ | ✓ | ✓ | ✓ |
solver.shell.builtins |
echo |
✓ | ✓ | ✓ | ✓ |
solver.utils.doclint |
check-commands |
✓ | |||
solver.utils.linter |
lint |
✓ | ✓ | ✓ | |
solver.utils.misc |
problems |
✓ | ✓ | ✓ | ✓ |
solver.utils.presentation |
update-presentation-data |
✓ | ✓ | ||
solver.utils.scripts |
pip-upgrade |
✓ | |||
solver.utils.scripts |
sys-setup |
✓ | |||
solver.utils.scripts |
tool-setup |
✓ | ✓ | ✓ | |
solver.utils.search |
search |
✓ | ✓ | ✓ | ✓ |
solver.utils.shell_utils |
pause |
✓ | ✓ | ✓ | ✓ |
solver.utils.shell_utils |
random |
✓ | ✓ | ✓ | ✓ |
solver.utils.shell_utils |
sleep |
✓ | ✓ | ✓ | ✓ |
solver.utils.update_doc |
update-docs |
✓ | |||
solver.utils.version |
version |
✓ | ✓ | ✓ | ✓ |
Where it is enforced
The floors above are a guardrail, not a boundary (docs/package-guide.md § 2.2): inside one
uid, someone who can edit their clone or run ! can call any function the shell can. So
every maintainer and admin command was audited for where its effect lands, and what
stops someone below its floor there (phase 7). The answer is never "the floor":
- own clone — it changes the caller's own working tree. It reaches anyone else only by
landing on
master, andmasteris locked on GitHub (branch protection: pull request with one approving review,lock_branch; only the repository admin bypasses), so landing is GitHub's decision, made against the GitHub account, not the shell profile. - the master key — it needs the master key, and holding the master key is the authority over it (proof of possession, § 9 of the web server guide): whoever can unwrap it can seal it to anyone, with or without this command.
- sudo — it goes through
sudo; only the operator is in thesudogroup, and no per-user uid is. - the spool —
euler-msgresolves the caller fromSO_PEERCREDagainst the root-ownedauthorizations.jsonand refuses a staff verb (a notice to a named user, a staff dismiss) belowmaintaineritself. - euler-maint —
/etc/euler/roster/isroot:euler-maint 2775, and the admin plane keeps that group at maintainer and above (users add/change), so writing the roster is a file permission. - relay firewall — mail goes through the loopback relay, which nftables opens to the
operator's uid and
euler-authonly; every per-user uid is barred.
| Command | Floor | What it changes | Enforced by |
|---|---|---|---|
check-commands |
admin | nothing — reads docstrings | nothing to enforce |
host-authorize |
admin | mails the master key, sealed to a public key | the master key; relay firewall |
host-unlock |
admin | this machine's own enc-key file, only if it unwraps with this machine's private key | the private key: a block sealed to another machine is refused before anything is written |
key-export |
admin | prints the master key sealed to a public key | the master key |
key-rekey |
admin | rotates this clone's key, commits re-encrypted blobs, re-issues over the spool | the master key; own clone (landing: GitHub); the spool for the re-issue |
logout-all |
admin | signs every web user out: closes their shells, revokes sessions and remember-me tokens | sudo (root-only instance route and the admin plane) |
pip-upgrade |
admin | the running venv | file ownership: /opt/euler is root's, so from a web shell it fails; a terminal's own .venv is its owner's anyway |
sys-setup |
admin | system packages and services | sudo |
update-docs |
admin | generated docs, committed | own clone (landing: GitHub) |
update-models |
admin | the model catalogue, committed | own clone (landing: GitHub) |
users |
admin | accounts, profiles, instances (list is reader and read-only) |
sudo (the wheel-gated admin plane) |
claude-batch |
maintainer | tags, via the Batches API | the caller's own API key (their vault); own clone (landing: GitHub) |
claude-blog |
maintainer | a blog article, via Claude Code | the caller's own Claude credentials; own clone (landing: GitHub) |
create-blog |
maintainer | a new blog article | own clone (landing: GitHub) |
gh-merge |
maintainer | merges a pull request onto master |
GitHub: the merge is made as the caller's GitHub account, and master is locked to the repository admin |
git-publish |
maintainer | pushes and opens a pull request | GitHub (a direct push to master only for the repository owner's account) |
key-split |
maintainer | lays the host's half (first run), or sends a sealed half | the master key; sudo for /etc/euler/share.json (root's); the spool for the send; euler-maint for the roster record |
summary |
maintainer | problems.json, committed; a staff notice |
own clone (landing: GitHub); the spool accepts a notice to staff from anyone |
update-presentation-data |
maintainer | the deck's data file, committed | own clone (landing: GitHub) |
update-usd-rate |
maintainer | values.conf, committed |
own clone (landing: GitHub) |
user-authorize |
maintainer | records a public key, sends half the master key, dismisses the request | euler-maint for the roster; the master key and the spool for the send; the spool for the dismiss |
The site's write routes are the same shape: a file edit, a delete, a progress upload or a
blog edit changes only the signed-in user's own clone, through their own instance (their
uid), on a profile the auth service vouched for (forward_auth, a separate uid) — and
reaches anyone else only through GitHub.
No gaps were found: nothing above depends on the floor alone. tests/test_enforcement_audit.py
holds this table to the live registry, so a new maintainer or admin command fails the
tests until it has a row here.