Security
The process that faces the internet cannot become root, open the database, execute a program or use a shell. The process that can do those things is not on the network.
Firewall and SSH
Security → Network and SSH reads the server rather than KLYRN's records:
ufw status numbered, ss -ltnp, and sshd -T for
sshd's own resolved configuration rather than a guess at its file. KLYRN's own table
answers exactly one question: did we create this rule.
The page pairs what is allowed with what is listening, because those are different facts and the gap between them is where surprises live. A database listening on all interfaces with its port open reads as an exposure, not as a row in a table.
What KLYRN will not do:
- Remove a rule it did not create. Rules added by the installer, by
you, or by another tool are shown, marked, and left alone. The page tells you the
ufwcommand instead. - Close the port sshd is listening on, unless another SSH port is verified both open and answering. The refusal says what would have happened: every session ended, every new one refused, including yours.
- Remove the last authorised key while password sign-in is off.
- Flush or reset anything. There is no such button.
Ports and source addresses are validated and rejected rather than escaped: a port of 0,
a port above 65535, an address containing a newline or a semicolon are all refused
before anything runs, and ufw is only ever reached with typed arguments.
Public keys are shown by fingerprint, type and comment. The key material itself is never sent to the panel, and a private key never touches KLYRN at all.
The boundary
- Browser
-
klyrn-web user klyrn
Sign-in, sessions, the REST API, live events and the panel's static files. Nothing else.
- become root
- open the database
- execute a program
- use a shell
- make outbound HTTP
- write outside its state directory
- Unix socket, mode 0660 root:klyrn · peer credentials checked by the kernel · the actor is re-authorised against core's own records
-
klyrn-core root · never on the network
A closed registry of typed operations: create site, create database, issue certificate, and so on. Each has its own request type, validator, authoriser and audit entry. No operation takes a command string or an unconstrained path.
- nginx · php-fpm · mariadb · systemd · ufw · filesystem
klyrn-web runs as uid 999 and handles sign-in, sessions, the REST API, live
events and the panel's static files. Nothing else. It reaches klyrn-core over
a Unix socket at mode 0660 root:klyrn, where the kernel checks the peer's
credentials and core re-authorises the actor against its own records.
Core exposes a closed registry of typed operations: create site, create database, issue certificate. Each has its own request type, validator, authoriser and audit entry. No operation takes a command string or an unconstrained path, programs get argument lists rather than a shell, and a CI check forbids process execution anywhere else in the codebase.
Accounts are hostile to each other
- One Unix user and group per hosting account, with a home at
0710that no other account can list. - One PHP-FPM pool per site, running as that user, confined by
open_basedir, withexecdisabled unless an administrator turns it on for that site. - One database user per database, granted on that database only, from localhost. Root stays on the Unix socket.
- One sandboxed systemd unit per Node application, on a private port with a memory ceiling.
- Kernel symlink and hardlink protections on.
- Paths inside an account resolved with
openat2andRESOLVE_BENEATH, so escaping the tree fails in the kernel.
Verified on the test server: a second site's PHP pool cannot read the first site's files, or /etc/hostname.
Signing in
- No default credentials
- The setup token is printed once on the console, and the setup endpoint stops answering the moment the first administrator exists.
- Passwords
- argon2id.
- Sessions
- Server-side,
HttpOnly,Secure,SameSite=Strict, rotated on sign-in, seven days. Visible and revocable from the account page. - Second factor
- TOTP with recovery codes. An administrator can clear a lost factor for another user; that is audited.
- Rate limits
- On setup and on sign-in, per address and again per account, so one attacker cannot lock every account by guessing.
- Cross-site requests
- A custom request header, an origin check and
Sec-Fetch-Site, on every method that changes something.
Supply chain
- Releases are ed25519-signed, with the public key built into the binary. A failed update rolls back automatically.
- Node.js runtimes are checked against nodejs.org's published SHA-256 list.
- WP-CLI is pinned by SHA-512; a mismatch refuses to install it.
- The PHP PPA is added with a pinned key fingerprint, not a blind
add-apt-repository. - The bootstrap script verifies the manifest signature before running anything.
What the panel sends
The panel is on its own TLS listener on port 7443, minimum TLS 1.2, never sharing a
listener with a customer site. Every response carries
X-Content-Type-Options: nosniff, X-Frame-Options: DENY,
Referrer-Policy: same-origin, a Permissions-Policy that turns off
camera, microphone and geolocation, Cross-Origin-Opener-Policy: same-origin
and a content security policy with frame-ancestors 'none' and
object-src 'none'.
API responses are Cache-Control: no-store, and an unknown JSON field in a request body is an error rather than something quietly ignored.
What is not covered
The full model, including the parts that are not finished, is on the security page. In short:
- Isolation is by Unix users and systemd sandboxing, not containers. A kernel vulnerability is a kernel vulnerability.
- Disk limits are measured, not filesystem quotas. See Resource limits.
- Per-account CPU and memory limits reach Node.js applications, not PHP-FPM pools or cron. See Resource limits for what is covered.
- The audit log has no retention policy and grows without bound.