Missing Error Boundaries, Missing Auth Checks: The Vibe-Coded Production Trap

Missing Error Boundaries, Missing Auth Checks: The Vibe-Coded Production Trap

pr0h0
error-boundariesauthenticationproduction-risksjavascript
AI Usage (88%)

Why vibe-coded features fail in production

The pattern is familiar: a feature looks fine in the demo, then falls apart the first time a real user hits an edge case. In practice, the failure usually comes from two places at once. The client assumes rendering will always succeed, and the backend assumes the UI will only send allowed actions.

That combination is how a flashy feature becomes a support ticket.

I call this the vibe-coded production trap because the implementation often has enough polish to impress in review, but not enough defensive structure to survive bad data, broken state, or a replayed request. The code ships with happy-path logic and no hard stops.

The two bugs that travel together

These bugs are different, but they reinforce each other:

  • the React tree has no error boundary, so a render failure blanks the experience
  • the API has no object-level authorization check, so the action still works if someone calls it directly

The UI bug hides the backend bug. Users see a broken page and assume the feature is down. Meanwhile, the server may still accept requests from accounts that should never have access.

Missing error boundaries in client flows

In a modern React app, a thrown error during render can tear down part of the tree. If there is no boundary, the user gets a blank screen or a generic crash page. That is bad on its own, but it also removes the normal path that would have guided the user through the intended workflow.

A missing fallback state is a security problem when the client is the only thing discouraging a forbidden action. If the page breaks after a state transition, the user may still have a valid token and a known request shape.

Missing authorization checks on the backend

The backend mistake is more serious. The server receives a resource ID, trusts it, and checks only that the caller is authenticated. It never verifies ownership, team membership, subscription state, or role.

That means a free account can often access a paid object if it knows or guesses the identifier.

The fix is not “hide the button better.” The fix is to re-check authorization on every sensitive route.

Reproducing the failure with a small React and API example

Here is the kind of code I often see in rushed feature work.

profile-widget.jsx
function ProfileWidget({ user }) {
// This throws if user.preferences is null
return <div>{user.preferences.theme.toUpperCase()}</div>;
}

No boundary means any bad payload can break the component tree.

On the server side, the matching API problem looks like this:

routes/update-report.js
app.post("/api/reports/:id/publish", async (req, res) => {
const report = await db.reports.findById(req.params.id);

if (!req.user) {
  return res.status(401).send("login required");
}

report.published = true;
await report.save();

res.json({ ok: true });
});

This checks identity, not permission. Any logged-in user who can reference a report ID can publish it.

What the UI hides when rendering breaks

If the component crashes while loading a report, the user may never see the publish button. That looks safe in the browser. It is not.

The broken UI can suppress the visible path while the backend route remains callable. The real test is not “did the page render?” The real test is “can an unprivileged account still send the request?”

What the API still allows when auth is skipped

A direct replay from an untrusted account is enough to prove impact. You do not need to modify the frontend at all.

POST /api/reports/123/publish HTTP/1.1
Authorization: Bearer <free-account-token>
Content-Type: application/json

If the response is 200 and the report changes state, the access control is missing at the place that matters.

How to test for the real impact

Break the component tree on purpose

I like to test the client by forcing bad state:

  1. pass null for an expected object
  2. remove a nested property the component reads during render
  3. simulate a slow request followed by malformed data

If the page dies completely, you have a resilience problem. If the page dies and the action still works through the API, you have a security problem.

Replay requests from an untrusted account

Use a low-privilege test account and capture the request from the browser devtools or a proxy. Then replay it with different object IDs.

Look for:

  • no ownership check
  • no role check
  • no subscription check
  • no team membership check
  • no server-side validation of the target object

A table helps keep the review honest:

LayerWhat to testCommon miss
React UIrender failure, fallback stateno error boundary
APIreplayed requestauth only, no authorization
Data accessobject lookuptrusting client-provided ID

Fixes that hold up under pressure

Add boundary coverage and fallback states

Wrap risky sections of the UI in an error boundary and give users a safe fallback. Not every crash becomes a security issue, but every crash becomes a support issue.

Also test loading, empty, and malformed states. A component that only works with perfect data is not production-ready.

Enforce auth and object-level checks server-side

Authorization belongs in the backend route or service layer, not in the button component.

A better pattern looks like this:

routes/update-report.js
app.post("/api/reports/:id/publish", async (req, res) => {
if (!req.user) {
  return res.status(401).send("login required");
}

const report = await db.reports.findById(req.params.id);
if (!report) {
  return res.status(404).send("not found");
}

const allowed =
  report.ownerId === req.user.id ||
  req.user.role === "admin";

if (!allowed) {
  return res.status(403).send("forbidden");
}

report.published = true;
await report.save();

res.json({ ok: true });
});

That is the difference between “the UI usually blocks this” and “the server refuses this every time.”

A safer review checklist for shipped features

Before you call a feature done, check these items:

  • does the UI have an error boundary around risky render paths?
  • are fallback states tested with missing and malformed data?
  • can the action still be triggered from a captured request?
  • does the backend verify ownership or role for the exact object?
  • are tests using a low-privilege account, not an admin session?
  • does the server reject requests even if the client is broken?

If the answer to any of those is no, the feature is still depending on luck.

Conclusion

The real trap is not the missing boundary or the missing auth check by itself. It is the way they hide each other. A broken React tree makes the app look fragile, while a weak backend makes the feature actually unsafe.

If you test both layers separately, the issue becomes obvious fast. The client should fail cleanly. The server should fail closed.

Share this post

More posts

Comments