All documentation
  1. Home
  2. Docs
  3. Security

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 ufw command 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

  1. Browser HTTPS :7443 on the panel's own listener · TLS 1.2+ · strict CSP, no inline scripts
  2. 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
  3. Unix socket, mode 0660 root:klyrn · peer credentials checked by the kernel · the actor is re-authorised against core's own records
  4. 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.

  5. nginx · php-fpm · mariadb · systemd · ufw · filesystem one Unix user per hosting account · one PHP-FPM master per account in its own cgroup · one grant per database · sandboxed unit per app
The only path from the internet to root on a KLYRN server. A compromised web tier can do no more than a signed-in administrator, and every one of those actions is in the audit log with actor, address, operation, target and outcome.

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 0710 that no other account can list.
  • One PHP-FPM pool per site, running as that user, confined by open_basedir, with exec disabled 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 openat2 and RESOLVE_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.