Skip to content

Commit a7ae397

Browse files
committed
merge(dev): integrate provider outbound hardening
2 parents 169e637 + c0ad57a commit a7ae397

153 files changed

Lines changed: 4592 additions & 457 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
blank_issues_enabled: false
22
contact_links:
3+
- name: Report a security vulnerability (private)
4+
url: https://github.com/lidge-jun/opencodex/security/advisories/new
5+
about: Report undisclosed vulnerabilities privately to the maintainers. Do not open a public issue.
36
- name: Security policy
47
url: https://github.com/lidge-jun/opencodex/blob/main/SECURITY.md
58
about: Read the supported-version and reporting guidance before sharing security-sensitive details.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,9 @@ The public docs — install, providers, routing, sidecars, Codex integration, Co
509509
Maintainer source-of-truth notes live under [`structure/`](./structure). Historical investigations remain under [`docs/`](./docs).
510510
Contributor setup lives in [`CONTRIBUTING.md`](./CONTRIBUTING.md), and security reporting guidance
511511
lives in [`SECURITY.md`](./SECURITY.md).
512+
Report undisclosed vulnerabilities privately through
513+
[GitHub private vulnerability reporting](https://github.com/lidge-jun/opencodex/security/advisories/new),
514+
not a public issue.
512515

513516
## Development
514517

SECURITY.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,19 @@ or the latest published package before triage continues.
1717

1818
Please avoid posting undisclosed vulnerabilities as public GitHub issues.
1919

20-
- Prefer this repository's GitHub private vulnerability reporting or GitHub Security Advisory flow
21-
when that option is available in the repository UI.
22-
- If no private reporting option is available, do not include exploit details, secrets, or live
23-
targets in a public issue. Open a minimal issue that asks maintainers for a safe coordination path.
24-
- Include affected versions, reproduction steps, impact, and any required configuration details.
20+
Report privately through GitHub private vulnerability reporting, which is enabled on this
21+
repository:
2522

26-
The project does not publish a dedicated private security email in this repository.
23+
**<https://github.com/lidge-jun/opencodex/security/advisories/new>**
24+
25+
The same form is reachable from the repository's **Security** tab under **Report a vulnerability**.
26+
It is private between you and the maintainers, and it is the only channel this project offers for
27+
undisclosed vulnerabilities — there is no dedicated private security email.
28+
29+
Include affected versions, reproduction steps, impact, and any required configuration details.
30+
31+
If the form is ever unreachable for you, open a minimal public issue that asks maintainers for a
32+
safe coordination path. Do not include exploit details, secrets, or live targets in that issue.
2733

2834
## Response Expectations
2935

bin/ocx.mjs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
1414
import { homedir } from "node:os";
1515
import { dirname, join, resolve } from "node:path";
1616
import { fileURLToPath } from "node:url";
17+
import { npmInvocation } from "../src/update/npm-invocation.mjs";
1718
import { handoffWindowsTrayForUpdate, planWindowsTrayUpdate } from "../src/update/tray-update-plan.mjs";
1819

1920
const PKG = "@bitkyc08/opencodex";
@@ -29,10 +30,6 @@ function isBunGlobalInstall() {
2930
return /[\\/]\.bun[\\/]/.test(here);
3031
}
3132

32-
function npmBin() {
33-
return process.platform === "win32" ? "npm.cmd" : "npm";
34-
}
35-
3633
function currentPackageVersion() {
3734
try {
3835
return JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8")).version ?? "?";
@@ -115,15 +112,17 @@ function runTrayLifecycle(launcher, action) {
115112
function runNpmSelfUpdate() {
116113
const current = currentPackageVersion();
117114
const tag = updateTag(current);
118-
const npm = npmBin();
119-
// Node ≥18.20/20.12 refuses to spawn .cmd/.bat without a shell (CVE-2024-27980
120-
// hardening) — spawning "npm.cmd" shell-less throws EINVAL on Windows.
121-
const winShell = process.platform === "win32";
122-
const latestResult = spawnSync(npm, ["view", `${PKG}@${tag}`, "version"], {
115+
const latestInvocation = npmInvocation(["view", `${PKG}@${tag}`, "version"]);
116+
const installInvocation = npmInvocation(["install", "-g", `${PKG}@${tag}`]);
117+
if (!latestInvocation || !installInvocation) {
118+
console.error("opencodex: could not resolve npm from a trusted absolute PATH entry; aborting before stopping the proxy.");
119+
process.exit(1);
120+
}
121+
const latestResult = spawnSync(latestInvocation.file, latestInvocation.args, {
123122
encoding: "utf8",
124123
timeout: 12000,
125124
windowsHide: true,
126-
shell: winShell,
125+
...latestInvocation.options,
127126
});
128127
const latest = latestResult.status === 0 ? latestResult.stdout.trim() : "";
129128

@@ -221,12 +220,12 @@ function runNpmSelfUpdate() {
221220
}
222221
}
223222

224-
console.log(`Updating${latest ? ` to v${latest}` : ""}...\n$ ${npm} install -g ${PKG}@${tag}`);
225-
const res = spawnSync(npm, ["install", "-g", `${PKG}@${tag}`], {
223+
console.log(`Updating${latest ? ` to v${latest}` : ""}...\n$ npm install -g ${PKG}@${tag}`);
224+
const res = spawnSync(installInvocation.file, installInvocation.args, {
226225
stdio: "inherit",
227226
timeout: 180000,
228227
windowsHide: true,
229-
shell: winShell,
228+
...installInvocation.options,
230229
});
231230
if (res.status === 0) {
232231
console.log(`\nUpdated${latest ? ` to v${latest}` : ""}.`);
@@ -278,7 +277,7 @@ function runNpmSelfUpdate() {
278277
process.exit(0);
279278
}
280279
if (trayBeforeUpdate.restoreOnFailure) runTrayLifecycle(launcher, "start");
281-
console.error(`\nUpdate failed (${npm} exit ${res.status ?? "?"}). Try manually: ${npm} install -g ${PKG}@${tag}`);
280+
console.error(`\nUpdate failed (npm exit ${res.status ?? "?"}). Try manually: npm install -g ${PKG}@${tag}`);
282281
process.exit(1);
283282
}
284283

docs-site/src/content/docs/ja/reference/cli.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,15 @@ ocx service status
360360
ocx service uninstall
361361
```
362362

363+
Windows でタスク スケジューラのエントリを作成するには昇格が必要です。認識できるローカライズ
364+
済みのアクセス拒否テキストは、既存の案内経路をそのまま使用します。そのテキストが読めない場合、
365+
コマンド形状が `/create /tn opencodex-proxy /xml <空でないパス> /f` と正確に一致し、終了 status
366+
が 1 で、現在のトークンが未昇格と確認できたときだけ、言語に依存しないフォールバックが働きます。
367+
その場合、ダッシュボードの Startup Safety アクションが UAC を自動的に要求できます。フォール
368+
バックでトークン状態を確認できない場合は、元のスケジューラエラーを保持します。他のタスクや操作
369+
は自動昇格 marker を生成できません。ダッシュボードの UAC を承認するか、管理者 PowerShell で
370+
`ocx service install` を再実行してください。
371+
363372
### `ocx codex-shim <subcommand>`
364373

365374
PATH 上のスクリプトベース `codex` ランチャーを軽量な自動起動スクリプトで包みます。実際の `codex.exe`

docs-site/src/content/docs/ko/reference/cli.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,14 @@ ocx service status
379379
ocx service uninstall
380380
```
381381

382+
Windows에서 작업 스케줄러 항목을 만들려면 권한 상승이 필요합니다. 인식 가능한 현지화 권한 거부
383+
문자열은 기존 안내 경로를 그대로 사용합니다. 문자열을 읽을 수 없을 때는 명령 모양이
384+
`/create /tn opencodex-proxy /xml <비어 있지 않은 경로> /f`와 정확히 일치하고, 종료 상태가 1이며,
385+
현재 토큰이 권한 상승되지 않았음이 확인돼야만 언어 독립 fallback이 작동합니다. 이때 대시보드의
386+
Startup Safety 작업이 UAC를 자동 요청할 수 있습니다. fallback에서 토큰 상태를 확인할 수 없으면 원래
387+
스케줄러 오류를 유지합니다. 다른 작업과 동작은 자동 권한 상승 marker를 만들 수 없습니다. 대시보드의
388+
UAC를 승인하거나 관리자 PowerShell에서 `ocx service install`을 다시 실행하세요.
389+
382390
### `ocx codex-shim <subcommand>`
383391

384392
PATH에 있는 스크립트 기반 `codex` 런처를 가벼운 자동 시작 스크립트로 감쌉니다. 실제 `codex.exe`

docs-site/src/content/docs/reference/cli.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,14 @@ ocx service status
439439
ocx service uninstall
440440
```
441441

442+
On Windows, creating the Task Scheduler entry requires elevation. Recognized localized
443+
access-denied text keeps the existing guidance path. If that text is unreadable, the fallback
444+
requires the owned command shape `/create /tn opencodex-proxy /xml <non-empty-path> /f`, status 1,
445+
and a confirmed non-elevated token; the dashboard's Startup Safety action can then request UAC
446+
automatically. If that fallback cannot determine the token state, it retains the original scheduler
447+
error. Foreign tasks and operations can never emit the automatic-elevation marker. Approve the
448+
dashboard UAC prompt or rerun `ocx service install` in an elevated PowerShell window.
449+
442450
### `ocx codex-shim <subcommand>`
443451

444452
Wrap a script-based `codex` launcher on PATH with a lightweight autostart script. Real `codex.exe`

docs-site/src/content/docs/reference/configuration.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,33 @@ inert: it does not create model-picker entries, pin sessions, or alter Pool or D
8686
`max_concurrent_threads_per_session` value under `[features.multi_agent_v2]` in Codex's
8787
`$CODEX_HOME/config.toml`; enable v2 first so that table exists.
8888

89+
## Provider diagnostic outbound safety
90+
91+
The dashboard provider connection test and live model discovery use a bounded GET-only outbound
92+
transport. Without an outbound proxy, opencodex resolves the provider hostname once and connects
93+
only to that validated address. HTTPS keeps the original hostname for Host, SNI, and certificate
94+
verification; certificate verification cannot be disabled by provider config.
95+
96+
When `HTTP_PROXY`, `HTTPS_PROXY`, or `ALL_PROXY` applies, these two operations keep Bun's native fetch
97+
so existing proxy behavior is not silently bypassed. URL/literal checks still run. Successful local DNS answers
98+
are classified, but a local DNS failure is allowed through because proxy-only networks commonly
99+
delegate name resolution to the proxy. The proxy chooses the final route, DNS answer, and peer, so
100+
opencodex logs that this path cannot pin or verify the proxy-selected peer. This is an explicit
101+
security limitation, not equivalent protection against DNS rebinding.
102+
103+
Private/local provider destinations require both `allowPrivateNetwork: true` and a matching
104+
`NO_PROXY` entry whenever an outbound proxy is configured. Loopback entries are added to `NO_PROXY`
105+
automatically. A LAN provider such as `192.168.1.50` must be added explicitly; otherwise connection
106+
tests and model discovery reject it with an actionable message instead of sending it to the proxy.
107+
Metadata and link-local destinations remain blocked even when `allowPrivateNetwork` is enabled.
108+
The safety guard accepts exact hosts, domain suffixes, optional ports, bracketed IPv6, and `*` in
109+
`NO_PROXY`; it does not interpret CIDR entries, so list each private provider host or address explicitly.
110+
111+
Both direct and proxied diagnostic paths reject redirects and report a credential-stripped target;
112+
configure the final provider URL directly. Ordinary provider requests, streaming responses, and
113+
retry paths are not migrated in this phase. Their redirect handling and per-hop destination review
114+
remain deferred, so this phase does not close the main-request redirect finding.
115+
89116
## Combos (`config.combos`)
90117

91118
Failover / round-robin aliases live under `combos.<id>` with `targets` (provider + model), optional
@@ -179,6 +206,7 @@ body-occupancy guard):
179206
| `claudeCode.bodyMaxBytes?` | `number` | `67108864` | Native passthrough cumulative body byte cap (streamed SSE and buffered non-stream). Exactly `0` disables. |
180207
| `claudeCode.authMode?` | `"proxy" \| "subscription"` | unset (auto) | How `ANTHROPIC_AUTH_TOKEN` is handled at launch. Unset means auto: opencodex detects Claude auth on every launch and picks subscription when it finds any, proxy when it finds none, and subscription with a warning when it cannot tell. An explicit value is never overridden by detection. See [Claude Code](/guides/claude-code/#auth-mode). |
181208
| `claudeCode.authModeMigratedAt?` | `string` | unset | Internal one-time marker. Written once when an upgrade pins a pre-`auto` config to `subscription`, so a deliberate subscriber is not silently moved onto the proxy. Do not set by hand. |
209+
| `claudeCode.subagentEffort?` | `"low" \| "medium" \| "high" \| "xhigh" \| "max"` | unset (inherit) | Effort written to every generated `~/.claude/agents/ocx-*.md` definition. Unset lets each subagent inherit the parent Claude Code session effort. This controls Claude Code custom-agent frontmatter; it is separate from Codex `injectionEffort` guidance and proxy-side effort caps. Regenerate the definitions by starting through `ocx claude` after changing it. |
182210

183211
### Managed record shapes
184212

docs-site/src/content/docs/ru/reference/cli.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,15 @@ ocx service status
401401
ocx service uninstall
402402
```
403403

404+
В Windows для создания задания Планировщика требуется повышение прав. Распознанный локализованный
405+
текст об отказе в доступе продолжает использовать прежний путь подсказки. Если текст нечитаем,
406+
независимый от языка fallback требует точную форму команды
407+
`/create /tn opencodex-proxy /xml <непустой-путь> /f`, статус 1 и подтверждённый неповышенный токен;
408+
тогда Startup Safety может автоматически запросить UAC. Если fallback не может определить состояние
409+
токена, сохраняется исходная ошибка Планировщика. Другие задания и операции не могут создать marker
410+
автоматического повышения. Подтвердите UAC в панели или повторите `ocx service install` в PowerShell
411+
от имени администратора.
412+
404413
### `ocx codex-shim <subcommand>`
405414

406415
Оборачивает скриптовый лаунчер `codex` в PATH лёгким скриптом автозапуска. Настоящие цели

docs-site/src/content/docs/ru/reference/configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ body-occupancy):
120120
| --- | --- | --- | --- |
121121
| `claudeCode.bodyStallSec?` | `number` | `90` | Бюджет неактивности тела нативного passthrough в секундах — тишина по сырым байтам от вышестоящей стороны, пока ожидается чтение, а не общая длительность. Минимум 1. Ровно `0` отключает. |
122122
| `claudeCode.bodyMaxBytes?` | `number` | `67108864` | Ограничение суммарного размера тела нативного passthrough в байтах (потоковый SSE и буферизованный непотоковый ответ). Ровно `0` отключает. |
123+
| `claudeCode.subagentEffort?` | `"low" \| "medium" \| "high" \| "xhigh" \| "max"` | не задано (наследуется) | Уровень effort, записываемый во все сгенерированные определения `~/.claude/agents/ocx-*.md`. Если значение не задано, subagent наследует effort родительской сессии Claude Code. Это поле управляет frontmatter пользовательского агента Claude Code и не связано с Codex `injectionEffort` или ограничениями effort на стороне proxy. После изменения запустите через `ocx claude`, чтобы пересоздать определения. |
123124

124125
### Управляемые формы записей
125126

0 commit comments

Comments
 (0)