Security
SynaptikCMS ships with a layered security setup out of the box. This page covers what is protected, how, and what you should configure before going to production.
Admin folder
During installation, install.php lets you choose a custom name for the admin folder. Use something non-obvious — avoid admin, cms, backend, or any other common name. This is the single most effective measure against automated scanners.
Protected paths
Direct HTTP access to sensitive paths is blocked at the .htaccess level.
Blocked files
| Pattern | Why |
|---|---|
settings.json, data.json | Site config and content database |
admin-credentials.php | Hashed admin password |
.htaccess files | Prevent rule disclosure |
Blocked directories
| Path | Method |
|---|---|
/bckps/ | Rewrite rule — returns 403 unless the referer contains admin |
/data/ | .htaccess inside the folder — Deny from all |
/private/ | .htaccess inside the folder — Deny from all |
The /bckps/ protection relies on the HTTP_Referer header, which can be spoofed. Do not treat it as a hard security boundary. Keep backups out of the web root for production deployments.
Security headers
Set via mod_headers in the root .htaccess:
| Header | Value | Purpose |
|---|---|---|
X-Frame-Options | SAMEORIGIN | Prevents clickjacking — the site can only be framed by itself |
X-Content-Type-Options | nosniff | Browser must honour the declared Content-Type |
Referrer-Policy | strict-origin-when-cross-origin | Limits referer leakage to external domains |
Permissions-Policy | camera, mic, geo, payment all disabled | Disables unused browser APIs |
Content-Security-Policy | See below | Controls which origins can load scripts, styles, fonts, frames |
Default CSP
default-src 'self';
script-src 'self' 'unsafe-inline' https://js.hcaptcha.com https://cdn.jsdelivr.net;
frame-src https://newassets.hcaptcha.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net;
font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net;
img-src 'self' data: blob:;
connect-src 'self' https://hcaptcha.com https://api.hcaptcha.com;
frame-ancestors 'self'
If you are not using hCaptcha, remove the js.hcaptcha.com, newassets.hcaptcha.com, hcaptcha.com, and api.hcaptcha.com entries. If you are not using Google Fonts, remove fonts.googleapis.com and fonts.gstatic.com.
'unsafe-inline' is required for scripts because the CMS injects inline JS for the i18n bridge (window.CMS_LANG) and gallery initialisation. Removing it will break the front end.
Contact form
The contact form (contact-process.php) has four independent protection layers:
| Layer | Mechanism |
|---|---|
| CSRF | HMAC-SHA256 token derived from a 32-byte secret stored in /private/contact.secret |
| Honeypot | Hidden field that bots fill in — submission is rejected silently |
| Rate limiting | Max 5 submissions per IP per hour. IPs are stored hashed (SHA-256) in /private/contact_rate.json |
| hCaptcha | Optional server-side verification. Configure via Settings → Contact |
Theme preview token
The live theme preview uses a signed HMAC-SHA256 token with a 2-hour TTL. The secret is derived from the bcrypt hash of the stored admin password — no additional secret file is needed. The token only works when $_SESSION['admin'] === true.
See Live Theme Preview for full details.
Browser caching strategy
Assets are cached aggressively. The .htaccess sets:
| Asset type | Cache-Control | Max-age |
|---|---|---|
| CSS, JS | public, immutable | 1 year |
| Images, fonts | public | 1 year |
| HTML, PHP | no-store | Never cached |
CSS and JS files served by render_header_scripts() include a ?v=<mtime> cache-busting parameter, so browsers automatically pick up changes without needing to clear cache.
Production checklist
[ ] Admin folder renamed to something non-obvious
[ ] /data/ and /private/ not accessible from the web (test: curl -I yourdomain.com/data/_index.json should return 403)
[ ] settings.json not accessible (curl -I yourdomain.com/settings.json should return 403)
[ ] HTTPS enabled — the CSP and cookie session are ineffective over HTTP
[ ] Directory listing disabled on the server (Options -Indexes or server config)
[ ] PHP error display off in production (display_errors = Off in php.ini)
[ ] /bckps/ moved out of the web root, or protected by server-level auth
