Skip to content

chrome-extension:// origins in controlUi.allowedOrigins silently fail to match #52477

Description

@openclaw-lisa

Summary

chrome-extension:// origins configured in gateway.controlUi.allowedOrigins are silently rejected because parseOrigin() returns null for them.

Root Cause

In parseOrigin(), the code does:

const url = new URL(trimmed);
return {
  origin: url.origin.toLowerCase(),
  // ...
};

Node's URL parser returns the string "null" for url.origin when the scheme is chrome-extension://:

new URL("chrome-extension://bfpnaggikhabdgbnhnngdfldkbinncdf").origin
// => "null"

Then earlier in parseOrigin, there's a guard:

if (!trimmed || trimmed === "null") return null;

This doesn't catch it (the raw input isn't "null"), but the returned origin: "null" will never match any entry in the allowlist. The result is a 1008 close with "origin not allowed" even though the exact chrome-extension://... string is in allowedOrigins.

This is particularly confusing because:

  1. The gateway logs show the correct origin being received
  2. The config has the correct origin in the allowlist
  3. There's no indication that the match silently failed due to URL parsing
  4. Other parts of the codebase already have chrome-extension:// awareness (e.g., the relay server CORS handling)

Workaround

Patch parseOrigin in the dist files:

sudo sed -i 's|origin: url.origin.toLowerCase()|origin: url.origin === "null" ? trimmed.toLowerCase() : url.origin.toLowerCase()|g' \
  /usr/lib/node_modules/openclaw/dist/gateway-cli-CuZs0RlJ.js \
  /usr/lib/node_modules/openclaw/dist/gateway-cli-Ol-vpIk7.js

This makes parseOrigin fall back to the raw input string when url.origin is "null", so chrome-extension://... origins match correctly against the allowlist.

Suggested Fix

In parseOrigin(), when url.origin === "null", use the trimmed raw input as the origin instead:

function parseOrigin(originRaw) {
  const trimmed = (originRaw ?? "").trim();
  if (!trimmed || trimmed === "null") return null;
  try {
    const url = new URL(trimmed);
    const resolved = url.origin === "null" ? trimmed.toLowerCase() : url.origin.toLowerCase();
    return {
      origin: resolved,
      host: url.host.toLowerCase(),
      hostname: url.hostname.toLowerCase()
    };
  } catch {
    return null;
  }
}

Environment

  • OpenClaw v2026.3.13 (npm)
  • Node.js v24.14.0
  • Chrome extension origin: chrome-extension://bfpnaggikhabdgbnhnngdfldkbinncdf (OpenClaw Copilot)
  • Gateway behind Cloudflare Access tunnel

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions