Security and privacy model
DynoStatic assumes that public widget endpoints are hostile internet surfaces and that the admin is a same-origin private control plane.
Public requests
- Sites have explicit, normalized origins. There are no suffix wildcards and no
Refererfallback. - Public reads resolve the site, widget, origin, publication, and status before module data.
- Browser mutations require a short-lived signed ticket bound to widget, origin, visitor key, and action.
- Tickets are stored by SHA-256 digest, single use, expire after ten minutes, enforce a minimum age, and carry a randomized honeypot name.
- Ticket issuance is anonymously rate-limited by a purpose-derived visitor hash. The raw browser key is never stored in the ticket table.
- Form submissions also use a UUID idempotency key so safe retries cannot duplicate a message.
Admin requests
Passwords use Argon2id. Login attempts consume independent token buckets for the normalized account identifier and the client's privacy-preserving network prefix before password verification; successful authentication clears those buckets and failures always retain the same generic response. The browser receives a path-scoped, opaque administrator token in an HttpOnly, SameSite=Strict cookie that is also Secure under HTTPS. Only an HMAC of that token is stored in admin_sessions; expiry, account disabling, fingerprint changes, or deleting the row revokes access immediately. CSRF tokens are derived independently from the opaque token and verified against the database session record. Login also rotates the separate PHP session used for short-lived setup and interface state. Logout is POST-only and requires that CSRF proof; a link prefetch or cross-site GET cannot destroy the session.
admin_users is the canonical identity store. Legacy platform accounts are linked only by public ID or normalized email—never by numeric ID alone—and ambiguous upgrades stop without rewriting roles. Platform owners and administrators can manage websites, website owners/managers can configure them, website-scoped client editors can edit CMS content with draft-only or explicitly configured content publication authority, moderators can moderate content, and viewers are read-only. Client content grants do not grant schema, widget design, integration, build, deployment, or access-management authority. Write endpoints enforce these boundaries even when someone bypasses the navigation.
TOTP enrollment is available per administrator. Login enforces the six-digit authenticator code or consumes a one-use recovery code; recovery codes are stored only as Argon2id hashes. Sensitive exports, TOTP replacement/removal, and backup creation require elevated roles or password reauthentication as appropriate.
Private data
Fresh installation requires a random 256-bit setup token retrieved from a mode-0600 protected file through File Manager, FTP, or SSH. It is never rendered over HTTP, is rate-limited before database work, and is removed only after installation commits. The installer also makes public HTTP probes for an executable guarded content sentinel and the guarded configuration before it can run.
The installation key is generated during installation. Sodium secretbox encryption uses a different derived key context for each data purpose and owning widget. Form payloads and comment email addresses are encrypted before insertion. Provider credentials have an encrypted column and must never be returned to public APIs. Stored CMS media names, alternative text, identifiers, and URLs are rendered with DOM text/property APIs; media URLs must resolve to same-origin HTTP(S), so stored values are never interpolated into administrator HTML.
DynoStatic does not store raw visitor IP addresses. Visitor counting uses a daily rotating HMAC and retains no cross-day identity. Application errors go to the PHP error log without request bodies, secrets, or private payloads.
Uploads
Public files are limited to 25 MB and to JPEG, PNG, WebP, PDF, or plain text. Server-side Fileinfo determines MIME from bytes. PDF and image signatures receive additional checks; common executable markers are rejected. Files receive generated .upload.php names in dynostatic-content/uploads/ and are stored after an executable 404 guard; downloads go only through the validated media gateway. SVG, HTML, PHP, executables, and archives are not accepted.
Outbound HTTPS
Remote feeds use one outbound client. It requires HTTPS on port 443, rejects credentials and local names, resolves all A/AAAA records, rejects the host if any answer is private/reserved, pins cURL to a validated address, verifies TLS, refuses redirects, and limits connection time, total time, and response bytes. Provider adapters must use this client.
Security depends on a correctly configured HTTPS host and an ordinary working PHP handler. dynostatic-config.php and every private runtime payload are self-guarding PHP files that return 404 before private bytes can be reached. Apache/Nginx deny rules remain optional defense in depth. The health endpoint intentionally exposes no database names, accounts, sites, or secrets.