Skip to content

fix(cors): reject wildcard origin with credentials#366

Merged
Junman140 merged 2 commits into
Pi-Defi-world:devfrom
nice-bills:fix/cors-wildcard-with-credentials-315
May 29, 2026
Merged

fix(cors): reject wildcard origin with credentials#366
Junman140 merged 2 commits into
Pi-Defi-world:devfrom
nice-bills:fix/cors-wildcard-with-credentials-315

Conversation

@nice-bills

@nice-bills nice-bills commented May 28, 2026

Copy link
Copy Markdown
Contributor

Closes #315

Browsers block Access-Control-Allow-Origin: * when credentials: true. This change rejects * in CORS_ORIGIN at startup, uses an explicit allowlist, and reflects
the request origin on allowed cross-origin calls. Adds tests for parsing, validation, and response headers.

Summary by CodeRabbit

  • New Features

    • Enhanced CORS handling: strict origin validation (no wildcard, no paths), credential-safe explicit origins, dev default localhost:3000, prod requires explicit config.
  • Refactor

    • CORS behavior split into reusable origin-resolution and middleware builders for clearer, modular handling.
  • Documentation

    • .env example updated with commented CORS guidance.
  • Tests

    • Added comprehensive tests for parsing, resolution, and middleware behavior.

Review Change Stack

Copilot AI review requested due to automatic review settings May 28, 2026 19:06
@drips-wave

drips-wave Bot commented May 28, 2026

Copy link
Copy Markdown

@nice-bills Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

A CORS security hardening that validates and enforces explicit origin allowlists, rejecting wildcard origins and requiring URL normalization. Environment configuration, parsing utilities, middleware refactoring, and comprehensive test coverage implement credential-compatible CORS.

Changes

CORS Wildcard Rejection and Explicit Allowlist

Layer / File(s) Summary
Environment Documentation
.env.example
CORS configuration format is documented with the development localhost default and commentary that wildcard origins are not supported.
CORS Origin Parsing and Validation
src/config/corsOrigins.ts
New DEFAULT_DEV_CORS_ORIGIN constant and parseCorsOrigins(raw, nodeEnv) function parse comma-separated origins from environment, enforce production requirements, reject wildcard *, validate URL structure (http/https only, no credentials/path/query/hash), normalize to url.origin, and deduplicate results.
Configuration Integration
src/config/env.ts
config.corsOrigin now uses parseCorsOrigins() instead of raw comma-split, with updated documentation that explicit origins are required and wildcard is rejected.
Middleware Refactoring and Integration
src/middleware/cors.ts
Origin-resolution logic extracted into resolveCorsOrigin(requestOrigin, allowedOrigins) helper; shared CORS options moved to corsOptionsBase; createCorsMiddleware(allowedOrigins) wires origin callback; exported corsMiddleware created via factory.
Test Coverage
tests/cors.test.ts, tests/env.test.ts
Unit tests for parsing (dev default, production requirement, wildcard rejection, validation, deduplication), origin resolution (absent/allowed/denied), and middleware behavior (allow/deny/preflight); env test verifies wildcard rejection throws.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Pi-Defi-world/acbu-backend#234: Earlier CORS wildcard origin refactoring that updated config fallback behavior and rejection logic in the same files.

Poem

🐰 A wildcard at the CORS gate said "try your luck",
But creds need care — no star, no muck.
Origins trimmed, normalized, and neat,
Deduped rows make the allowlist complete. 🌱

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(cors): reject wildcard origin with credentials' accurately summarizes the main change: preventing wildcard CORS origins when credentials are enabled.
Linked Issues check ✅ Passed The PR fully addresses issue #315 by implementing origin parsing with wildcard rejection, explicit allowlist enforcement, and request Origin reflection for allowed origins.
Out of Scope Changes check ✅ Passed All changes are directly scoped to addressing the CORS wildcard + credentials issue: env validation, parsing logic, middleware refactoring, and comprehensive test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Hardens CORS configuration by replacing the permissive origin handling with a validated allowlist that rejects wildcards (incompatible with credentials) and requires explicit origins in production.

Changes:

  • Adds parseCorsOrigins to validate, normalize, and deduplicate CORS_ORIGIN, with environment-specific defaults and wildcard rejection.
  • Refactors CORS middleware into createCorsMiddleware + resolveCorsOrigin helpers that reflect exact allowed origins.
  • Adds unit/integration tests and updates .env.example documentation.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/config/corsOrigins.ts New parser with validation, normalization, and production safeguards.
src/config/env.ts Wires parseCorsOrigins into config and removes unsafe split.
src/middleware/cors.ts Splits middleware into testable helpers; reflects allowed origin.
tests/cors.test.ts New tests for parser, resolver, and middleware behavior.
tests/env.test.ts Adds wildcard rejection test for CORS_ORIGIN.
.env.example Documents new CORS_ORIGIN format and defaults.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/config/corsOrigins.ts Outdated
`Invalid CORS origin "${origin}". Only http:// and https:// origins are supported.`,
);
}
if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
Comment thread tests/cors.test.ts Outdated
Comment on lines +85 to +88
await request(buildApp([allowedOrigin]))
.get("/ping")
.set("Origin", "https://evil.example.com")
.expect(500);
Comment thread src/config/env.ts Outdated
// CORS
corsOrigin: process.env.CORS_ORIGIN?.split(",") || [],
// CORS — explicit origins only; wildcard * is rejected (incompatible with credentials)
corsOrigin: parseCorsOrigins(process.env.CORS_ORIGIN, env.NODE_ENV),

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/config/corsOrigins.ts`:
- Around line 11-18: The NODE_ENV check is case-sensitive and can misclassify
production; update the logic around nodeEnv (used with trimmed and
DEFAULT_DEV_CORS_ORIGIN) to normalize nodeEnv (e.g., nodeEnv?.toLowerCase()) or
perform a case-insensitive comparison before deciding to throw the production
Error — this ensures values like "Production" are treated as production and will
not fall back to DEFAULT_DEV_CORS_ORIGIN.

In `@tests/env.test.ts`:
- Around line 50-53: The test currently calls require("../src/config/env")
inside it (the require call in tests/env.test.ts) which violates
`@typescript-eslint/no-require-imports`; add a localized eslint suppression
directly above that require (use an inline // eslint-disable-next-line
`@typescript-eslint/no-require-imports`) so only that line is exempt, keeping the
rest of the file lint-clean and preserving the dynamic require behavior used in
the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 51435035-78ba-4fd0-b587-fe4574f574dc

📥 Commits

Reviewing files that changed from the base of the PR and between df93aea and 3596581.

📒 Files selected for processing (6)
  • .env.example
  • src/config/corsOrigins.ts
  • src/config/env.ts
  • src/middleware/cors.ts
  • tests/cors.test.ts
  • tests/env.test.ts

Comment thread src/config/corsOrigins.ts
Comment thread tests/env.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/cors.test.ts`:
- Around line 90-97: The test "rejects disallowed cross-origin requests"
currently only checks that CORS headers are missing but doesn't assert the
response status; update the assertion so the request to
buildApp([allowedOrigin]).get("/ping").set("Origin", "https://evil.example.com")
also verifies a 200 status (e.g., add .expect(200) to the request chain or
assert res.status === 200) while keeping the existing checks for missing
access-control headers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d314c8f2-1900-4cc4-b848-8d9ac27397b6

📥 Commits

Reviewing files that changed from the base of the PR and between 3596581 and 8d0212d.

📒 Files selected for processing (5)
  • src/config/corsOrigins.ts
  • src/config/env.ts
  • src/middleware/cors.ts
  • tests/cors.test.ts
  • tests/env.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/env.test.ts
  • src/middleware/cors.ts
  • src/config/corsOrigins.ts
  • src/config/env.ts

Comment thread tests/cors.test.ts
Comment on lines +90 to +97
it("rejects disallowed cross-origin requests", async () => {
const res = await request(buildApp([allowedOrigin]))
.get("/ping")
.set("Origin", "https://evil.example.com");

expect(res.headers["access-control-allow-origin"]).toBeUndefined();
expect(res.headers["access-control-allow-credentials"]).toBeUndefined();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check cors library behavior when origin callback returns an error

# Search for error handling in the cors middleware and related Express error patterns
rg -nP -C5 'callback\(new Error.*CORS' --type=ts

# Look for any existing documentation or tests about expected status codes
rg -nP 'expect\(\d{3}\)|toHaveStatus|status.*\d{3}' tests/cors.test.ts

Repository: Pi-Defi-world/acbu-backend

Length of output: 641


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Show the whole relevant test file section around the disallowed-origin test
nl -ba tests/cors.test.ts | sed -n '1,220p'

# 2) Inspect CORS middleware implementation
nl -ba src/middleware/cors.ts | sed -n '1,220p'

# 3) Find where buildApp registers error handlers (to see what status errors map to)
#    (Look for common Express error-handler signatures or `app.use((err, req, res, next) => ...)`)
rg -n --hidden --glob='!**/node_modules/**' "buildApp|function buildApp|app\.use\(\s*\(err" src tests || true
rg -n --hidden --glob='!**/node_modules/**' "res\.status\(|status\(" src | head -n 50

# 4) Check if there's a centralized error handler file
fd -t f -a 'error|errors|middleware.*error|http.*error' . 2>/dev/null | head -n 50
rg -n --hidden --glob='!**/node_modules/**' "Not allowed by CORS|cors" src | head -n 50

Repository: Pi-Defi-world/acbu-backend

Length of output: 113


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Show relevant parts of tests/cors.test.ts (first 220 lines; adjust if needed)
echo "=== tests/cors.test.ts (1-220) ==="
cat -n tests/cors.test.ts | sed -n '1,220p'

# 2) Inspect CORS middleware implementation
echo "=== src/middleware/cors.ts (1-220) ==="
cat -n src/middleware/cors.ts | sed -n '1,220p'

# 3) Locate Express error handler(s) in the app setup
echo "=== searching for error-handler signatures ==="
rg -n --hidden --glob='!**/node_modules/**' "(err\s*,\s*req\s*,\s*res\s*,\s*next)|(function\s*\(\s*err\s*,\s*req)|(app\.use\(\s*\(\s*err)" src tests || true

# 4) Find buildApp registration of middleware/error handling
echo "=== searching for buildApp ==="
rg -n --hidden --glob='!**/node_modules/**' "buildApp" src tests || true

# 5) If there's an error-handling module, inspect it
echo "=== searching for error handling modules ==="
fd -t f -a 'error|errors|http.*error|middleware.*error' . 2>/dev/null | head -n 50

# 6) If we find a candidate error handler file, print it (top portion)
#    (Try common filename matches first)
for f in $(fd -t f -a 'error|errors' src 2>/dev/null | head -n 10); do
  echo "=== candidate: $f (first 200 lines) ==="
  cat -n "$f" | sed -n '1,200p'
done

# 7) Also inspect where routes/middleware are wired (if there is an app file)
for f in $(fd -t f -a 'app\.ts$|server\.ts$|index\.ts$|main\.ts$' src 2>/dev/null | head -n 10); do
  echo "=== candidate: $f (first 200 lines) ==="
  cat -n "$f" | sed -n '1,200p'
done

Repository: Pi-Defi-world/acbu-backend

Length of output: 34068


🌐 Web query:

How does the cors npm package handle errors passed to the origin callback (callback(new Error(...))) in Express? What HTTP status is returned?

💡 Result:

cors does not translate callback(new Error(...)) into an Express error response with a specific HTTP status. Instead, the middleware treats the dynamic origin callback error as an origin rejection: it will not set Access-Control-Allow-Origin, and the request continues to the next middleware/route handler (so the final HTTP status is whatever your Express app ultimately sends). Authoritative behavior: the origin callback is documented as callback(error, origin) where error can be an error object to indicate failure [1][2][3]. For rejection/denial cases, the project specifically discusses handling when the origin callback returns false / rejects and focuses on CORS header behavior rather than sending an HTTP error response; e.g., a PR notes the middleware must set Vary: Origin even when the origin callback rejects, implying the middleware is controlling headers (not returning an app-level status) [4]. So, there isn’t a single fixed HTTP status returned by cors for callback(new Error(...)) in Express; your app’s normal response status (e.g., 200, 404, 500, etc.) will be used, and the client will typically see a CORS error because the required CORS headers are missing.

Citations:


Update the disallowed-origin CORS test to assert the correct HTTP status (200, not 500).

In tests/cors.test.ts the request should still reach the /ping handler; disallowed origins are handled by omitting Access-Control-* headers, not by producing an Express error response. Add a status assertion (.expect(200)) alongside the existing checks for missing CORS headers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/cors.test.ts` around lines 90 - 97, The test "rejects disallowed
cross-origin requests" currently only checks that CORS headers are missing but
doesn't assert the response status; update the assertion so the request to
buildApp([allowedOrigin]).get("/ping").set("Origin", "https://evil.example.com")
also verifies a 200 status (e.g., add .expect(200) to the request chain or
assert res.status === 200) while keeping the existing checks for missing
access-control headers.

@Junman140 Junman140 merged commit 3605feb into Pi-Defi-world:dev May 29, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cors.ts allows Access-Control-Allow-Origin: * with credentials – T

3 participants