Lorem, ipsum dolor sit amet consectetur adipisicing elit. Qui, itaque voluptate ipsa non enim amet ducimus voluptatibus deserunt nam esse!
Vibe Coding's Blind Spots: A Security Audit of AI-Generated JavaScript

Vibe Coding's Blind Spots: A Security Audit of AI-Generated JavaScript

pr0h0
securityjavascriptai-generated-codecode-audit
AI Usage (89%)

Why AI-Generated JavaScript Needs a Security Pass

AI-generated JavaScript is usually fast, readable, and just wrong enough to be dangerous. It often works in a happy-path demo, which is why it gets merged. The trouble shows up later, when the app meets real users, real permissions, and real data.

I review generated code the same way I review rushed human code: assume the UI is lying until the backend proves otherwise. That catches most of the damage.

The main issue with vibe coding is not syntax. It is trust. Generated code tends to trust the browser, trust client state, trust request bodies, and trust defaults that look harmless until they become access control bugs.

What Vibe Coding Misses in Practice

Unsafe defaults and hidden trust boundaries

Generated code often picks the easiest path: localStorage for auth state, isAdmin flags in React state, or a frontend-only gate before a sensitive action. Those choices feel practical because they reduce friction.

They also create a fake boundary. If the server never re-checks the action, the client-side check is theater.

Client-side checks that never reach the server

A common failure pattern is “disable the button for free users.” That is not authorization. It is presentation.

If the API accepts the same request from any authenticated account, a free user can often replay the request directly. The UI never had authority in the first place.

Overbroad data access and accidental privilege leaks

Generated code also tends to fetch too much data and filter later in the browser. That is a quiet leak. Even if the UI hides a field, the response still contained it.

That matters when the payload includes billing info, internal IDs, drafts, or other users' records. Redaction belongs where the data is assembled, not where it is displayed.

A Practical Audit Workflow for Generated Code

Start with data flow and entry points

I start by tracing three things:

  1. Where data enters the app.
  2. Where it is stored.
  3. Where it is used to make a decision.

If a generated component reads from localStorage, query params, or an untrusted API response and then changes authorization, I treat that as a review hotspot.

💪

Draw the request path first. Most security mistakes are easier to spot as data-flow problems than as “buggy code.”

Trace authorization, not just validation

Validation answers “is this input shaped correctly?” Authorization answers “is this user allowed to do this?”

Generated code often does the first one and skips the second. A form might validate projectId, but the API still needs to confirm that the current user owns that project.

Look for dangerous browser APIs and dynamic execution

The obvious red flags are still red flags:

  • eval
  • new Function()
  • innerHTML
  • dangerouslySetInnerHTML
  • string-built script URLs

Sometimes the code is not obviously malicious. It just builds HTML from user content or assembles code from fragments. That is enough to create XSS or logic injection.

Small Code Examples That Fail Real Tests

Token storage and session handling mistakes

bad-session.js
// Looks convenient, but this is a weak pattern.
localStorage.setItem("token", token);

export function isLoggedIn() {
return Boolean(localStorage.getItem("token"));
}

This fails a basic threat model. Any script running in the page can read that token. The app also treats “token exists” as proof of identity, which is not the same thing as a verified session.

Use an HttpOnly session cookie when you can, and verify the session on the server for every protected route.

API calls that trust the UI too much

update-plan.js
async function upgradeUser(userId) {
if (!window.confirm("Upgrade this user?")) return;

await fetch("/api/admin/upgrade", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ userId }),
});
}

The confirm dialog is irrelevant. If the endpoint trusts the caller just because the button was hidden in the UI, any authenticated user may be able to hit it directly.

The fix is backend authorization: verify the role, the resource, and the action.

Unsafe use of eval, Function, or unescaped HTML

render-preview.js
function renderPreview(userInput) {
preview.innerHTML = userInput;
}

function runSnippet(code) {
return Function(`return (${code})`)();
}

Both lines are bad for different reasons. The first can execute injected HTML or script. The second turns input into executable code.

If you need preview rendering, sanitize with a real allowlist-based approach. If you need a calculator or rule engine, use a parser or a constrained interpreter, not raw JavaScript execution.

How to Reduce Risk Without Slowing Development

Add secure-by-default prompts and review checklists

The easiest defense is to make the model work inside a safer template. I ask for:

  • server-side authorization checks
  • HttpOnly cookies for sessions
  • no eval or Function
  • no innerHTML unless sanitized
  • explicit ownership checks for every resource mutation

That prompt alone removes a lot of bad defaults.

Use linting, dependency checks, and test cases

Static checks help because generated code repeats patterns. Add rules that catch risky APIs and force review for escaped HTML or insecure storage choices.

Then write tests for the security contract, not just the UI contract:

  • free user cannot call admin endpoint
  • user cannot read another user's record by changing an ID
  • session expires correctly
  • HTML preview does not execute scripts

Keep sensitive logic on the backend

This is the part people skip when they want speed. But the backend is where trust belongs.

If a feature affects permissions, billing, ownership, or secrets, the frontend should only request it. The backend should decide it. That separation keeps generated code useful without letting it define your security model.

Conclusion

AI-generated JavaScript is fine for scaffolding, UI glue, and repetitive work. It becomes risky when it starts making trust decisions.

My rule is simple: if the code decides who may do what, or what data they may see, I audit it like production security code. The fast path is still useful. It just needs a security pass before it becomes real.

Share this post

More posts

Comments