Developer docs
Security rules for apps
An app runs inside a real business's site, with its customers' accounts and data. These rules are not optional: the automatic checks and the reviewer look for them, and the templates already follow them. Keep the secure defaults.
Keys and secrets
- The Site Key (
SITE_KEY) is server only. Never send it to a browser, a log, a file or git. - A deployed app site has four secrets, all set by EzyStudio:
DATABASE_URL,JWT_SECRET,SITE_KEYandSITE_JOBS_SECRET. Read them only insrc/platform/config.ts. An app you list can't add secrets of its own, and a bundle with a private key or a live API key in it is refused. - Everything in a page or in browser code is public: no secrets, no hidden admin routes, no “security by obscurity” flags.
- Secrets your own feature issues (an invite code, an API token) are stored as a SHA-256 hash with a short hint, and shown once.
Network
- A deployed site's server reaches only the EzyStudio platform host and its own database. Everything else is blocked.
- Never fetch or proxy a URL a user typed in. Platform calls go through
src/platform/api.ts. - Don't add CORS headers to your site's server: its pages talk only to the site's own address.
- Payments go through EzyStudio. Sending payment details to an outside service (Stripe or PayPal scripts and forms, payment links) is flagged with a warning in review. A percentage fee, or a price inside a free app, fails the pricing check.
- Webhooks into the site live in their own Hono app, mounted before the CSRF checks, and verify an HMAC signature with a constant-time compare before reading the body.
Access control
- Every route that isn't public runs
withViewer → requireAuth → requirePermission(codename). Hiding a button is not access control. - Scope every query to the caller (
where: {id, ownerId: viewer.id}) and answer 404, not 403, for rows that aren't theirs. - Never accept
userId,role,status,ownerIdorcreatedByfrom the client. Build each write field by field from validated input. - Use only the built-in sign-in (
/account/*): don't add your own login, register or reset pages. - For a dangerous action (delete an account, change an email), ask for the current password again.
Input and output
- Validate every input: type, trimmed length, allowed values, format. Reject bad input, don't repair it.
- SQL only through Prisma's query API, or parameterized tagged templates. Never
$queryRawUnsafe, never SQL built from strings, never a column name from input. - HTML: Astro escapes values. Raw HTML only from
sanitizeHtml(); in the browser usetextContent, neverinnerHTMLwith data. - Links from users:
https:(andmailto:where it makes sense) only, parsed withnew URL. Neverjavascript:ordata:. - Requests are limited to 64 KB and there is no file storage for sites yet. Never put base64 files in the database.
- Redirects go to paths on the same site only.
In the browser
- Keep
sameOriginand the CSRF check on the whole site backend. Browser writes go throughsiteFetchor the portal's API client. - Keep the security headers and the Content Security Policy. Never add
'unsafe-inline'or'unsafe-eval', inline event handlers (onclick=) orstyle=""attributes in Astro markup. - No
eval, nonew Function, no scripts loaded from a CDN at runtime.
Customer data
- Collect only what the app needs. Never sell or share customer data, and let owners export or delete it.
- Say what you store and why in your data note. Health information is flagged for a closer look.
- Only the user, and people with permission, see an email address.
- Never log passwords, tokens, cookies, the Authorization header, emails, full request bodies, raw IP addresses or environment values.
- Record security events (role changes, deletes, exports) with
audit(): who, what, on what. - Rate-limit anything that sends email, costs a lot or can be guessed, and give the same answer whether something exists or not.
Dependencies
| Rule | Why |
|---|---|
| Add a package only when you need it, at an exact version. | Fewer things to break or attack. The dependencies in your bundle's manifest take exact versions only. |
| Put package.json and package-lock.json at the root of your bundle. | Review reads them, never installs them, and checks the versions against known advisories. Without a package.json the security check can only warn. |
| No packages with native modules. | Sites run on Workers, where they can't load. |
| No install scripts and no packages from git or URLs. | Review warns on both. |
| Prefer WebCrypto to crypto libraries. | It is built in and fast. |
A critical or high advisory in your dependencies fails the security check.
Files you don't edit
src/platform/** and docs/agent/** in a site are managed by EzyStudio and updated with the template. Don't edit them, weaken a guard or remove sameOrigin or csrf. If your app needs a change there, say what it needs instead.
The full rules
The same rules, written for coding agents and kept up to date with the templates, are served on every site at /api/public/site/v1/agent-docs/security.md. See Rules for coding agents.