Security
What you'll learn
What Easy CMS protects for you, what stays your job, and a checklist before going live.
Before this page: Access control and Users & auth.
What Easy CMS does for you
Accounts and sessions
- Passwords are hashed with scrypt (N=2¹⁷) and never returned by any API. They must be at least 8 characters.
- Session tokens are random, signed with
EASY_CMS_SECRET, and stored only as hashes, so a copy of the database does not let anyone log in. - Cookies are
HttpOnly,SameSite=Lax, andSecurein production (or over HTTPS). - Logins are rate-limited: after 5 failures for an email (and IP, when known) within 15 minutes, login answers
429(auth.maxLoginAttempts,auth.lockWindow). - Logging out, changing the password or deactivating a user ends all of their sessions.
Requests
- CSRF: a write authenticated by the session cookie must send the session's CSRF token in the
x-csrf-tokenheader, and itsOriginmust be the API's own or inauth.trustedOrigins. Cross-site requests without anOriginare refused. Requests with a Bearer token skip the token check (a browser never sends one on its own). - CORS is closed by default.
corslists origins whose browser code may call the API; onlyauth.trustedOriginsmay send cookies. Origins incorsmay also write without a session cookie (e.g. a public form): there is no session to forge, and the collection's access rules still decide. - Request bodies are limited to 1 MB (JSON) and
upload.maxFileSize(files, 10 MB). - Errors show their details in development only; in production unexpected errors answer
Internal Server Errorand are logged on the server.
Content
- Access is closed by default: without rules, only logged-in users can read or change a collection. Field-level rules remove fields from responses and ignore them in input.
- Uploads are checked by their content (not the file name), limited in size, renamed, and served with
Content-Security-Policy: sandboxandnosniff, so an uploaded SVG or HTML file can't run scripts on your site. - Rich text rendering (
renderRichText) escapes text and attributes and drops unsafe URLs such asjavascript:. - Preview links carry a signed token for one document or global that expires after an hour.
The admin
- Admin pages send a strict Content Security Policy (scripts only from your own origin),
X-Frame-Options: DENYandReferrer-Policy: same-origin. - Admin modules are served by your server to logged-in users only; URLs of other sites are refused in the config.
What stays your job
- Keep
EASY_CMS_SECRETsecret and long (32+ random characters, e.g.openssl rand -hex 32). Changing it logs everyone out. - Write access rules on purpose, especially
read: a collection is public only when you say so, e.g.read: () => trueor "published only". - Give people the smallest role they need: editors get
editor, notadmin. Only admins can manage users. - Install plugins you trust. A plugin runs on your server, and its admin components run with the rights of whoever is logged in.
- Review migrations before deploying and keep dependencies updated.
- Back up the database and uploads. See Backups.
Checklist before going live
- [ ]
EASY_CMS_SECRETis set in the server's environment, not only in.envon your laptop. - [ ]
NODE_ENV=production, and the site is served over HTTPS. - [ ] Every collection's
readrule is what you intend; test it logged out. - [ ]
corsandauth.trustedOriginslist only your own origins. - [ ] Behind a proxy you control, enable trust-proxy (
trustProxyfor Nuxt and Next.js,--trust-proxyfor standalone) so rate limiting sees real client IPs. - [ ] The first admin has a strong password; other people have the
editorrole. - [ ] Migrations are applied (
easy-cms migrate) and backups run on a schedule.
Reporting a vulnerability
Report vulnerabilities privately as described in SECURITY.md, not in a public issue.
Next steps
- Access control: rules per collection, document and field.
- Migrations & deployment: ship schema changes safely.