Configuration and hardening flaws in agent-written code

A setting is not code the tests check. It is the one line an agent widens to make an error disappear, and the boundary it removed rarely gets put back.

Tristan Roth,

In plain words

Picture an office that locks every door at night, except the one a new hire propped open with a chair so deliveries would stop setting off the alarm. Nobody broke the lock.

Someone left a door unlocked to solve a smaller problem, and now the lock does not matter.

Configuration and hardening flaws are that propped door: nothing in the application logic is wrong, a setting has just been loosened until the feature works again, and loosened is where it stays.

The standard defence is a narrow default: a server rejects requests from other origins, an error page shows a code and not a stack trace, a cookie only leaves the browser over HTTPS. Most frameworks already choose that default.

The interesting question is where an agent widens it because the default stood in its way during a session.

Why agents write it: the error disappears, and so does the boundary

A frontend calling a new API route gets blocked by the browser's cross-origin check, or a client component throws because it tried to read a server-only environment variable, or a login redirect loop shows a generic 500.

Each of those is a boundary doing its job, and each one also looks, from inside a coding session, like a bug to make go away.

The fastest fix an agent can verify in the same session is to widen the setting that is blocking it: allow any origin, expose the variable to the browser, turn on verbose errors to see what is actually failing.

The test suite rarely notices, because the test that would notice is about the setting, not the feature.

A reflected CORS origin still lets the real request through, a public environment variable still holds the right value for the demo, and a stack trace is genuinely useful while debugging.

The diff that ships is the fix, with the guard still off, because turning the guard back on was the step nobody got back to.

The shapes it takes in a pull request

  • A wildcard or reflected CORS origin

    Access-Control-Allow-Origin set to * or read back from the request's own Origin header, added to stop a browser error, which can let another site read the response for a signed-in user.

  • A server secret exposed to the browser

    A value moved behind the framework's public-variable prefix, or passed as a prop from a server component into a client component, so a service key or signing secret ships inside the page's JavaScript bundle.

  • Verbose errors left on past the fix

    A debug or stack-trace flag turned on to see why a route was failing, still true in the diff that ships the actual fix.

  • A cookie or header missing its flag

    A session cookie set without Secure, HttpOnly or SameSite, or a security header dropped, usually while working around a local-only redirect or iframe problem.

  • A debug or seed route left reachable

    A seed script's default admin password, or a debug endpoint added to inspect data while building the feature, still mounted in the diff that ships because it only mattered in the environment where it was built.

Worked example, an illustration

An export route that reflects the browser's Origin header.

The request: let the dashboard, served from a staging subdomain, call this export endpoint without the browser blocking the response. The quickest diff that made the CORS error go away was to read the browser's own Origin header back as the allowed origin, with credentials turned on.

src/app/api/account/export/route.ts+7 -2
export async function GET(req: Request) {  const data = await getExport(req);  return new Response(JSON.stringify(data));  const origin = req.headers.get("origin") ?? "*";  const data = await getExport(req);  return new Response(JSON.stringify(data), {    headers: {      "Access-Control-Allow-Origin": origin,      "Access-Control-Allow-Credentials": "true",    },  });}
Illustration written for this page, not from a customer repository. The export endpoint reads the caller's own session cookie to decide whose data to return. Reflecting the Origin header back while allowing credentials removes the one check a browser would otherwise enforce on that combination. If the session cookie is not marked SameSite=Lax or Strict, a script on any other site the signed-in user has open can fetch this endpoint with credentials and read their export: a cookie left SameSite=None, the shape above, is exactly what makes that reachable.

What an Aevral finding on it looks like

On a reviewed pull request, a finding is an inline comment pinned to the added line, next to an advisory Check that never blocks the pull request. This is an illustration of that shape for the diff above.

AevralIllustration

src/app/api/account/export/route.ts, added line: `"Access-Control-Allow-Origin": origin,` beside `Access-Control-Allow-Credentials: true`

The response reflects the request's own Origin header back as the allowed origin while also allowing credentials. A browser treats that combination as a green light for any site, so a signed-in user's export can be read from a page they never meant to share it with.

Missing control: A fixed allowlist of the dashboard's real origins, checked before the header is set, with credentials allowed only for an origin on that list.

Fix with your agent
In src/app/api/account/export/route.ts, the added CORS headers reflect req.headers.get("origin") and allow credentials for any origin. Replace the reflected value with a check against a fixed list of allowed dashboard origins, including the staging subdomain, return no Access-Control-Allow-Origin header when the origin is not on the list, and add a test that an unlisted origin receives no CORS headers.

Check this finding against the code before changing anything. Make the smallest change that restores the control, add a test for it, and stop before commit or push.

A finding is a lead with evidence, not a confirmation. You or your agent check it against the code; Aevral never applies or merges a change. See handing a finding to your coding agent.

How to fix it

The fix is not removing CORS, since the dashboard on another subdomain still needs it. It is naming the origins allowed to use credentials, instead of trusting whatever the browser sent.

A short allowlist checked against the incoming Origin header gives the real dashboard its access back and leaves every other site with the browser's default boundary.

A Vary: Origin header on the response tells any cache in front of the route that the allowed-origin header changes per caller, so a cache never serves one origin's CORS headers to another.

src/app/api/account/export/route.ts (safe version)
const ALLOWED_ORIGINS = new Set([
  "https://app.example.com",
  "https://staging-dashboard.example.com",
]);

export async function GET(req: Request) {
  const origin = req.headers.get("origin");
  const data = await getExport(req);
  const headers: Record<string, string> = { Vary: "Origin" };
  if (origin && ALLOWED_ORIGINS.has(origin)) {
    headers["Access-Control-Allow-Origin"] = origin;
    headers["Access-Control-Allow-Credentials"] = "true";
  }
  return new Response(JSON.stringify(data), { headers });
}

What this page does not claim

  • Aevral's PR security review, live on install, looks for configuration and hardening flaws on the diff of the pull requests it reviews, as one class among several. Looking for a class does not mean finding each instance of it: a review can miss one, and a quiet review is not proof that the code is clean.
  • The whole-repo scan reads authorization, IDOR, and business-logic access control only. It does not read configuration and hardening flaws; on this class, the pull request is where Aevral looks.
  • No class-specific performance evidence for configuration and hardening flaws is published. The receipts page reports named runs of the whole PR review eval suite, not a result for this class.
  • The diff, the finding and the fix on this page are illustrations written for this page. They are not output from a customer repository.
  • This page covers a response's own CORS, environment-variable and header configuration, read from the diff. Cloud infrastructure settings, such as a storage bucket's public access or a database's network rules, are configuration too, but they are outside what a pull request diff shows.

Sources

OWASP Top 10 2021, A05: Security Misconfiguration; CWE-942: Permissive Cross-domain Policy with Untrusted Domains; CWE-209: Generation of Error Message Containing Sensitive Information; CWE-1004: Sensitive Cookie Without 'HttpOnly' Flag; Next.js documentation: Environment Variables (bundling public variables into the browser).

Read next

Reviewing a pull request for access control; Token and session flaws in agent-written code; PR security review.

Other classes


Security review, handled.

One GitHub App. Reviews start when the App is installed. A Check on each pull request it reviews, with inline comments when there is a grounded finding. Free tier live: public repos free, 500/org/month, 25 private reviews a month. Paid plans are live in the console.

Install the GitHub AppLog inSign up

For professional use. By installing, you confirm you can act for the account or organization that owns it, and you accept the Terms and DPA on its behalf.