These are intentional design decisions that are documented and accepted.
Status: Accepted risk - by design
The WebUI's SSH key (/config/.ssh/id_symbios) is a normal root key without
command= restriction. The exec gateway (symbios-exec.sh) runs arbitrary
commands via bash -c.
Rationale: The admin can upload arbitrary Playbooks that execute as root. Restricting the exec gateway would provide a false sense of security while making the system harder to use. The exec gateway is the mechanism by which the WebUI manages the host.
Mitigations:
- Host key pinning via fingerprint verification (
ssh_exec.py:46-69) - Audit logging of all commands (
symbios-exec.sh:62-63) - SSH key is only accessible to the WebUI container (chmod 600)
- The exec gateway only accepts connections from the WebUI container
Status: Accepted - required for break-glass flow
The WebUI is accessible via multiple hostnames (localhost, Docker IP, public
domain). Restricting ALLOWED_HOSTS would break the break-glass flow on
http://localhost:8080.
Status: Accepted - break-glass requires HTTP
The break-glass endpoint on http://localhost:8080 requires plain HTTP.
Setting SESSION_COOKIE_SECURE=True would break the recovery flow. The
session is signed (not encrypted), but this is acceptable for a single-admin
system.
Status: Already implemented correctly
The AutheliaMiddleware enforces password change on first login:
- Checks if admin bind with password "admin" succeeds
- If yes: sets
force_password_changesession flag - Redirects all non-bypass requests to
/change-password/ - Only clears flag after successful LDAP bind with new password
Cannot be bypassed - the check runs in middleware before any view.
Status: Accepted - mitigated by forced change
The uid=admin user gets password "admin" during initial setup. This is
acceptable because:
- The password is forced to change on first login (C3)
- The LDAP bind DN (
cn=head-of-ldap) uses a randomly generated password - The init password is only valid until first successful login
SymbiOS is a single-server management platform designed for hobby sysadmins. The security model reflects this use case: a single admin operator manages one Debian server from a WebUI.
-
Single Admin Model: Only one operator (the admin) manages the server. The
ldap-adminsgroup is for privileged management access. Theldap-usersgroup exists for service-level access (e.g. Nextcloud, Matrix) but NOT for server management. -
SSH Exec Gateway: The WebUI sends commands to the host via SSH (
symbios-exec.sh). The SSH key has nocommand=restriction - this is intentional and documented. The exec gateway runs all commands as root. -
Break-Glass Access:
http://localhost:8080provides passwordless admin access for recovery when Authelia/Traefik is misconfigured. This is only reachable from the host itself or via SSH tunnel. -
Authelia SSO: Remote access goes through Traefik + Authelia. Only users who pass forward-auth are accepted. The
Remote-Userheader is only honored from the Traefik proxy IP.
- LDAP only listens on
127.0.0.1:389inside the Docker network. Traffic never leaves the host. TLS would add complexity without security benefit for this architecture.
- Required for Traefik's Docker provider. Compromise of Traefik = root. This is accepted because Traefik is a base service managed by the admin.
- Session data is stored in signed cookies (HMAC), not encrypted.
- Only authorization flags (is_staff, force_password_change) are stored.
- No secrets or passwords in session data.
- LDAP only listens on 127.0.0.1:389 inside the Docker network.
- Traffic never leaves the host. LDAPS available via Traefik.
- Required for automatic service discovery. Industry standard.
- Consider Docker socket proxy for enhanced isolation.
- Bound to all interfaces but restricted by UFW to private networks.
- Required for break-glass access via SSH tunnel.
- Non-standard SSH port (33). Key-based auth enforced.
- UFW default deny policy limits exposure.
- DCT adoption limited in home server ecosystem.
- Registry-level verification provides sufficient integrity.
- Same trust boundary as Docker socket access.
- Standard Docker mechanism for configuration.
- Contains only playbook names and timestamps (not sensitive).
- Required for cross-UID read access (root + WebUI container).
- Only used for initial SSH key deployment.
- Removed from PATH after setup completes.
- Isolated on Docker internal networks.
- Only accessible from authorized containers.
- Required by Collabora for document conversion sandbox.
- Contained within Docker network.
- TLS 1.2 remains secure and widely supported.
- Excluding it would break compatibility with older clients.
- Single-admin system. Password policy is admin responsibility.
- Forced default password change provides initial security gate.
- Test scripts use StrictHostKeyChecking=no for dev environments.
- Production SSH uses key-based auth with known hosts.
- Installer-only feature for debugging playbook failures.
- No security exposure after installation completes.
- Extremely narrow race window (microseconds).
- Worst case: duplicate playbook runs (operational, not security).
- Explicit error handling throughout scripts.
- Deliberate architectural choice per coding standards.
- C2: Login required check now verifies is_staff (ldap-admins only)
- C5: LDAP password read via stdin instead of CLI argument
- C6: Shell injection prevented via shlex.quote() in all views
- C6b: Email/group validation added to symbios-ldap-user.sh
- C7: eval() restricted to docker compose/systemctl only
- C8: source replaced with grep; state file chmod 600
- H1: SECRET_KEY mandatory (ValueError if missing)
- H2: XSS prevented (|safe removed from flash messages)
- H4: Log stream requires authentication
- H5: Autoconfig XML requires authentication
- H6: DB passwords no longer on CLI (temp files with chmod 600)
- H8: Regex injection prevented (grep -F in state.sh)
- H9: Regex injection prevented (escaped keys in lib.sh)
- H10: Path traversal prevented (container ID hex validation)
- H11: Authelia secrets hidden from logs (no_log: true)
- H12: OIDC client secret dynamically generated
- H14: Password policy enforced (min 8 chars, letter+digit)
- M1: SMTP password hidden from Ansible logs (no_log: true)
- M4: TLS verification optional (checkbox for self-signed certs)
- M6: nslcd.conf mode changed from 0640 to 0600
- M13: Temp files moved from /tmp to /run/
- M15: Displayname validation prevents LDIF injection
- L1: DEBUG defaults to False
- L7: Unused csrf_exempt import removed