Skip to content

fix: return full origin with port in CORS headers for localhost#30

Merged
jimaek merged 5 commits intomasterfrom
29-invalid-origin-error
Mar 22, 2026
Merged

fix: return full origin with port in CORS headers for localhost#30
jimaek merged 5 commits intomasterfrom
29-invalid-origin-error

Conversation

@eyepokes
Copy link
Copy Markdown
Collaborator

@eyepokes eyepokes commented Mar 14, 2026

Summary

  • getMatchingOrigin() was stripping the port from localhost/127.0.0.1/[::1] origins, returning e.g. http://127.0.0.1 instead of http://127.0.0.1:8080
  • This caused a CORS mismatch: the preflight (OPTIONS) response returned the full origin with port, but the actual POST response returned origin without port
  • Browsers rejected the response with "Failed to fetch" because Access-Control-Allow-Origin didn't match the request's Origin header

Root cause
Browser-based MCP clients (e.g. llama.cpp webui on http://127.0.0.1:8080) send Origin: http://127.0.0.1:8080. The server validated this correctly but then returned Access-Control-Allow-Origin: http://127.0.0.1 — per
CORS spec these are different origins.

Fix
Reconstruct the CORS origin from validated components (baseOrigin + parsed port) instead of returning only the base origin. This is safe because both the hostname and port are extracted via new URL() parsing.

@eyepokes eyepokes linked an issue Mar 14, 2026 that may be closed by this pull request
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Mar 14, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
globalping-mcp-server c122fff Mar 14 2026, 01:35 PM

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 14, 2026

Walkthrough

getMatchingOrigin now preserves and returns the request origin including its port when the request hostname is localhost, 127.0.0.1, or [::1] and the base origin is whitelisted. isLoopbackIPv6 detection was refactored to construct a URL from the cleaned IPv6 and read its origin inside a try/catch (returns false on error). Other edits are formatting-only (target validation and input schema descriptions). Unit tests for CORS now expect port-inclusive origins.

Possibly related PRs

  • feat: Implement CORS and Origin validation for enhanced security #24 — Modifies src/lib/security.ts and CORS-related behavior; directly overlaps changes to getMatchingOrigin and cors options.
  • Add MCPcat #26 — Alters the GlobalpingEnv interface in src/types/globalping.ts; directly overlaps environment typing changes in this PR.
  • Changes #7 — Adjusts GlobalpingMCP server/agent usage and API token handler signatures; related to the server initialization and handler signature changes here.

Suggested reviewers

  • jimaek
  • MartinKolarik
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the primary fix: returning full origin with port in CORS headers for localhost origins.
Description check ✅ Passed The description clearly explains the CORS issue, root cause (port mismatch), and the fix applied, directly relating to the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 29-invalid-origin-error
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/target-validation.ts (1)

110-118: ⚠️ Potential issue | 🟠 Major

Canonicalize IPv6 before checking for loopback.

A valid loopback like 0:0:0:0:0:0::1 still misses this string list, so isPublicTarget() can accept a localhost target as public. That weakens the guard all of the measurement tools rely on.

Suggested fix
 export function isLoopbackIPv6(ip: string): boolean {
-	const cleanIp = ip.replace(/^\[|\]$/g, "").toLowerCase();
-
-	// ::1 is the only IPv6 loopback
-	return (
-		cleanIp === "::1" ||
-		cleanIp === "0:0:0:0:0:0:0:1" ||
-		cleanIp === "0000:0000:0000:0000:0000:0000:0000:0001"
-	);
+	const cleanIp = ip.replace(/^\[|\]$/g, "");
+
+	try {
+		return new URL(`http://[${cleanIp}]`).origin === "http://[::1]";
+	} catch {
+		return false;
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/target-validation.ts` around lines 110 - 118, isLoopbackIPv6
currently compares raw strings and misses equivalent loopback forms like
0:0:0:0:0:0::1; update isLoopbackIPv6 to canonicalize/parse the input IPv6
before checking (e.g., use a proper IPv6 parser such as ipaddr.js to parse the
ip string, normalize/expand it or use its isLoopback()/range checks) and then
compare against the canonical loopback (::1) or use the parser's loopback test;
ensure you still strip surrounding brackets as done today and replace the
current raw-string comparisons in isLoopbackIPv6 with the parser-based check to
catch all zero-compressed or expanded equivalents.
🧹 Nitpick comments (1)
src/lib/security.ts (1)

228-236: Update the inline comment to match the new behavior.

Line 228 still says this branch returns the base origin, but Line 236 now preserves the localhost port. The comment now describes the old bug, not the current logic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/security.ts` around lines 228 - 236, The inline comment above the
localhost handling is outdated: update the comment that references "return the
base origin" to accurately state that for localhost/127.0.0.1/[::1] the function
will return the base origin including the port when present (i.e., it preserves
the localhost port), and mention it checks CORS_CONFIG.ALLOWED_ORIGINS against
the base origin before appending the port; modify the comment near
requestOrigin/originUrl/hostname/baseOrigin to reflect this new behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/lib/target-validation.ts`:
- Around line 110-118: isLoopbackIPv6 currently compares raw strings and misses
equivalent loopback forms like 0:0:0:0:0:0::1; update isLoopbackIPv6 to
canonicalize/parse the input IPv6 before checking (e.g., use a proper IPv6
parser such as ipaddr.js to parse the ip string, normalize/expand it or use its
isLoopback()/range checks) and then compare against the canonical loopback (::1)
or use the parser's loopback test; ensure you still strip surrounding brackets
as done today and replace the current raw-string comparisons in isLoopbackIPv6
with the parser-based check to catch all zero-compressed or expanded
equivalents.

---

Nitpick comments:
In `@src/lib/security.ts`:
- Around line 228-236: The inline comment above the localhost handling is
outdated: update the comment that references "return the base origin" to
accurately state that for localhost/127.0.0.1/[::1] the function will return the
base origin including the port when present (i.e., it preserves the localhost
port), and mention it checks CORS_CONFIG.ALLOWED_ORIGINS against the base origin
before appending the port; modify the comment near
requestOrigin/originUrl/hostname/baseOrigin to reflect this new behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e8e3fc2d-0de3-4503-9fbe-8beae7caa715

📥 Commits

Reviewing files that changed from the base of the PR and between 2ff3891 and 8fd610f.

📒 Files selected for processing (4)
  • src/lib/security.ts
  • src/lib/target-validation.ts
  • src/mcp/tools.ts
  • test/unit/lib/security.test.ts

@eyepokes eyepokes requested a review from jimaek March 14, 2026 04:05
@jimaek
Copy link
Copy Markdown
Member

jimaek commented Mar 14, 2026

<html>
<body>
<!--StartFragment-->
✘ [ERROR] Could not resolve "ai"
--
05:03:27.390 |  
05:03:27.390 | node_modules/agents/dist/client-QZa2Rq0l.js:945:39:
05:03:27.390 | 945 │       const { jsonSchema } = await import("ai");
05:03:27.390 | ╵                                           ~~~~
05:03:27.390 |  
05:03:27.390 | You can mark the path "ai" as external to exclude it from the bundle, which will remove this error and leave the unresolved path in the bundle. You can also add ".catch()" here to handle this failure at run-time instead of bundle-time.

<!--EndFragment-->
</body>
</html>

@eyepokes
Copy link
Copy Markdown
Collaborator Author

@jimaek Could you explain in more detail? I didn't understand where this is coming from

@jimaek
Copy link
Copy Markdown
Member

jimaek commented Mar 14, 2026

Failed Cloudflare build

@jimaek jimaek requested a review from MartinKolarik March 14, 2026 12:35
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 35: package.json is missing the direct dependency "ai" required by
"agents": "0.2.21"; add "ai": "5.0.78" to the dependencies object in
package.json alongside "agents": "0.2.21" and update the lockfile
(npm/yarn/pnpm) or run install so the package is resolved and Cloudflare
bundling errors are prevented.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7e80fa3f-837b-4e25-9920-1fce981508d2

📥 Commits

Reviewing files that changed from the base of the PR and between f238007 and c00286c.

📒 Files selected for processing (1)
  • package.json

Comment thread package.json Outdated
"@cloudflare/workers-oauth-provider": "^0.1.0",
"@modelcontextprotocol/sdk": "^1.20.2",
"agents": "^0.2.21",
"agents": "0.2.21",
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Confirm pinned agents version"
rg -nP '"agents"\s*:\s*"0\.2\.21"' package.json

echo
echo "2) Inspect [email protected] metadata from npm"
curl -s https://registry.npmjs.org/agents/0.2.21 | jq '{name,version,dependencies,peerDependencies,optionalDependencies}'

echo
echo "3) Check repo config for bundler handling of ai (external/alias/noExternal)"
fd -HI 'wrangler.*|vite.*|vitest.*|esbuild.*|rollup.*|webpack.*|package.json' . \
  | xargs -I{} sh -c 'echo "---- {} ----"; rg -nP "external|alias|noExternal|\\bai\\b" "{}" || true'

echo
echo "4) Check for local patches related to agents/ai"
fd -HI 'patches|*.patch' . \
  | xargs -I{} sh -c 'echo "---- {} ----"; rg -nP "agents|import\\(\"ai\"\\)|\\bai\\b" "{}" || true'

Repository: jsdelivr/globalping-mcp-server

Length of output: 1315


🏁 Script executed:

cat package.json | jq '.dependencies | keys | .[]' | grep -i "^ai$"

Repository: jsdelivr/globalping-mcp-server

Length of output: 56


🏁 Script executed:

cat package.json | jq '.dependencies'

Repository: jsdelivr/globalping-mcp-server

Length of output: 287


Add ai to dependencies—[email protected] requires it but it's missing from package.json.

[email protected] declares [email protected] as a direct dependency. Pinning agents alone will not resolve the Cloudflare bundling error; the missing ai package must be added to the dependencies object.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 35, package.json is missing the direct dependency "ai"
required by "agents": "0.2.21"; add "ai": "5.0.78" to the dependencies object in
package.json alongside "agents": "0.2.21" and update the lockfile
(npm/yarn/pnpm) or run install so the package is resolved and Cloudflare
bundling errors are prevented.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/index.ts (1)

499-504: Remove the // @ts-ignore`` by typing the request context.

The Props type is already imported. Create a small context alias to eliminate the type suppression:

Refactor
+type AgentRequestContext = ExecutionContext & { props?: Props };
+
 async function handleAPITokenRequest(
 	agent: typeof GlobalpingMCP,
 	req: Request,
 	env: GlobalpingEnv,
-	ctx: ExecutionContext,
+	ctx: AgentRequestContext,
 ) {
 	// ...
 	ctx.props = {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.ts` around lines 499 - 504, The // `@ts-ignore` should be removed by
explicitly typing the request context: create a small alias (e.g. type
RequestContext = Props['ctx'] or another appropriate property from the imported
Props) and use it in the handleAPITokenRequest signature so it reads async
function handleAPITokenRequest(agent: typeof GlobalpingMCP, req: Request, env:
GlobalpingEnv, ctx: RequestContext). Replace the ts-ignore with this typed alias
and ensure Props is referenced so the compiler recognizes the type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/index.ts`:
- Around line 499-504: The // `@ts-ignore` should be removed by explicitly typing
the request context: create a small alias (e.g. type RequestContext =
Props['ctx'] or another appropriate property from the imported Props) and use it
in the handleAPITokenRequest signature so it reads async function
handleAPITokenRequest(agent: typeof GlobalpingMCP, req: Request, env:
GlobalpingEnv, ctx: RequestContext). Replace the ts-ignore with this typed alias
and ensure Props is referenced so the compiler recognizes the type.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b0a4503d-d518-409c-8f5f-03470cbdbe01

📥 Commits

Reviewing files that changed from the base of the PR and between c00286c and c122fff.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • package.json
  • src/index.ts
  • src/types/globalping.ts
  • worker-configuration.d.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json

@jimaek jimaek merged commit 7d36667 into master Mar 22, 2026
4 checks passed
@jimaek jimaek deleted the 29-invalid-origin-error branch March 22, 2026 10:43
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.

Invalid origin error

3 participants