Commit Graph
1497 Commits
Author SHA1 Message Date
Tam Nhu Tran e461c7a67e fix(codex-auth): address persistent review focus 2026-05-17 18:33:20 -04:00
Tam Nhu Tran 81c4acc73a fix(codex-auth): harden registry fail-closed paths 2026-05-17 18:20:56 -04:00
Tam Nhu Tran 54738e88f7 fix(codex-auth): surface registry corruption 2026-05-17 18:02:13 -04:00
Tam Nhu Tran c90b3cb9da fix(codex-auth): reject stray profile args 2026-05-17 17:51:32 -04:00
Tam Nhu Tran 85521018bf fix(codex-auth): close local review gaps 2026-05-17 17:41:23 -04:00
Tam Nhu Tran 2cd2d43186 fix(codex-auth): fail closed on registry corruption 2026-05-17 17:14:54 -04:00
Tam Nhu Tran 211e51b949 fix(codex-auth): address review focus areas 2026-05-17 17:02:59 -04:00
Tam Nhu Tran 5c79df4311 fix(codex-auth): close remaining review gaps 2026-05-17 16:51:22 -04:00
Tam Nhu Tran 08fe63c626 fix(codex-auth): fail fast on missing active profile 2026-05-17 16:06:32 -04:00
Tam Nhu Tran ea421c4a20 chore: merge origin/dev into codex auth profile branch 2026-05-17 15:33:06 -04:00
Tam Nhu Tran a3fe2c63d8 fix(codex-auth): harden profile review findings 2026-05-17 15:32:29 -04:00
Tam Nhu Tran 631c799322 feat(codex-auth): add import-default migration + integration tests + docs
Adds opt-in `ccsx auth import-default <name>` to migrate the existing
~/.codex/auth.json into a new profile, plus the cross-system integration
tests and user-facing documentation.

- import-default-command (C3 torn-write protection):
  - readFileSync + JSON.parse with 3x retry / 100ms backoff to survive
    Codex's truncate-then-write auth.json refresh race
  - decode-id-token sanity-check on JWT shape (catches mid-write JWT
    corruption that JSON.parse alone wouldn't notice)
  - pgrep -f codex best-effort detection; warns + refuses without
    --force-while-running flag if a live codex process is found
  - rejects cliproxy-format auth files ({type: "codex", ...} wrapper)
    with a clear "use ccs cliproxy ..." pointer
  - atomic write to <dest>.tmp.<pid>.<rand> + rename
  - --with-history defaults to false per D8 (auth-only is the safer
    default; opt in for bulkier data)
  - --force backs up existing auth.json to .bak-<ts> before overwrite
  - non-destructive — never modifies ~/.codex/; legacy mode keeps
    working without ever running this command
- integration tests:
  - two-terminal-isolation: two profiles with separate CODEX_HOMEs
    write to their own auth.json/history.jsonl with no crosstalk
  - ccsxp-independence: codex-auth profile set; ccsxp still uses its
    own CCSXP_CODEX_HOME / ~/.codex pool (H5 stderr notice present)
  - legacy-fallback: no profiles registered → codex-runtime-router
    leaves CODEX_HOME unset → codex falls back to ~/.codex
  - import-default.integration: real fs copy + decode + register
- docs/codex-auth.md: user guide covering quick start, two-terminal
  example, migration, dashboard, and caveats (cmd.exe, Windows
  symlinks, ccsx vs ccsxp distinction)

155 codex-auth-scope tests green (45 Phase 1 + 57 Phase 2 + 19 Phase 3
+ 15 Phase 4 + 19 Phase 5). Full suite 3051/3082 — the 1 failure is a
pre-existing test-pollution issue between ccsxp-runtime.test.ts and
codex-runtime-integration.test.ts that exists on dev today; the test
passes in isolation.
2026-05-17 14:46:16 -04:00
Tam Nhu Tran e99c4612a8 feat(codex-auth): add dashboard Auth Profiles tab + GET /api/codex/profiles
Adds a read-only dashboard surface for the codex-auth profile registry.
Power users can see which profile is active and its decoded email/plan
without leaving the browser; mutations stay CLI-only.

- codex-auth-dashboard-service: builds the response shape (active +
  default + profiles[]), reads each profile's auth.json, decodes id_token
  via Phase 1 decoder (nested URI claims per C1), 5s in-memory single-key
  cache plus exported invalidateCodexAuthProfilesCache() hook for
  in-process Phase 2 callers (D7); strict field whitelist — id_token /
  access_token / refresh_token NEVER appear in response body or logs
- GET /api/codex/profiles registered INSIDE the
  requireLocalAccessWhenAuthDisabled middleware (H6) — emails are PII
  and must not leak when dashboard is exposed remotely; integration
  test asserts 403 from non-localhost origin
- NEW Auth Profiles tab (D5) in ui/src/pages/codex.tsx — distinct from
  the existing codex-profiles-card (which edits config.toml [profiles],
  a different concept); active profile + email + plan tier highlighted,
  table of all profiles below, disabled Switch/Remove buttons redirect
  to terminal commands
- accountId returned by API for power users (curl) but hidden from
  the default UI (D6)
- 11 service unit tests + 4 endpoint integration tests, all green
2026-05-17 14:45:35 -04:00
Tam Nhu Tran 8c604a040f feat(codex-auth): wire ccsx bin router + ccsxp scope notice
Upgrades the previously-stub ccsx binary entry (src/bin/codex-runtime.ts)
into an argv router: `ccsx auth <cmd>` dispatches to the Phase 2 router;
any other argv resolves the active codex-auth profile and spawns codex
with CODEX_HOME pointed at the profile dir.

- resolve-active-profile: sync, hot-path-safe (<5ms), reads YAML
  registry via Phase 1 helpers; precedence is CODEX_HOME (explicit)
  > CCS_CODEX_PROFILE (env) > registry default > null (legacy
  ~/.codex fallback); fails open on any error (silent for missing
  registry, stderr warn for corrupt/missing-profile)
- codex-runtime-router: extracted main() for testability; entry
  script is a thin 3-line wrapper; returns -1 sentinel for the
  CCS branch so the spawn lifecycle isn't terminated
- ccsxp-runtime: H5 defensive stderr notice when CCS_CODEX_PROFILE
  is set, surfacing the boundary between codex-auth (native codex)
  and ccsxp (cliproxy pool) without changing functional behavior;
  CLIProxyAPI does not read CODEX_HOME so no pool contamination
  possible
- 14 unit tests (8 resolver + 6 router); ccsxp regression suite
  (5 tests) untouched and still green
2026-05-17 14:45:13 -04:00
Tam Nhu Tran bf92645b35 feat(codex-auth): add ccsx auth CLI subcommands (create/login/switch/use/show/remove)
Implements the user-facing surface for ccsx auth profile management.
After `ccsx auth create work` (auto-spawns codex login with CODEX_HOME
pinned per D11), users can `eval "$(ccsx auth use work)"` in any shell
to scope all subsequent codex invocations to that profile — letting
two terminals run two different Codex accounts concurrently.

- codex-auth-router: dispatches argv to subcommand handlers
- create: idempotent, --force re-links config.toml preserving auth.json
  (D9), then auto-spawns codex login with CODEX_HOME pinned (D11);
  filesystem ops happen before registry write to avoid registry orphans
  on EACCES/ENOSPC
- login: standalone re-auth for an existing profile
- switch: persistent default in YAML registry
- use: STDOUT-DISCIPLINED — emits only shell-evalable exports;
  bash/zsh/fish/PowerShell/cmd syntaxes via shell-detect; sets
  CCS_NO_PRE_DISPATCH=1 at module load to suppress recovery/migration
  banners that would otherwise contaminate eval (C2)
- show: list (active(missing) row at top per D14) + detail views
- remove: default-profile guard, active-shell warn, --yes / --force
- ASCII-only output, NO_COLOR honored, all errors to stderr via
  exitWithError
- pre-dispatch.ts: early-return when CCS_NO_PRE_DISPATCH=1, placed
  before autoMigrate which is itself a stdout writer

57 unit tests, all green. Help text cross-references the ccsxp/ccsx
distinction since the binaries differ by one character (H5).
2026-05-17 14:44:44 -04:00
Tam Nhu Tran 358b703d98 feat(codex-auth): add profile registry + storage foundation
Adds the storage substrate for ccsx auth profile isolation.
Each Codex profile gets its own CODEX_HOME dir under
~/.ccs/codex-instances/<name>/ with isolated auth.json and
history.jsonl; config.toml is shared via symlink to ~/.codex/config.toml
so two terminals can run two Codex accounts simultaneously without
duplicating user config.

- CodexProfileRegistry (YAML, atomic write tmp.<pid>.<rand> + rename,
  orphan cleanup, full CRUD + default pointer)
- decode-id-token: pure base64 JWT decoder for OpenAI id_token,
  reads nested https://api.openai.com/auth claims (chatgpt_plan_type,
  chatgpt_account_id) and dual-path email
- ensureSharedConfigSymlink: self-healing, idempotent, overwrites
  stale entries with stderr warning
- 45 unit tests, all green

Foundation only — no CLI, no runtime injection, no dashboard.
Subsequent commits wire those in.
2026-05-17 14:44:07 -04:00
Kai (Tam Nhu) TranandGitHub d68ee37590 fix(codex): preserve custom ccsxp cliproxy base URLs
Preserve valid custom model_providers.cliproxy.base_url values during ccsxp Codex provider repair while keeping local fallback repair for missing or invalid URLs.\n\nCloses #1281
2026-05-17 06:59:09 -04:00
Kai (Tam Nhu) TranandGitHub db175b6807 fix(security): require localhost for Claude extension /setup when dashboard auth is disabled (#1270)
* fix(security): restrict Claude extension setup endpoint

* style: apply prettier formatting
2026-05-16 13:58:57 -04:00
Kai (Tam Nhu) TranandGitHub ece43fc30e fix(cliproxy): redact AI provider header secrets (#1268)
* fix(cliproxy): redact AI provider header secrets

* style: apply prettier formatting
2026-05-16 13:58:55 -04:00
Kai (Tam Nhu) TranandGitHub 78004746be fix: address upstream reviewer findings + failing CI checks (#1261 loop 1) (#1271)
* fix(ci): breaking-change-guard skips missing base files (CI-1)

Guard each git-show call with git cat-file -e existence check before
attempting to read docker/compose.yaml from the base branch. When the
file doesn't exist on the base (new file in this PR), the script would
crash with "fatal: path exists on disk but not in origin/dev". New
files can't cause a contract regression, so we exit early with
breaking=0.

* style: prettier reformat unrelated drift (CI-2)

Two files had minor formatting drift from earlier umbrella PRs:
- src/cliproxy/quota/quota-manager.ts
- src/management/checks/image-analysis-check.ts

No logic changes — formatter-only pass to unblock CI format:check.

* fix(test): update trusted-author gate count to 4 in ci-workflow test (CI-3)

PR #1260 added a compose-parity job to ci.yml. That job runs on
self-hosted runners using PR-provided checkout, so it legitimately
requires the trusted-author guard (same as validate, build, test).

The test expected 3 occurrences; the correct count is now 4:
  validate (matrix), build, test, compose-parity.

* fix(ci): smoke-test passes compose path + image ref to network-contract (REV-1)

network-contract.sh signature is: <compose-file> [image-ref]
The smoke-test job was calling it as:
  bash tests/docker/network-contract.sh "${{ steps.image.outputs.ref }}"
which placed the image ref in the compose-file position ($1), causing
the script to try docker compose -f <image-ref> which fails.

Corrected to:
  bash tests/docker/network-contract.sh docker/compose.yaml "${{ steps.image.outputs.ref }}"

Also removes publish-dashboard from smoke-test.needs (REV-2): when
publish-dashboard is SKIPPED on prerelease events, GitHub Actions
propagates the skip to downstream jobs, so smoke-test and
promote-mutable-tags were silently skipped on every rc.N publish.
smoke-test only verifies the integrated image; it has no dependency
on the legacy dashboard image job.

* docs(docker): annotate /root/.ccs path in compose volume (REV-3 clarification)

The reviewer raised a concern that the compose volume mounts /root/.ccs
but the entrypoint might default to /home/node/.ccs. This is a false
positive: the integrated image uses entrypoint-integrated.sh (not
entrypoint.sh), which runs under supervisord with user=root and
explicitly mkdir -p /root/.ccs. HOME is /root inside the container.
The volume mount at /root/.ccs is correct.

Added an inline comment documenting the reasoning so future reviewers
do not confuse entrypoint.sh (legacy dashboard image) with
entrypoint-integrated.sh (integrated image).

* fix(ci): gate docs-parity pull_request job to trusted authors

docs-parity.yml runs on a self-hosted runner and checks out PR code.
The self-hosted-runner-policy test requires any such workflow to include
the trusted-author guard. The workflow was missing the guard, causing
bun test:fast to fail with 1 failure.

Allow push events (no author check needed — push is to own branch)
and trusted-contributor PRs only.
2026-05-16 13:57:50 -04:00
Kai (Tam Nhu) TranandGitHub 14cbe8fb67 fix(security): reject 127-prefixed websocket origins (#1263)
* fix(security): reject 127-prefixed websocket origins

* style: apply prettier formatting
2026-05-16 13:56:10 -04:00
Kai (Tam Nhu) TranandGitHub 28f08cbb50 docs(docker): document ccs-net contract for sibling containers (#1259)
Add "Connect your app to CLIProxy" section to docker/README.md with:
- Public contract table (network=ccs-net, service DNS=ccs, ports 8317/3000)
- Pattern A: same compose file with external network reference
- Pattern B: docker run --network ccs-net
- Troubleshooting subsection (DNS, missing network, conflict, Podman, MTU)

Add one-line link in README.md pointing to the new section.
Add CHANGELOG entry under Unreleased noting the contract as SemVer-major stable.
Add CONTRIBUTING.md note that changing services.ccs or networks.ccs-net requires major bump.
2026-05-16 12:46:33 -04:00
Kai (Tam Nhu) TranandGitHub 609bed0c39 fix(codex): normalize native cliproxy tuning aliases (#1254) 2026-05-15 09:51:42 -04:00
Tam Nhu Tran 5b664d2105 hotfix(codex): tolerate BOM in Codex TOML config 2026-05-14 13:13:55 -04:00
Kai (Tam Nhu) TranandGitHub 4748c452bd fix(codex): self-heal ccsxp cliproxy provider (#1243) 2026-05-14 12:35:31 -04:00
walker1211andGitHub 35b6210cd2 fix: support Claude subcommands and background sessions
Squash merge PR #1240.\n\nValidated locally from the PR head because trusted-author GitHub CI was skipped for this fork PR:\n- bun run validate\n- bun run build:all\n- bun run test:all\n- CCS_E2E_SKIP_BUILD=1 bun run test:e2e
2026-05-14 10:18:23 -04:00
Tam Nhu Tran 1932b2ca61 fix(proxy): strip stale encoding from upstream error responses 2026-05-13 09:13:28 -04:00
Kai (Tam Nhu) TranandGitHub 667b1d1e1f fix: ignore internal instance directories in health scans
Closes #1236
2026-05-12 22:17:16 -04:00
Kai (Tam Nhu) TranandGitHub 349db830df fix(settings): confine dashboard settings paths (#1231) 2026-05-12 18:13:26 -04:00
Kai (Tam Nhu) TranandGitHub a840793a9b fix(proxy): avoid exposing local auth token in daemon argv (#1230) 2026-05-12 18:13:20 -04:00
Kai (Tam Nhu) TranandGitHub 9c5d4976c5 fix(windows): pin cmd shell for wrapper launches (#1229) 2026-05-12 18:13:13 -04:00
Tam Nhu Tran c2b00b7ad6 feat(dispatcher): pass through Claude subcommands without interactive-session args
Closes #1218.

When the user invokes `ccs <profile> agents` (and other Claude subcommands
like `mcp`, `doctor`, `plugin`, ...), CCS was unconditionally injecting
session-only Claude flags (`--append-system-prompt`, `--disallowedTools`,
`--settings`, official-channels plugin specs) and forwarding the
`DISABLE_TELEMETRY=1` env var from profile settings. Each of those
either errored out the subcommand (`error: unknown option
'--append-system-prompt'`) or silently flipped Claude into
non-interactive list mode, so the new `claude agents` agent view never
opened under CCS.

Add a small `claude-subcommand-detector` that recognizes the documented
Claude subcommand set after skipping known value-taking flags, then have
the three steering-prompt injectors (websearch, image-analysis, browser),
the cliproxy and settings launchers, and the official-channels plan
short-circuit when a subcommand invocation is detected. Also strip
`DISABLE_TELEMETRY` from the spawned env only for subcommand
invocations — upstream Claude Code uses that var as a kill switch for
the subcommand TUIs, and the user's telemetry preference still applies
to every normal interactive session.

Verified end-to-end against `ccs glm agents` and `ccs ck agents`: the
agent view opens correctly, no `unknown option` error, and all 1938
existing tests pass.
2026-05-12 16:35:37 -04:00
Kai (Tam Nhu) TranandGitHub d61469edcb fix(config): bind dashboard to loopback by default 2026-05-12 10:59:49 -04:00
Kai (Tam Nhu) TranandGitHub f09cdfcf2b fix(security): protect dashboard WebSocket upgrades 2026-05-12 10:42:05 -04:00
Kai (Tam Nhu) TranandGitHub bd588a271d fix(codex): require local access for dashboard config routes 2026-05-12 10:24:25 -04:00
Kai (Tam Nhu) TranandGitHub 29da75c0d4 fix(cliproxy): explain headless Codex OAuth recovery
Closes #1213
2026-05-11 14:25:34 -04:00
Tam Nhu Tran a983332016 refactor(web-server): modularize shared-routes and Windows-aware symlink status 2026-05-10 22:15:33 -04:00
Kai (Tam Nhu) TranandGitHub 248b6b735d Merge pull request #1200 from kaitranntt/kai/feat/1199-shared-resource-controls
feat(auth): add shared resource controls
2026-05-10 22:01:57 -04:00
Tam Nhu Tran 31076dff3e fix(web-server): gate Gemini/AGY dashboard OAuth on Plus credential availability
The dashboard Add Account flow hits POST /api/cliproxy/auth/:provider/start-url
which fetches the OAuth consent URL directly from the CLIProxy Plus
management API. With cliproxy.backend=plus and unset
CLIPROXY_{GEMINI,ANTIGRAVITY}_OAUTH_CLIENT_ID/SECRET, Plus returns a URL
with empty client_id= and Google rejects with 400 invalid_request. PR
#1131 only guarded the CLI /start path; /start-url was unguarded.

- Pre-fetch guard via getPlusOAuthCredentialError -> 400
  plus_oauth_credentials_missing before contacting the Plus binary.
- Post-fetch guard via getPlusAuthUrlCredentialError -> 502
  plus_oauth_url_missing_client_id if Plus still emits a URL without
  client_id (logged with query string redacted).
- New integration test (cliproxy-auth-routes-oauth-guard.test.ts) covers
  gemini/agy guard firing, non-table providers passing through, and both
  error body contracts. 15 cases pass.

Closes #1208
2026-05-10 21:55:08 -04:00
Tam Nhu Tran 4ba9df5425 fix(cliproxy/auth): generalize Plus OAuth credential guards for gemini and agy
Refactor getGeminiPlusOAuthCredentialError / getGeminiAuthUrlCredentialError
behind a provider-table-driven helper (PLUS_OAUTH_ENV_BY_PROVIDER) and add
getPlusOAuthCredentialError / getPlusAuthUrlCredentialError exports covering
both gemini and agy. Existing gemini-named exports preserved as aliases so
PR #1131 test surface remains unchanged. New AGY-parity test cases mirror
the original gemini diagnostics. CLI call sites in triggerOAuth /
handlePasteCallbackMode stay gemini-only; dashboard handler in the
follow-up commits will use the generalized exports for both providers.

Refs #1208
2026-05-10 21:55:08 -04:00
Tam Nhu Tran 641d492cd6 feat(cliproxy): add OAuth callback traceability across profiles
Introduces structured per-phase tracing for OAuth flows so users see
branch-specific error messages instead of a generic "token not found"
fallback.

- New oauth-trace/ module: recorder, redactor, three sinks
  (in-memory ring buffer, verbose stdout, opt-in JSONL file at
  ~/.ccs/logs/oauth-YYYYMMDD.log mode 0o600 via CCS_OAUTH_LOG_FILE=1)
- Branch-specific diagnostics for: URL-not-displayed,
  callback-not-observed, binary-error, token-exchange-error,
  session-expired, token-file-missing; UNKNOWN fallback preserves
  prior UX
- Trace recorder threaded through oauth-process.ts spawn/stdout/stderr/
  exit lifecycle; SIGINT/SIGTERM cleanup flushes recorder and records
  Cancelled
- Redactor strips code, state, access_token, refresh_token, id_token,
  client_secret, code_verifier, device_code, assertion, subject_token,
  plus Authorization: Bearer headers; covers URL fragments,
  URL-encoded keys, and arrays
- 68 new tests including adversarial regression cases for redactor
  bypass attempts

Phases for paste-callback path and cross-profile failure-matrix
deferred to follow-up #1207.

Closes #1206
2026-05-10 15:12:40 -04:00
Tam Nhu Tran 8b34060294 fix(dashboard): show real shared plugin registry state 2026-05-09 03:18:20 -04:00
Tam Nhu Tran 2a422b3bd3 feat(dashboard): add shared resource controls 2026-05-09 02:29:08 -04:00
Tam Nhu Tran 3b550205dd fix(auth): require resource mode value 2026-05-08 23:36:12 -04:00
Tam Nhu Tran bfe32257b6 fix(auth): reject resource mode outside resources command 2026-05-08 23:29:03 -04:00
Tam Nhu Tran 64e1d1f815 feat(auth): add shared resource controls 2026-05-08 11:15:28 -04:00
Kai (Tam Nhu) TranandGitHub 74d73748ee feat: support Codex fast service-tier aliases
* feat: support Codex fast service-tier aliases

* fix: send Codex fast tier as priority
2026-05-07 15:34:05 -04:00
Kai (Tam Nhu) TranandGitHub 19a50a8dd8 feat: deprecate GitHub Copilot compatibility surfaces (#1196) 2026-05-07 13:06:03 -04:00
Kai (Tam Nhu) TranandGitHub 8b681df455 fix: route Cursor auth through browser polling
Closes #1194
2026-05-07 11:25:17 -04:00
Kai (Tam Nhu) TranandGitHub 61390e5691 fix(cliproxy): diagnose Gemini Plus OAuth credentials
Closes #1131
2026-05-07 11:05:53 -04:00