Preventing Abuse
Protect a production Dyrected app with Dyrected's built-in rate limiting and login lockout, then add the host-layer controls that still belong at your edge.
This page helps you decide which abuse protections belong in Dyrected itself and which still belong at your host, proxy, or edge.
Dyrected gives you two built-in protections now:
- built-in HTTP rate limiting for
/apiroutes - built-in auth account lockout for repeated failed logins
That is a good base layer, not a full security perimeter. In production you should still assume public routes, auth flows, preview routes, and upload paths need deliberate protection from the stack around Dyrected too.
The recommended path is:
- turn on Dyrected's built-in API rate limiting and configure proxy trust correctly
- leave auth lockout on unless you have a deliberate reason to disable it
- keep secrets and privileged keys server-side
- treat preview, uploads, and side-effect routes as real attack surfaces
- add host-level rate limits and network controls where Dyrected is not the right layer
- test the abuse cases you care about before launch
Start with Dyrected's built-in rate limiting
Dyrected now ships with in-process API rate limiting enabled by default. Out of the box it protects /api with a per-IP limit of 500 requests per 15 minutes.
That gives you a sensible default immediately, but it only works well if Dyrected can identify the real client correctly. If you are deployed behind a reverse proxy or platform edge, set trustProxy so Dyrected reads the forwarded client IP instead of treating the proxy hop as the caller:
import { defineConfig } from "@dyrected/core";
export default defineConfig({
rateLimit: {
max: 500,
window: 15 * 60 * 1000,
trustProxy: true,
},
// ...
});Use trustProxy: true when you trust the full forwarded chain from your platform. Use a number when you need to trust a specific number of proxy hops from the right side of that chain.
The recommended path is to leave the default /api scope alone unless you know you need something narrower or broader. If you do need more control:
- use
rateLimit.pathsto change which route prefixes are protected - use
rateLimit.skipfor the rare request class that should bypass the in-app limiter - use
enabled: falseonly if another layer is intentionally taking over this job
One caveat matters in production: this limiter is in-process. It is a useful app-layer protection, but it is not a distributed rate limit shared across multiple app instances.
Keep account lockout on
Rate limiting protects the route. Account lockout protects a specific user record after repeated bad password attempts. The two layers solve different problems, so keep both.
For auth collections, Dyrected enables login lockout by default. You only need to tune it when your product needs a different threshold or lock window:
export const Users = defineCollection({
slug: "users",
auth: {
maxLoginAttempts: 5,
lockTime: 15 * 60 * 1000,
},
fields: [{ name: "name", label: "Name", type: "text" }],
});Set maxLoginAttempts: 0 only if you have another deliberate lockout strategy and understand the tradeoff.
Add host and edge protection on top
Dyrected's limiter protects the app layer. Your host or edge should still protect the infrastructure in front of it.
That matters most for:
- auth endpoints
- media uploads
- preview routes
- custom routes that call third-party services
- any public collection endpoint that is expensive to query
Keep the normal protection mechanisms for your host too:
- framework middleware
- edge middleware
- reverse proxy or CDN rules
- provider-native throttling
- bot protection, WAF, or IP allowlists when needed
If you run more than one app instance, this outer layer stops being optional. It becomes the shared limit that protects the whole deployment.
Keep privileged credentials out of the browser
This is still one of the simplest and highest-value production rules:
- server-side keys stay server-side
- browser-visible keys should only expose the access you actually intend to expose
If you embed the admin or use browser-side SDK calls, use your framework's public runtime variables only for values that are meant to be public in that environment. Do not leak your server-only API key just because a page needs content.
Treat preview and uploads as public surfaces
Server-side preview tokens are short-lived, but they are still bearer credentials. Anyone holding the token can redeem that draft until it expires.
For token-mode preview in production:
- set
DYRECTED_JWT_SECRET - keep preview URLs out of places they do not need to go
- guard sensitive preview routes at the app layer if the content is sensitive
Uploads deserve the same mindset. If a route accepts files, check that:
createandupdateaccess are as restrictive as they should be- public
readaccess is intentional - your storage and serving setup match the sensitivity of the files
Bound expensive reads and side effects
Abuse is not only malicious traffic. Real traffic spikes also expose weak spots.
A route can become expensive just by asking for too much data. Keep collection reads bounded:
- set explicit
limitvalues on list queries - keep
depthonly as high as the page actually needs - promote fields you filter or sort on frequently
Hooks need the same discipline. If a hook sends email, calls a webhook, or touches another API, decide whether that side effect should block the write. If not, fail it safely:
afterChange: [
async ({ doc }) => {
try {
await sendWebhook(doc);
} catch (error) {
console.error("Webhook failed:", error);
}
},
];That keeps downstream failures from turning into avoidable admin outages.
Pre-launch checks
Before you ship, verify the basics:
- built-in Dyrected rate limiting sees the real client IP in production because
trustProxyis set correctly - repeated login attempts hit Dyrected's account lockout and also get throttled by the host layer
- upload endpoints are protected the way you expect
- preview URLs cannot be reused long after issuance
- public collection reads have sane limits
- external side effects degrade safely under failure
Related pages
Building without a DB connection
Understand which Dyrected tasks can run during build without a live database, and where the real runtime boundary starts.
Overview
The main Dyrected performance levers are query shape, promoted fields, bounded reads, and infrastructure choices that match your deployment target.