Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs-site/src/content/docs/ja/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ namespaced selected id を bare id に変えます。
| `webSearchSidecar?` | `OcxWebSearchSidecarConfig` | on | ウェブ検索サイドカーオプション(下記参照)。 |
| `visionSidecar?` | `OcxVisionSidecarConfig` | on | ビジョンサイドカーオプション(下記参照)。 |
| `tokenGuardian?` | `OcxTokenGuardianConfig` | off | 選択型の proactive OAuth 更新と Codex アカウント warmup ポリシー。フィールドは下で説明します。 |
| `corsAllowOrigins?` | `string[]` | `[]` | CORS で追加で許可する正確な origin。loopback origin は常に許可します。 |
| `corsAllowOrigins?` | `string[]` | `[]` | CORS で追加で許可する正確な origin。loopback origin は常に許可します。`chrome-extension://<extension-id>` など、authority ベースのブラウザー拡張機能の origin に対応しています。`*` はワイルドカードではありません。 |

`codexAccountNamespaces` のキーは公開 selector です。長さは 1〜64 文字、先頭と末尾は ASCII
英数字、内部には英数字、`.`、`_`、`-` を使用でき、予約済み JavaScript object 名は拒否されます。
Expand Down
2 changes: 1 addition & 1 deletion docs-site/src/content/docs/ko/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ namespaced selected id를 bare id로 바꿉니다.
| `webSearchSidecar?` | `OcxWebSearchSidecarConfig` | on | 웹 검색 사이드카 옵션(아래 참조). |
| `visionSidecar?` | `OcxVisionSidecarConfig` | on | 비전 사이드카 옵션(아래 참조). |
| `tokenGuardian?` | `OcxTokenGuardianConfig` | off | 선택형 proactive OAuth 갱신 및 Codex 계정 warmup 정책. 필드는 아래에 설명합니다. |
| `corsAllowOrigins?` | `string[]` | `[]` | CORS에서 추가로 허용할 정확한 origin. loopback origin은 항상 허용합니다. |
| `corsAllowOrigins?` | `string[]` | `[]` | CORS에서 추가로 허용할 정확한 origin. loopback origin은 항상 허용합니다. `chrome-extension://<extension-id>` 같은 authority 기반 브라우저 확장 프로그램 origin을 지원하며, `*`는 와일드카드가 아닙니다. |

`codexAccountNamespaces` 키는 공개 selector입니다. 길이는 1~64자이고 시작과 끝은 ASCII 영숫자여야
하며, 내부에는 영숫자, `.`, `_`, `-`를 사용할 수 있습니다. 예약된 JavaScript object 이름은 거부됩니다.
Expand Down
2 changes: 1 addition & 1 deletion docs-site/src/content/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ differing backup and rewrites known legacy namespaced selected ids to bare ids.
| `visionSidecar?` | `OcxVisionSidecarConfig` | on | Vision sidecar options (see below). |
| `images?` | `OcxImagesConfig` | automatic OpenAI selection | Standalone Images relay options for Codex's built-in `image_gen` tool (see below). |
| `tokenGuardian?` | `OcxTokenGuardianConfig` | off | Optional proactive OAuth refresh and Codex-account warmup policy; fields are listed below. |
| `corsAllowOrigins?` | `string[]` | `[]` | Additional exact origins allowed by CORS. Loopback origins are always allowed. |
| `corsAllowOrigins?` | `string[]` | `[]` | Additional exact origins allowed by CORS. Loopback origins are always allowed. Authority-based browser extension origins such as `chrome-extension://<extension-id>` are supported; `*` is not a wildcard. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -i '^configuration\.md$' docs-site/src/content/docs | sort
rg -n -C 2 'corsAllowOrigins|chrome-extension|not a wildcard|不是通配符' \
  docs-site/src/content/docs

Repository: lidge-jun/opencodex

Length of output: 6108


Sync localized CORS documentation

Update the corsAllowOrigins rows in the Japanese (line 66), Korean (line 67), and Russian (line 71) pages. State that chrome-extension://<extension-id> origins are supported and * is not a wildcard.

🤖 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 `@docs-site/src/content/docs/reference/configuration.md` at line 73, Update the
corsAllowOrigins documentation rows in the Japanese, Korean, and Russian
localized pages to match the English description: state that authority-based
browser extension origins such as chrome-extension://<extension-id> are
supported and that * is not a wildcard, while preserving the existing
configuration details.

Source: Path instructions


`codexAccountNamespaces` keys are public selectors: 1–64 characters, starting and ending with an
ASCII letter or number, with letters, numbers, `.`, `_`, or `-` inside; reserved JavaScript object
Expand Down
2 changes: 1 addition & 1 deletion docs-site/src/content/docs/ru/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ opencodex настраивается файлом `~/.opencodex/config.json`. Е
| `webSearchSidecar?` | `OcxWebSearchSidecarConfig` | вкл. | Параметры сайдкара веб-поиска (см. ниже). |
| `visionSidecar?` | `OcxVisionSidecarConfig` | вкл. | Параметры vision-сайдкара (см. ниже). |
| `tokenGuardian?` | `OcxTokenGuardianConfig` | выкл. | Необязательная политика проактивного обновления OAuth и прогрева аккаунтов Codex; поля перечислены ниже. |
| `corsAllowOrigins?` | `string[]` | `[]` | Дополнительные точные origin, разрешённые CORS. Loopback-origin разрешены всегда. |
| `corsAllowOrigins?` | `string[]` | `[]` | Дополнительные точные origin, разрешённые CORS. Loopback-origin разрешены всегда. Поддерживаются origin расширений браузера на основе authority, например `chrome-extension://<extension-id>`; `*` не является подстановочным знаком. |

Ключи `codexAccountNamespaces` — публичные селекторы длиной 1–64 символа. Они должны начинаться и
заканчиваться ASCII-буквой или цифрой; внутри разрешены буквы, цифры, `.`, `_` и `-`. Зарезервированные
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ no-replace 方式创建 `config.json.pre-openai-tiers-v2.bak`,并把已知旧
| `webSearchSidecar?` | `OcxWebSearchSidecarConfig` | 开启 | 网络搜索 sidecar 选项(见下文)。 |
| `visionSidecar?` | `OcxVisionSidecarConfig` | 开启 | 视觉 sidecar 选项(见下文)。 |
| `tokenGuardian?` | `OcxTokenGuardianConfig` | 关闭 | 可选的 proactive OAuth 刷新和 Codex account warmup 策略;字段见下文。 |
| `corsAllowOrigins?` | `string[]` | `[]` | CORS 额外允许的精确 origin。loopback origin 始终允许。 |
| `corsAllowOrigins?` | `string[]` | `[]` | CORS 额外允许的精确 origin。loopback origin 始终允许;支持 `chrome-extension://<扩展 ID>` 等基于 authority 的浏览器扩展 origin,`*` 不是通配符。 |

`codexAccountNamespaces` 的 key 是公开 selector:长度为 1–64 个字符,首尾必须是 ASCII 字母或数字,
中间可使用字母、数字、`.`、`_` 或 `-`;保留的 JavaScript object 名称会被拒绝。value 必须是有效的
Expand Down
23 changes: 18 additions & 5 deletions src/server/auth-cors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,28 @@ export function isAllowedRequestOrigin(req: Request, config: OcxConfig): boolean

function isExtraAllowedOrigin(origin: string, cfg: OcxConfig): boolean {
if (!cfg.corsAllowOrigins?.length) return false;
const parsedOrigin = comparableOrigin(origin);
return cfg.corsAllowOrigins.some(allowed => {
try {
return new URL(allowed).origin === new URL(origin).origin;
} catch {
return allowed === origin;
}
const parsedAllowed = comparableOrigin(allowed);
return parsedOrigin !== null && parsedAllowed !== null
? parsedAllowed === parsedOrigin
: allowed === origin;
});
}

function comparableOrigin(value: string): string | null {
try {
const parsed = new URL(value);
if (parsed.origin !== "null") return parsed.origin;
// WHATWG URL exposes authority-based custom schemes (for example browser
// extensions) as opaque `null` origins. Compare their scheme + authority so
// one allowlisted extension cannot admit every other opaque origin.
return parsed.host ? `${parsed.protocol}//${parsed.host}` : null;
} catch {
return null;
}
}

export function managementRequestOrigin(req: Request, config: OcxConfig): string | null {
const host = req.headers.get("Host");
const parsedHost = parseHttpHost(host);
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,7 @@ export interface OcxConfig {
combos?: Record<string, OcxComboConfig>;
/** Background proactive token refresh ("Token Guardian"). Off by default; see OcxTokenGuardianConfig. */
tokenGuardian?: OcxTokenGuardianConfig;
/** Additional origins allowed for CORS (e.g. ["https://clisu-oracle.tail19a2d7.ts.net"]). Loopback origins are always allowed. */
/** Additional exact origins allowed for CORS (e.g. HTTPS or chrome-extension://<id>). Loopback origins are always allowed. */
corsAllowOrigins?: string[];
}

Expand Down
37 changes: 37 additions & 0 deletions tests/server-auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,43 @@ describe("server local API auth", () => {
}
});

test("extension allowlist gates preflight and data-plane requests by authority", async () => {
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
mkdirSync(TEST_DIR, { recursive: true });
process.env.OPENCODEX_HOME = TEST_DIR;
const extensionOrigin = "chrome-extension://modkelfkcfjpgbfmnbnllalkiogfofh";
saveConfig({
...config("127.0.0.1"),
corsAllowOrigins: [extensionOrigin],
});
stubModelDiscoveryFor("https://api.example.test");

const server = startServer(0);
const modelsUrl = new URL("/v1/models", server.url);
try {
const preflight = await fetch(modelsUrl, {
method: "OPTIONS",
headers: {
origin: extensionOrigin,
"access-control-request-method": "GET",
},
});
expect(preflight.status).toBe(204);
expect(preflight.headers.get("access-control-allow-origin")).toBe(extensionOrigin);

const accepted = await fetch(modelsUrl, { headers: { origin: extensionOrigin } });
expect(accepted.status).toBe(200);
expect(accepted.headers.get("access-control-allow-origin")).toBe(extensionOrigin);

const rejected = await fetch(modelsUrl, {
headers: { origin: "chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" },
});
expect(rejected.status).toBe(403);
} finally {
await server.stop(true);
}
});

test("loopback management API rejects host-header same-origin rebinding", async () => {
if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
mkdirSync(TEST_DIR, { recursive: true });
Expand Down
35 changes: 35 additions & 0 deletions tests/server-loopback-host-gate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,38 @@ describe("isAllowedRequestOrigin over a forwarded port", () => {
).toBe(false);
});
});

describe("isAllowedRequestOrigin with extension origins", () => {
test("admits only the configured browser extension authority", () => {
const config = {
...loopbackConfig,
corsAllowOrigins: ["chrome-extension://modkelfkcfjpgbfmnbnllalkiogfofh"],
} as OcxConfig;

expect(
isAllowedRequestOrigin(
request("localhost:10100", "chrome-extension://modkelfkcfjpgbfmnbnllalkiogfofh"),
config,
),
).toBe(true);
expect(
isAllowedRequestOrigin(
request("localhost:10100", "chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
config,
),
).toBe(false);
expect(
isAllowedRequestOrigin(
request("localhost:10100", "moz-extension://modkelfkcfjpgbfmnbnllalkiogfofh"),
config,
),
).toBe(false);

expect(
isAllowedRequestOrigin(
request("localhost:10100", "chrome-extension://modkelfkcfjpgbfmnbnllalkiogfofh"),
{ ...loopbackConfig, corsAllowOrigins: ["*"] } as OcxConfig,
),
).toBe(false);
});
});
Loading