Error messages are an information leak wearing a helpful face
Most apps show you the server’s error. That error knows table names, ids and stack traces. Here is the line Nex draws, and where it draws it.
A helpful error message and a security disclosure are frequently the same string. “Channel 8f2a not found in table channels” is useful to a developer, useful to an attacker, and useless to the person who clicked a button.
The decision Nex makes is to treat every message as a disclosure until somebody has decided otherwise, one message at a time.
Two kinds of error, and only one of them travels
The backend distinguishes an error written to be read from an error that is a bug. The first is thrown as a typed error whose message reaches the browser intact. The second is a plain error, and the platform redacts it to “Server Error” before it leaves the server.
So choosing which to throw is the choice of what to disclose, and the default is the safe one. The rule in the codebase is short: if you are not sure a string is safe, throw the plain one. Nothing in a readable error may name a table, describe the shape of the data, or confirm that something exists which the caller cannot see.
“Not found” and “not yours” are one answer
If asking for someone else’s page returns “you do not have access” and asking for a page that does not exist returns “not found”, then the difference between those two responses is a tool for enumerating what exists. Both are the same answer in Nex, deliberately.
There is exactly one exception, and it is argued for rather than overlooked: a published page tells a reader who cannot see it to sign in, rather than claiming not to exist. That address carries fifty bits of randomness, so whoever is asking already holds it — they were sent it. Being coy with that person is not security, it is a colleague concluding your link is broken.
The 404 and 500 pages show nothing
Not the message. Not the stack. Not in development either.
The development carve-out is the one people push back on, and it is the one worth keeping. A screenshot taken while debugging is still a screenshot, and it ends up in a ticket, a chat, a support thread, a conference talk. More to the point, anything reaching that boundary is unhandled by definition — it is a bug, not something to explain to the person who hit it. It goes to the console, which is where you look when you are the one debugging.
The client half of the rule
On the browser side there is one helper that every catch block goes through. A typed error from the server is shown. Anything else is logged and replaced with a fallback sentence written for that specific spot.
That second bucket is where stack traces and dependency internals live, and in development the platform does not redact them — so “just show the error” is a habit that works perfectly right up until a production build makes it a disclosure. Routing every error through one function makes the safe path the easy path, which is the only kind of security rule that survives a deadline.
Rate limits are part of this
Every function that creates something is metered, per account and globally. That is not politeness about server bills; it is a correctness boundary. Every one of those functions is reachable by anyone with a session, and “an authenticated user would not do that” is not a defence, it is an assumption.