PL-026mediumCode

Any website can call a signed-in function

The endpoint tells browsers that any website may send your users' cookies to it and read the answer.

Severity
Medium
Who can exploit it
Any website your signed-in users visit
Found by
PatchLabs Code: CORS headers in functions and API routes
Lab proof
Not lab-proven: file and line in the report

What it is

Browsers stop a page on one site from reading responses from another site, unless that site opts in with CORS headers. Access-Control-Allow-Origin: * opts in for every site, but browsers refuse to send cookies with it, so on its own * only shares responses that need no sign-in, or that need a token the other site doesn't have.

The dangerous version is echoing back whatever origin asked together with Access-Control-Allow-Credentials: true, often written to get past the browser's error about * and credentials. Now any website your user visits can send requests to your endpoint with their cookies and read the reply: their profile, their orders, an API key. This bites apps that keep the session in cookies, such as Next.js apps using Supabase's server-side auth. PL-026 covers that, and * on endpoints that sign users in with cookies.

Supabase Edge Functions called with a bearer token from localStorage are a different case: another site can't read that token, so * there doesn't hand over your users' sessions. The report lists those as information.

Why AI builders produce it

CORS errors are among the errors most often pasted into an AI tool, and the quickest answer that works is "allow everything". When * plus credentials fails too, the next answer is to echo the request's origin.

Example of vulnerable code

Simplified from the patterns we see in AI-built apps. Synthetic: not taken from any real app.

app/api/me/route.ts (vulnerable)
// app/api/me/route.ts (Next.js, session kept in cookies)
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';

export async function GET(req: NextRequest) {
  const supabase = await createClient();  // reads the session cookie
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) return new NextResponse('Unauthorized', { status: 401 });
  const { data } = await supabase.from('profiles').select('*').eq('id', user.id).single();

  return NextResponse.json(data, {
    headers: {
      'Access-Control-Allow-Origin': req.headers.get('origin') ?? '*',  // any site
      'Access-Control-Allow-Credentials': 'true',
    },
  });
}

Check it yourself

check-pl-026.sh
# Where the app sets CORS headers
grep -rnE "Access-Control-Allow-(Origin|Credentials)" --exclude-dir=node_modules .

# Ask your endpoint as if you were another site
curl -s -i https://yourapp.example/api/me -H "Origin: https://attacker.example" | grep -i "access-control"
# If it echoes https://attacker.example with Allow-Credentials: true, any site can read it

The fix

app/api/me/route.ts (fixed)
const ALLOWED = new Set(['https://yourapp.example', 'https://www.yourapp.example']);

export async function GET(req: NextRequest) {
  // ...same as before...
  const origin = req.headers.get('origin');
  const headers: Record<string, string> = { Vary: 'Origin' };
  if (origin && ALLOWED.has(origin)) {
    headers['Access-Control-Allow-Origin'] = origin;
    headers['Access-Control-Allow-Credentials'] = 'true';
  }
  return NextResponse.json(data, { headers });
}

If only your own site calls the endpoint, from the same origin, you need no CORS headers at all: delete them. CORS controls who can read a response, not who can send a request, so endpoints that change data with cookie sign-in also need SameSite cookies or a CSRF token.

Code findings come with the file, the line and a one-line fix in the report, for your engineer to make. PatchLabs never pushes to your repo.

How PatchLabs finds it

code scan · report excerpt
[SCAN]    app/api/me/route.ts:12
          origin echoed + credentials LEAK
[FIX]     allow-list your own origins
synthetic exampleReport excerpt · read from the repo, not lab-proven

PatchLabs Code reads the CORS headers your functions and API routes set. It flags an echoed origin with credentials, and * where the endpoint signs users in with cookies; * on a bearer-token function is listed as information. It never sends requests to your app.

When the pattern is intended

A public API that needs no sign-in, such as a status endpoint or public data, can use * safely.

Why it’s rated medium

Medium: the victim has to be signed in and visit the attacker's page, and the endpoint has to use cookie sign-in. When that lines up, the other site reads or acts as them.

Guides

Free check

Does your app have PL-026?

Paste your public repo. PatchLabs Code reads it for this and every other code finding, and the Lab rebuilds your database to test who can read what.

Free for your first appReads your public repo onlyOr run the free static RLS check

Last reviewed 10 Nov 2026. Finding IDs are stable: link to them freely. Spotted a mistake? Tell us.