{"slug": "authenticated-isnt-authorized-the-ai-code-review-bug-that-looks-secure", "title": "Authenticated Isn’t Authorized: The AI Code Review Bug That Looks Secure", "summary": "An engineer warns that AI-generated code often implements authentication but misses authorization, leading to Broken Object Level Authorization (BOLA) vulnerabilities. The developer demonstrates how an endpoint that checks only that a user is logged in can expose another user's data, and advises reviewers to trace permissions to the specific resource. The post also notes that AI-generated tests may pass while failing to catch such flaws.", "body_md": "One of the more dangerous mistakes in AI-generated code is also one of the easiest to miss in review. The code has authentication; the user is logged in, and there is an authentication check somewhere in the request path. Everything looks right, but *the endpoint still lets one user access another user's data*.\n\nThe problem is simple:\n\n**Authentication tells you who the user is; Authorization tells you whether that user is allowed to access this specific resource.**\n\nAI-generated code often gets the first part right and quietly skips the second.\n\nImagine an endpoint like this:\n\n```\ndef show\n  return head :unauthorized unless current_user\n\n  @invoice = Invoice.find(params[:id])\n  render json: @invoice\nend\n```\n\nAt first glance, this doesn't look reckless.\n\nBut the important question is missing: **Does this invoice belong to the current user?**\n\nIf I am logged in as user 42 and request:\n\n```\n/invoices/123\n```\n\nThe code checks only that I am authenticated: it doesn't check whether invoice 123 is mine.\n\nIf changing `123`\n\nto `124`\n\ngives me somebody else's invoice, authentication has done nothing to protect that data.\n\nThis is the class of problem commonly described as **BOLA** *(Broken Object Level Authorization)* or, in older terminology, **IDOR**: object-level access control is missing even though authentication exists.\n\nAI coding tools are often very good at reproducing familiar application patterns. A controller action often looks like:\n\n```\n@invoice = Invoice.find(params[:id])\n```\n\nAn authentication check often looks like:\n\n```\nreturn head :unauthorized unless current_user\n```\n\nBoth are individually plausible. Put them together and the code *looks* secure.\n\nThat is the problem.\n\nThe generated code has matched two common patterns without necessarily reasoning about the relationship between the authenticated user and the object being loaded.\n\nThe failure is not:\n\n“There is no security.”\n\nThe failure is:\n\n“Security exists, but it is not being applied to this resource.”\n\nThat is much harder to catch casually.\n\nThe exact implementation depends on the application, but the data access should normally be constrained by the user's permitted scope.\n\nFor example:\n\n```\ndef show\n  @invoice = current_user.invoices.find(params[:id])\n  render json: @invoice\nend\n```\n\nNow the lookup itself enforces the ownership boundary.\n\nA request for somebody else's invoice doesn't merely fail an `if`\n\nstatement later. The resource is outside the queryable scope in the first place. In a more complex system, that boundary may come from a policy object, tenant scope, permission service, or domain rule instead.\n\nThe implementation is less important than the review question:\n\n**Can I trace the current user's permission all the way to the specific object being accessed?**\n\nIf I can't, I treat it as a security finding that needs verification.\n\nThis kind of bug is especially easy to miss when the generated tests were written by the same AI that generated the implementation. You often get tests like:\n\n```\nit \"returns an invoice for an authenticated user\" do\n  sign_in(user)\n\n  get \"/invoices/#{invoice.id}\"\n\n  expect(response).to have_http_status(:ok)\nend\n```\n\nThat test passes. It also proves almost nothing about authorization. The test you actually need is closer to:\n\n```\nit \"does not allow a user to access another user's invoice\" do\n  sign_in(user)\n\n  get \"/invoices/#{other_users_invoice.id}\"\n\n  expect(response).to have_http_status(:not_found)\nend\n```\n\nor whatever denial behavior your application uses.\n\nThis is one reason I don't treat a green test suite as proof that AI-generated code is safe. A test suite can faithfully confirm the same misunderstanding that produced the code.\n\nNegative testing is a separate topic, and probably worth its own article.\n\nWhen I review security-sensitive AI-generated code, I don't ask:\n\n“Is there authentication?”\n\nI ask:\n\n“Is authorization enforced for this exact operation on this exact resource?”\n\nThat wording matters. It forces the review away from the comforting presence of middleware, `current_user`\n\n, role checks, and authentication helpers.\n\nYou **have** to follow the access path.\n\nFor an object lookup, that means tracing from the request down to the data access:\n\n```\nrequest\n  ↓\nauthenticated user\n  ↓\nauthorization decision\n  ↓\nspecific resource\n  ↓\ndata access\n```\n\nIf the chain contains a jump like:\n\n```\nauthenticated user\n  ↓\nModel.find(params[:id])\n```\n\nI want to know exactly what prevents cross-user or cross-tenant access. “Auth is handled elsewhere” isn't an answer until you verify **where** and **how**.\n\nOne of the prompts I use in the Security Deep-Dive is deliberately explicit about this failure mode:\n\n```\nReview this code for missing or incorrect authorization checks.\n\nFor every resource accessed by ID or other user-controlled identifier:\n\n- identify who is allowed to access it\n- verify that authorization is checked for the specific resource\n- do not treat authentication alone as authorization\n- flag any lookup where an authenticated user could substitute another object's ID\n\nFor each finding, include:\n- severity\n- file and line number\n- exploit path\n- minimal fix\n\nIf authorization depends on middleware, policies, framework defaults, or another layer, state that assumption and mark it as needing verification rather than inventing a vulnerability.\n```\n\nThe last sentence is important: security review prompts can become useless if you tell the model to be “aggressive” and it responds by inventing vulnerabilities that depend on imaginary configuration. I want it to challenge the code, but I also want findings grounded in what is actually there.\n\nFor security-sensitive changes, I often do a second pass from the opposite direction.\n\nInstead of:\n\n“Review this code for security problems.”\n\nask:\n\n“Assume you are a malicious but valid user of this system.”\n\nThat user already has credentials. That removes the easy answers.\n\nThe question becomes:\n\nA surprising number of access-control mistakes only become obvious once you stop imagining an anonymous attacker and start imagining a perfectly legitimate user who is curious about what happens when they change a parameter.\n\nI treat this pattern as especially important in changes involving:\n\nThose are the places where “logged in” and “allowed to do this” most obviously diverge.\n\nAI-generated security bugs aren't always dramatic. They don't necessarily contain `eval()`\n\nor expose a secret in plaintext. Sometimes the code uses the correct framework, the correct authentication system, the correct ORM, and the correct controller structure. It just misses one relationship.\n\n**This user. This operation. This resource.**\n\nThat is enough.\n\nAuthentication answers:\n\nWho are you?\n\nAuthorization answers:\n\nAre you allowed to do\n\nthistothat?\n\nDon't let an AI-generated authentication check convince you that the second question has been answered.\n\nThis is one of the checks in **Round 2: Security Deep-Dive** of *The AI Code Review Protocol*.\n\nIf this way of reviewing code is useful, my free repo contains the free prompts, and the book explains the full four-round method and why each round exists.\n\nThe free prompt library and automated review skill are available in the [GitHub repository](https://github.com/Raithlin/ai-code-review-protocol).\n\nIf you want the reasoning behind the full four-round process, the guide is available on [Selar](https://selar.com/ai-code-review-protocol) and [Amazon Kindle](https://www.amazon.com/dp/B0H84XDQDW).", "url": "https://wpnews.pro/news/authenticated-isnt-authorized-the-ai-code-review-bug-that-looks-secure", "canonical_source": "https://dev.to/raithlin/authenticated-isnt-authorized-the-ai-code-review-bug-that-looks-secure-507m", "published_at": "2026-08-25 21:05:45+00:00", "updated_at": "2026-08-25 21:44:21.576486+00:00", "lang": "en", "topics": ["ai-safety", "ai-tools", "developer-tools"], "entities": [], "alternates": {"html": "https://wpnews.pro/news/authenticated-isnt-authorized-the-ai-code-review-bug-that-looks-secure", "markdown": "https://wpnews.pro/news/authenticated-isnt-authorized-the-ai-code-review-bug-that-looks-secure.md", "text": "https://wpnews.pro/news/authenticated-isnt-authorized-the-ai-code-review-bug-that-looks-secure.txt", "jsonld": "https://wpnews.pro/news/authenticated-isnt-authorized-the-ai-code-review-bug-that-looks-secure.jsonld"}}