Data storage & residency
Every install is self-hosted. There's no central data store, no telemetry pipeline, no third-party analytics. Customer data lives in your MariaDB / MySQL database on your server. The application runs as a single PHP process — nothing phones home.
PDFs and uploaded files live in a local storage/
directory under your web root. The directory is denied at the
web-server level via a triple-layer .htaccess rule
(Apache 2.4 + 2.2 + RewriteRule fallback).
Authentication
Password hashing
All user passwords are hashed with bcrypt (cost factor 12). Plain text is never stored or logged.
Sessions
DB-backed sessions, not PHP file sessions. Secure cookie flag set automatically when HTTPS is detected. 30-day idle expiry.
Login rate limiting
Failed login attempts are throttled per email and per IP address. Default: 5 attempts per 15 minutes.
CSRF tokens
Every state-changing form posts a per-session token. Tokens expire on logout and on session timeout.
Security headers
Every HTTP response carries seven security headers. They're applied at the request boundary so coverage is uniform across every page (including share tokens, binary downloads, and the login flow).
| Header | Value | Why |
|---|---|---|
X-Content-Type-Options | nosniff | Prevents MIME-sniffing attacks. |
X-Frame-Options | SAMEORIGIN | Blocks clickjacking. |
Referrer-Policy | strict-origin-when-cross-origin | Limits referrer leakage. |
Permissions-Policy | camera/mic/geolocation/payment disabled | Disables unused browser features. |
Cross-Origin-Opener-Policy | same-origin | Isolates browsing context. |
Cross-Origin-Resource-Policy | same-origin | Blocks cross-origin asset loads. |
Content-Security-Policy | strict + 'unsafe-eval' for Alpine.js | XSS protection with Alpine.js compatibility. |
Audit log
Every action that mutates state writes to an append-only
audit_log table: who did what, when, from what IP
address, and what changed. The log is never editable from
the application UI — only direct DB access can modify
it. Super-admin impersonation events include both the
impersonator and the impersonated user in the metadata.
Retention is configurable per install (default: indefinite). Production deployments typically keep 12 months minimum.
Responsible disclosure
Found a security issue? We want to hear about it. Email
with the subject prefix Security disclosure.
Please include:
- A clear description of the issue and where it lives.
- Steps to reproduce (ideally with screenshots).
- The impact you observed or suspect.
- Your assessment of severity (informational / low / medium / high / critical).
We commit to acknowledging security reports within 48 hours and providing a remediation timeline within 7 days, depending on severity. We're happy to coordinate disclosure timing and credit you in the changelog if you'd like.
Vulnerability history
We publish every fix that addresses a security issue in the
changelog with the security
tag. There are no silent fixes.