Last reviewed: 2026-10-29 (Asia/Shanghai)

Direct answer

Prevent CORS data leaks in coding-agent API patches by defining which browser origins may read each response before enabling cross-origin credentials. For an authenticated endpoint, match a small, configuration-controlled set of complete origins, return the matched origin and Access-Control-Allow-Credentials: true only when sharing is intended, and test the result from both trusted and untrusted browser pages. Keep authentication and authorization on the actual request.

The dangerous patch often looks like a connectivity fix: reflect any incoming Origin and enable credentials so a frontend stops reporting a CORS error. If the browser sends a valid session cookie, that combination can let an attacker-controlled page read the user’s API response. A successful authenticated GET and a failing OPTIONS test do not establish the boundary: some GET requests need no preflight.

The WHATWG Fetch Standard defines the credential-sharing checks. A request with credentials mode include needs an exact matching Access-Control-Allow-Origin value and the case-sensitive value true for Access-Control-Allow-Credentials. Access-Control-Allow-Origin: * does not satisfy that check, even when no cookie happens to exist.

Who this is for

Use this workflow when a coding agent changes API middleware, a reverse proxy, browser fetch options, preview-environment access, or any Access-Control-* response header. It also applies when a patch moves a frontend onto a different scheme, host, or port.

The running example has an authenticated API on api.example.test over HTTPS and two approved browser frontends. Only those frontends may read account responses cross-origin. Anonymous access still fails authentication. The example policy, Express fragment, and test matrix are proposed review guidance; no deployed application or browser test result is claimed.

Key takeaways

  • Treat an allowed origin as permission for browser scripts at that origin to read a response, not as a user identity.
  • Compare complete origins. A substring or loose hostname suffix can admit an unrelated site.
  • Review preflight and actual responses together; a request can reach the server without preflight.
  • In Express’s CORS middleware, origin: true reflects the request origin. It is unsuitable as an unrestricted credentialed policy for this example.
  • Verify cookie delivery in the positive control so browser privacy settings do not hide a permissive server policy.
  • Test the headers after proxies and caches have handled them, including responses that fail authentication.

Sources checked

  • WHATWG: Fetch Standard — fetched successfully on 2026-10-28 UTC. Sections 3.3.3–3.3.5 define response headers and credentialed sharing; Sections 4.8–4.10 define preflight, its cache, and the CORS check. Its HTTP representation reported an October 19 modification time.
  • MDN: Cross-Origin Resource Sharing — fetched successfully on 2026-10-28 UTC. Supports non-preflighted requests, credentialed fetches, wildcard restrictions, cookie-policy limits, and Vary: Origin for origin-dependent responses.
  • Express: cors middleware — fetched successfully at this canonical URL on 2026-10-28 UTC. Documents origin callbacks, origin reflection, credential and header options, application-level preflight handling, and the difference between response headers and server access control.

These are maintained primary technical references for an evergreen code-review task. A page’s HTTP modification time does not establish a new security announcement. The browser and middleware behavior was rechecked for this article; the acceptance policy below remains an application design choice.

Contract details to verify

Specify response access per route

Write a policy table before asking the agent to alter headers:

Contract elementExample decision
Protected resourceAccount summary, authenticated and authorized on every actual request
Approved originsHTTPS, port 443, with hosts app.example.test and admin.example.test
Credential modeBrowser fetch uses include; cookie delivery follows browser rules
Methods and request headersGET; explicitly permit X-Client-Version when used
Exposed response headersOnly X-Request-ID, if the frontend needs it
Missing or opaque originNo cross-origin sharing grant; normal authentication still applies
Personalized storageCache-Control: no-store for this example, plus verified managed-cache bypass
Policy failureNo permissive fallback or reflected origin

An origin consists of scheme, host, and port. Configure browser-serialized origins without a path or trailing slash. Review production, staging, and local-development lists independently. An HTTP origin, an alternate port, and a preview hostname are separate entries that need their own reason for access.

For this policy, reject the literal origin value null from the sharing list. It cannot identify either approved frontend. Treat an absent Origin separately: command-line clients and same-origin flows can legitimately omit it. Neither absence nor a matching value proves authentication, and a non-browser client can construct its own Origin header.

Avoid includes('example.test'), suffix checks without a hostname boundary, and broad expressions introduced to admit all previews. A hostname such as app.example.test.attacker.invalid must not match the application entry. Even a correctly matched subdomain must be controlled by a party entitled to read the response; the domain suffix alone does not establish that trust.

Keep credential and preflight rules consistent

MDN’s CORS guide shows a credentialed GET that is not preflighted. An untrusted page can therefore cause an actual request to arrive even when an OPTIONS policy would deny it. CORS determines whether the browser exposes the result to that page; it does not remove the need to protect the handler.

Under Fetch’s rules, cross-origin preflight excludes credentials. Let a recognized preflight obtain the route’s sharing policy without demanding a session cookie on OPTIONS. Authenticate and authorize the subsequent actual request. Preflight approval must never create an authenticated principal, mutate an account, or bypass the handler’s checks. Enterprise client-certificate behavior has browser exceptions documented by MDN; deployments using that mechanism need separate compatibility tests.

For credentialed sharing, list allowed methods, request headers, and exposed response headers explicitly. Do not expect * to act as a wildcard in those fields for credentials mode include. Return one allowed origin, not a comma-separated list. Keep the same origin decision on the actual response, including an intended readable authentication error from a trusted frontend.

Cookie attributes and browser privacy settings remain independent constraints. credentials: 'include' does not override SameSite or third-party-cookie restrictions. If the allowed frontend receives an anonymous response because its cookie was withheld, the harness has not proved that protected response sharing works.

Review what the middleware actually does

The Express CORS documentation states that the package sets response headers. It does not block ordinary requests. Its origin callback receives the origin string or undefined; origin: true reflects the request origin, while origin: false disables CORS headers for that decision.

This integration fragment keeps an exact allowlist for the example account router. It assumes the application already supplies the named router, authentication, authorization, and trusted configuration components. config.accountReaderOrigins contains the two complete, browser-serialized HTTPS origins described in the policy table. Validate that configuration at startup; never populate it from caller-supplied values:

const accountOrigins = new Set(config.accountReaderOrigins);

app.use('/api/account', cors({
  origin(origin, done) {
    done(null, typeof origin === 'string' && accountOrigins.has(origin));
  },
  credentials: true,
  methods: ['GET'],
  allowedHeaders: ['X-Client-Version'],
  exposedHeaders: ['X-Request-ID'],
  maxAge: 60,
}));

app.use('/api/account', (req, res, next) => {
  res.set('Cache-Control', 'no-store');
  next();
});
app.use('/api/account', requireSession, authorizeAccount, accountRouter);

An unlisted origin produces no sharing grant, but the actual request can still reach authentication and the router. That is intentional in this fragment. If the application also requires rejection of particular requests at the server, implement and test that as a separate request policy. For state-changing cookie-authenticated routes, follow the site’s CSRF review workflow .

The 60-second preflight lifetime is illustrative. Choose a value compatible with the service’s policy-change process. A browser can reuse a prior successful preflight, so every actual request must retain authentication, authorization, and the current response-sharing decision.

For dynamic origin responses, verify that Vary includes Origin without discarding existing fields. Keep personalized bodies out of shared caches under this example policy. Origin variation distinguishes response variants; it does not isolate users. Use the site’s shared-cache isolation guide for that separate check.

Happy-path operator workflow

  1. Freeze the agent revision and inventory every CORS writer: application middleware, route handlers, ingress, CDN, and error handlers.
  2. Record the route policy, complete approved origins, credential mode, and supported browser versions.
  3. Review the effective middleware configuration. Remove unconditional reflection from the example’s protected routes and handle policy-loading failures without widening access.
  4. Serve a small test page from an approved HTTPS origin. Sign in with a disposable account containing a harmless, user-specific response marker.
  5. Fetch the account URL with credentials: 'include'. Assert that JavaScript reads that marker, the intended custom response header is exposed, and the server recognizes the fixture user.
  6. Add X-Client-Version to exercise preflight. Verify the OPTIONS exchange and the subsequent authenticated GET separately.
  7. Run the negative matrix from a separately served untrusted page, through the same proxy and middleware stack.
  8. Retain the revision, policy version, browser configuration, request classes, sanitized headers, and response-access assertions.

Use a real browser for the JavaScript access assertions. An HTTP client can inspect headers and reachability, but it does not enforce the browser’s CORS checks. Keep web security enabled in the test browser.

Prove response isolation with a browser matrix

Use synthetic data and origins you control. These are proposed integration assertions, not reported test outcomes.

FixtureRequired observation
Approved origin, valid session, GET without custom headersJavaScript reads the fixture user’s account marker; server confirms authenticated delivery
Approved origin, valid session, X-Client-Version headerPreflight succeeds without session authentication; actual GET still requires it
Untrusted origin, valid session, GET without custom headersRequest may arrive, but JavaScript cannot read the protected body
Untrusted origin requesting the custom headerNo successful sharing grant; in a fresh context, failed preflight prevents the actual fetch
Wrong scheme, alternate port, and hostname-suffix lookalikeEach is denied cross-origin response access
Origin: null or no Origin in an HTTP probeNo sharing grant under the example policy; authentication is still enforced
Approved origin without a sessionProtected data is absent; the documented authentication error may be readable
Both approved origins in alternating orderEach receives only its matching origin value; no duplicate or comma-joined allow-origin header
An old successful preflight followed by policy removalA subsequent actual response has no obsolete sharing grant; authorization remains enforced
Proxy-generated error or authentication failureNo broad reflection appears; trusted-client error visibility follows the policy

Run browser-origin cases by navigating to pages served at those origins. Do not simulate a browser security test by injecting an arbitrary Origin header into a Node request. Use raw HTTP probes for malformed or absent-header handling, and keep their results distinct from browser enforcement evidence.

For the decisive negative case, serve a controlled, unlisted sibling such as untrusted.example.test over HTTPS. It is cross-origin with the API while remaining same-site, helping exercise response isolation without a third-party-cookie block masking the result. Add different-site cases for the browser behavior the product supports.

For each untrusted-origin case, confirm the test did not merely lose its session. Establish a successful authenticated positive control, then use server-side fixture identifiers and sanitized request records to determine whether the cookie-bearing request arrived. The important assertion is that the hostile page cannot read protected content even when authentication succeeds. Never log the cookie itself.

Test both fresh browser contexts and contexts with previously successful preflight entries. The Fetch Standard gives that cache its own origin, URL, method, header, credential, and network-partition matching rules. Avoid assuming that clearing an ordinary HTTP response cache establishes a fresh preflight state; verify the expected OPTIONS exchange in the harness.

Error-path operator workflow

If an untrusted page reads the marker, block the patch and preserve the smallest synthetic reproduction tied to its exact revision. Restore the last reviewed sharing policy or disable cross-origin sharing on the affected protected route while investigating. Retest after every intermediary has the corrected configuration.

If a legitimate frontend fails, inspect the browser console and network trace alongside server logs. Separate a missing cookie, failed authentication, denied preflight, mismatched allow-origin value, and a proxy-added duplicate header. A generic fetch rejection does not identify which layer failed. Do not repair it with unconditional origin reflection.

If the origin list cannot be loaded, return a controlled error or omit the sharing grant according to the application contract. Do not default to allowing the caller’s origin. Verify the error handler and ingress do not add permissive headers after the application has withheld them.

After removing an origin, account for prior preflight permissions and cached responses. Keep enforcing the new policy on actual responses and preserve account authorization. A configuration change cannot recall information a previously trusted page already read.

Failure modes

A green curl test is accepted as browser proof. Keep it for header inspection; add JavaScript response-access assertions from separate origins.

Only OPTIONS is restricted. A non-preflighted GET can still arrive. Evaluate actual response sharing and protect every actual handler.

A wildcard error becomes an origin-reflection patch. * failing with credentials does not justify trusting every supplied origin. Match the configured set.

The frontend reads nothing because no cookie was sent. That is an inconclusive isolation test. Prove the positive credential path and inspect the authenticated server identity.

Two layers both own CORS headers. Duplicate or contradictory headers can break valid clients. Assign ownership and inspect the final browser-visible response.

FAQ

Does an allowed origin authorize a user?

No. It grants browser response access to scripts from that origin. The actual request still needs a valid credential and resource-level authorization.

Should every OPTIONS request require login?

A cross-origin preflight normally has no session credentials. Return the narrowly defined route policy without treating preflight as user authentication; require authentication on the subsequent protected request.

Can public endpoints use a wildcard?

Yes, when their responses are deliberately public and the client uses a compatible credential mode. Keep that policy separate from the account route, and verify a broad middleware default cannot reach protected responses.

Does withholding CORS headers prevent a mutation?

No. Some requests are sent before response access is checked. Use authentication, authorization, and the appropriate CSRF protections for state changes, then assert side effects separately.

Reader next step

Choose one CORS-affecting coding-agent patch. Write its route-specific origin and credential policy, prove an approved page can read a disposable user’s account response, then fetch the same URL from an untrusted page without a custom header. Keep the patch blocked until the browser denies that page response access, authentication still protects the endpoint, and the final headers agree across the application and proxy stack.