| name | klytos-security-architecture |
| description | Security architecture and best practices for Klytos CMS. Use when dealing with authentication, encryption, access control, CSRF protection, rate limiting, security headers, HTTPS, or security hardening. Essential for secure development and understanding Klytos security model. |
Klytos Security Architecture
Secret Admin URL
CRITICAL: The admin panel URL is SECRET. It must NEVER be discoverable from the public-facing site.
Directory Structure
/ ā Web root (public-facing)
āāā index.html ā Redirect or landing page
āāā assets/ ā Public assets (CSS, JS, images, fonts)
ā āāā css/
ā āāā js/
ā āāā images/
ā āāā fonts/
āāā sitemap.xml ā Search engine sitemap
āāā robots.txt ā Crawler directives
āāā llms.txt ā AI indexing summary
āāā llms-full.txt ā AI indexing full content
ā
āāā {random-admin-name}/ ā SECRET admin directory (e.g. "x7k9m2-panel")
āāā .htaccess ā Routes all requests, blocks sensitive dirs
āāā index.php ā Front controller
āāā install.php ā Installer (renamed after use)
āāā t.php ā Analytics pixel
āāā config/ ā BLOCKED by .htaccess
āāā core/ ā BLOCKED by .htaccess
āāā data/ ā BLOCKED by .htaccess
āāā backups/ ā BLOCKED by .htaccess
āāā plugins/ ā PHP blocked, assets allowed
āāā admin/ ā Admin panel (requires auth)
āāā public/ ā Generated static site (served via .htaccess)
āāā templates/ ā HTML templates (BLOCKED)
Security Rules
-
No admin URL leaks: Generated HTML pages NEVER contain references to the admin URL.
- No admin links in HTML source.
- No admin paths in CSS/JS URLs.
- No admin references in meta tags.
- The
<meta name="generator"> says "Klytos" but NOT the admin path.
-
Public assets are separate: CSS, JS, images, and fonts for the public site
live in /assets/ at the web root, NOT inside the admin directory.
-
Build output goes to root: The build engine writes HTML pages to the web root
and assets to /assets/. The admin directory is never exposed.
-
Admin URL is configured during installation: The directory name is chosen by
the user or auto-generated. It should be random and non-guessable.
Encryption
- Algorithm: AES-256-GCM (authenticated encryption with associated data).
- Key: 256-bit (32 bytes) generated with
random_bytes(32) (CSPRNG).
- IV: 12 bytes, random per encryption (never reused).
- Authentication tag: 16 bytes (GCM built-in ā prevents tampering).
- Key storage:
config/.encryption_key with chmod 0600.
- Key rotation: Supported via
Encryption::rotateKey().
Encryption Levels
The site admin chooses an encryption level during installation. It determines which data is encrypted at rest:
| Level | What is encrypted |
|---|
| Basic | System config only (config.json.enc, license, AI keys, MCP tokens) |
| Medium | + Users, audit logs, sessions, chats, 2FA (GDPR-relevant data) |
| Professional | + ALL data (pages, blocks, templates, theme, menus, forms, logs, etc.) |
The level can be changed bidirectionally from Settings > Security (requires re-auth).
Option-Level Sensitivity
Plugins declare the sensitivity of their options via klytos_register_option(). This provides per-option encryption control independent of the site-wide encryption level:
| Sensitivity | Encrypted at | Example |
|---|
true | Always (all levels) | API keys, tokens, webhook secrets |
'user_data' | Medium + Professional | Emails, IPs, personal data (GDPR) |
false (default) | Professional only | Colors, toggles, non-sensitive settings |
klytos_register_option('my-plugin.stripe_key', true);
klytos_register_option('my-plugin.user_email', 'user_data');
klytos_register_option('my-plugin.color', false);
Identity Keys (RSA-2048)
- Admin identity is proven via an RSA-2048 key pair generated during installation.
- Public key: stored in
config/admin-identity.pub.enc (encrypted with AES).
- Private key: stored in
config/admin-identity.priv.enc (encrypted with AES).
- Recovery file:
klytos-identity.pem ā downloaded during installation, used with klytos-encryption.key for emergency access recovery via the unified installer.
- Challenge-response: The installer verifies identity by signing 32 random bytes with the private key and verifying with the public key.
Authentication Methods (MCP)
Order of authentication in token-auth.php:
- Bearer token:
Authorization: Bearer <token> ā tokens.json.enc
- OAuth 2.0/2.1 access token:
Authorization: Bearer <token> ā oauth_tokens.json.enc
- Application Password (Basic Auth):
Authorization: Basic base64(user:pass) ā app_passwords.json.enc
OAuth 2.1 Compliance
- PKCE mandatory for ALL clients (S256 only, plain rejected).
- No Implicit Grant (response_type=token rejected).
- No Resource Owner Password Credentials (grant_type=password rejected).
- Refresh token rotation (one-time use).
- Redirect URI exact string match (no wildcards).
- Bearer tokens in Authorization header only (query params rejected).
Password Security
- Algorithm: bcrypt with cost factor 12.
- Minimum length: 12 characters.
- Stored as hash only ā NEVER in cleartext.
- Application passwords: 24 chars, format xxxx-xxxx-xxxx-xxxx-xxxx-xxxx.
Rate Limiting
- Sliding window: 60 seconds.
- MCP requests: 60/minute per authenticated identifier.
- Auth failures: 10/minute per IP.
- Admin login: 5 attempts ā 15 minute lockout.
CSRF Protection
- Token: 32 hex characters, per-session.
- Required on ALL admin POST forms.
- Validated via
$auth->validateCsrf($token).
Security Headers
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: [with nonce support]
Permissions-Policy: camera=(), microphone=(), geolocation=()
.htaccess Protection
Blocks direct access to:
config/ (encryption keys, credentials)
core/ (PHP source code)
data/ (encrypted data files)
backups/ (backup archives)
templates/ (HTML templates)
.enc files (encrypted data)
.encryption_key (master key)
.install.lock (installation lock)
VERSION file
File Permissions
- Directories: 0700 (owner read/write/execute only).
- Encryption key: 0600 (owner read/write only).
- Data files: inherited from directory (0700 ā files are 0600 effective).
Audit Logging
Every significant action is logged with:
- Who (user_id, username)
- What (action type)
- On what (entity_type + entity_id)
- From where (source: admin, mcp, cli, plugin)
- IP address
- Timestamp
Retention: 90 days (configurable), auto-pruned by CronManager.