Skip to content

[Roadmap] Centrally hosted multi-user OpenCodex with tenant isolation #95

Open
1 of 1 issue completed

Description

@rafalkwol

Area

Server architecture / authentication / multi-user hosting / policy / observability

Goal

Support OpenCodex as a centrally hosted proxy for multiple independent users without leaking credentials, usage, request state, routing policy, or administrative capabilities across tenants.

The intended deployment is:

many Codex Desktop / CLI clients
            ↓
central OpenCodex instance
            ↓
native ChatGPT passthrough + routed providers / gateways

Each user may bring their own ChatGPT subscription credentials while the operator centrally manages additional routed models such as a LiteLLM gateway.

This issue is the long-term tracker for making that deployment a supported OpenCodex architecture rather than a collection of reverse-proxy workarounds.

Current status

A substantial part of the original use case already works.

Already available

OpenCodex can already support important pieces of a centrally hosted deployment:

  • non-loopback proxy binding;
  • authenticated data-plane access;
  • ChatGPT credential forwarding without OpenCodex taking ownership of the user's ChatGPT credential;
  • routed OpenAI-compatible providers such as a centrally managed LiteLLM gateway;
  • remote /v1/models model discovery;
  • background catalog refresh;
  • standard reverse-proxy/TLS deployment in front of OpenCodex.

Those foundations mean the remaining problem is not simply “make OpenCodex reachable over the network.”

The remaining problem is safely introducing a first-class tenant boundary.

Remaining architecture gap

Current OpenCodex still fundamentally operates as a single-user proxy.

Multiple remote users can reach the same server, but there is no complete security and policy model ensuring that one user's activity is isolated from another.

The remaining design must cover at least:

tenant identity
tenant authentication
tenant authorization
routing policy
credential ownership
request / usage attribution
account-pool boundaries
management-plane isolation
concurrency / capacity controls
auditability

1. Tenant identity

Every request must have a trustworthy tenant identity established before tenant-specific policy is applied.

Do not derive authorization identity from caller-controlled informational headers alone.

Possible identity sources may include:

  • a dedicated per-user OpenCodex client credential;
  • an authenticated reverse-proxy identity;
  • another explicit identity contract designed for centrally managed deployments.

chatgpt-account-id may be useful as provider metadata, but it must not automatically become the OpenCodex authorization principal unless that relationship is explicitly proven and secured.

Likewise, do not parse arbitrary OAuth token contents merely to guess a user identity.

2. Admission credential versus forwarded provider credential

A centrally hosted deployment may need both:

OpenCodex admission identity
+
user ChatGPT Authorization credential

These are separate security concepts.

OpenCodex admission must not consume, replace, or reinterpret the user's forwarded ChatGPT bearer token.

Likewise, the upstream ChatGPT credential must not accidentally grant OpenCodex management access.

Tests must establish the separation directly.

3. Tenant authorization and model policy

The operator should be able to define which routes each tenant may use.

Examples:

User A
  native ChatGPT
  litellm/claude-sonnet
  litellm/gpt-x

User B
  native ChatGPT
  litellm/internal-small-model

The exact policy format can be designed separately, but tenant identity must be able to constrain:

  • providers;
  • models;
  • routing profiles;
  • possibly account pools or quotas;
  • other tenant-scoped capabilities introduced later.

A user must never gain access merely because a model exists in the server-wide catalog.

4. Credential isolation

User-owned credentials must remain associated with the correct tenant.

This includes any future centrally managed representation of:

  • ChatGPT/OAuth identity;
  • provider OAuth accounts;
  • provider API keys;
  • account-pool membership.

Tenant A must not be able to:

  • use Tenant B's credential;
  • see Tenant B's credential metadata;
  • trigger Tenant B's account selection;
  • inherit Tenant B's affinity/cooldown state.

Where ChatGPT credentials remain pure forward-auth, OpenCodex should continue avoiding persistence unless the architecture explicitly requires otherwise.

5. Request and usage isolation

Current single-user request/usage views are insufficient for supported multi-user hosting.

Request history and usage need a tenant attribution contract that is:

  • trustworthy;
  • bounded;
  • privacy-safe;
  • usable for filtering and auditing.

At minimum, operators should be able to answer:

Which tenant generated this request?
Which model/provider did it route to?
How much usage did that tenant generate?
Which failures belong to that tenant?

A normal tenant must not be able to read another tenant's request or usage history.

Raw provider credentials, prompts, or other private payloads must not become necessary for attribution.

6. Management-plane boundary

The OpenCodex management API remains an operator surface.

A normal inference client must not gain management privileges simply because it is a valid tenant.

Tenant credentials should not automatically authorize:

/api/config
/api/providers
/api/oauth/*
/api/codex-auth/*
/api/stop

or equivalent administrative operations.

Any future tenant self-service API should be deliberately scoped rather than implemented by weakening /api/*.

7. Catalog distribution

Remote clients need the generated Codex catalog without receiving management credentials.

That focused least-privilege problem is tracked separately in:

#809

#809 owns the proposed read-only data-plane:

GET /v1/catalog

This roadmap issue should consume that capability when it lands rather than duplicating catalog-distribution policy here.

8. Catalog and model visibility

A server-wide catalog and a tenant-visible catalog may eventually differ.

If tenant model authorization exists, the client-facing model surface must not advertise routes that the tenant cannot actually invoke.

The implementation must define whether filtering occurs at:

  • /v1/models;
  • /v1/catalog;
  • both;
  • or another canonical tenant projection.

Catalog filtering and request admission must agree.

9. Routing and account pools

Multi-user hosting must define which routing state is:

global
tenant-scoped
conversation-scoped
account-scoped

Examples include:

  • route selection;
  • conversation affinity;
  • provider cooldowns;
  • OAuth account selection;
  • API-key pools;
  • quotas.

A global upstream-health observation may legitimately be shared.

A tenant's identity, affinity, credential, or entitlement must not be shared accidentally.

The generic OAuth-pool work in #695 should remain reusable rather than becoming a second tenant-routing implementation.

10. Concurrency and capacity

Supported multi-user hosting needs explicit load validation.

The server must define and test behavior when several users simultaneously:

  • stream long Responses turns;
  • invoke tools;
  • reconnect;
  • trigger retries;
  • use different providers;
  • consume account pools.

Required safeguards may include:

  • global concurrency limits;
  • tenant-level limits;
  • bounded queues or fail-fast admission;
  • memory/stream budgets;
  • fair scheduling where appropriate.

This should align with the bounded-concurrency work tracked in #820 rather than creating an independent resource model.

11. Reverse proxy / TLS deployment

Production multi-user deployments will normally require TLS and may use an external reverse proxy or authenticated network layer.

Documentation should cover a supported deployment shape without making one specific product mandatory.

Examples may include:

nginx
Caddy
Tailscale / private network
enterprise ingress

OpenCodex must not assume that the apparent socket peer alone establishes the end-user identity.

Forwarded identity headers should only be trusted under an explicit trusted-proxy contract.

Security invariants

A supported multi-user mode must prove:

  • Tenant A cannot invoke a model forbidden to A.
  • Tenant A cannot use Tenant B's credentials.
  • Tenant A cannot inspect Tenant B's requests or usage.
  • Tenant A cannot mutate server configuration.
  • Tenant identity cannot be forged through an untrusted header.
  • Forwarded ChatGPT OAuth credentials remain distinct from OpenCodex admission credentials.
  • Provider/account affinity cannot cross tenant boundaries accidentally.
  • Tenant-visible catalog entries agree with tenant request authorization.
  • Logs do not expose authentication secrets.

Suggested delivery strategy

This should remain a roadmap/architecture program rather than one giant PR.

Useful focused slices include:

  1. tenant identity and admission contract;
  2. tenant authorization/model policy;
  3. tenant-safe request and usage attribution;
  4. tenant-aware catalog projection;
  5. routing/account-pool isolation;
  6. concurrency/resource policy;
  7. supported remote deployment documentation and acceptance harness.

Security-sensitive slices should receive explicit maintainer review.

Related work

Out of scope

This issue does not require:

  • OpenCodex centrally owning every user's ChatGPT OAuth credential;
  • weakening the management API for remote users;
  • trusting arbitrary user-id headers;
  • making public-internet exposure the default;
  • embedding a specific commercial reverse proxy;
  • solving every provider-specific account-management feature here.

Acceptance criteria

Foundations

  • Non-loopback hosting is possible.
  • Data-plane admission authentication exists.
  • ChatGPT forward-auth can preserve caller credentials.
  • Additional OpenAI-compatible gateways can be routed centrally.
  • Remote model discovery through /v1/models exists.
  • Catalog refresh can be managed centrally.

Remaining multi-user contract

  • A trustworthy tenant identity contract exists.
  • Tenant admission and forwarded provider credentials are independently represented.
  • Per-tenant model/provider authorization exists.
  • Tenant credential/account state cannot cross identities.
  • Request history is safely tenant-attributed.
  • Usage is safely tenant-attributed.
  • Normal tenants cannot access management-only APIs.
  • Tenant-visible model/catalog projections agree with authorization.
  • OAuth/account-pool affinity is safe across tenants.
  • Concurrency and resource behavior is explicitly bounded and tested.
  • Trusted-proxy / forwarded-identity semantics are documented.
  • Multi-user negative security tests prove cross-tenant isolation.
  • A supported centrally hosted deployment is documented.
  • Accepted implementation is merged into dev.

Close condition

Close this roadmap issue only when centrally hosted multi-user OpenCodex is a supported architecture with tested tenant isolation and authorization.

Individual focused issues such as #809 may close earlier without closing this tracker.

Activity

  1. lidge-jun commented on Jul 11, 2026

    @lidge-jun
    Owner

    Hey @rafalkwol, thanks for the kind words and the clear question.

    Short answer: the architecture already supports most of what you described, but opencodex is currently designed as a personal proxy (single-user, loopback). Multi-user hosting is on the roadmap — I'm planning to patch toward that direction and will keep this issue open to track it.

    What already works today

    • ChatGPT auth passthrough — the built-in ChatGPT provider uses authMode: "forward", which relays the caller's Authorization and chatgpt-account-id headers verbatim to upstream. The proxy never stores or manages ChatGPT credentials; each user's own OAuth token flows through untouched.
    • LiteLLM routing — register your LiteLLM instance as an openai-chat adapter provider in the opencodex config. All non-ChatGPT model requests route there.
    • Non-loopback binding — set "hostname": "0.0.0.0" and OPENCODEX_API_AUTH_TOKEN to expose the proxy on the network. The token protects both management and data-plane endpoints.
    • Model catalog — the /v1/models endpoint serves the merged catalog (native gpt + routed LiteLLM models) over HTTP, so remote Codex clients see the expanded picker.

    The gap: admission auth vs. ChatGPT auth

    When bound to a non-loopback address, opencodex requires an admission token (x-opencodex-api-key header or Authorization: Bearer <token>) on every request. The problem is that Codex Desktop sends the user's ChatGPT OAuth token in the Authorization header — there's no built-in mechanism in codex-rs to send both the admission token and the ChatGPT token simultaneously without modifying the Codex client.

    Recommended workaround (no codex-rs modification): put a reverse proxy (nginx, Caddy, etc.) in front of opencodex that:

    1. Terminates TLS (you'll want this anyway — ChatGPT OAuth tokens should not travel in plaintext).
    2. Injects the x-opencodex-api-key header from a shared secret or per-user auth layer.
    3. Forwards the original Authorization header untouched so ChatGPT passthrough still works.

    This keeps codex-rs completely unmodified on each user's machine — they only need openai_base_url pointing at your reverse proxy, plus a model_catalog_json pointing at a catalog file you distribute (or they can fetch it from the proxy's /v1/models).

    Caveats to be aware of

    • No per-user isolation — dashboard, usage logs, and request logs currently merge all traffic into a single view. There's no user/tenant concept yet.
    • V2 sub-agents across providers — there's a known issue (V2 cross-provider sub-agent loses NEW_TASK body in encrypted_content #92) where native-parent → routed-child sub-agents lose the task body in encrypted_content. If your users rely on sub-agents hitting LiteLLM models, this is a blocker until patched.
    • Catalog distribution — each Codex client needs a local opencodex-catalog.json (or model_catalog_json in config.toml) for the model picker to show routed models. You can automate this by having your reverse proxy serve it, or by distributing the file alongside the config.

    If you want to implement this now with AI assistance

    If you'd like to fork and customize opencodex for your multi-user setup, here's a detailed prompt you can give to an AI agent to implement the changes. This approach keeps codex-rs completely unmodified — all changes are proxy-side:

    AI implementation prompt
    I need to modify opencodex to support centralized multi-user hosting behind a
    reverse proxy. All changes must be proxy-side only — codex-rs on client machines
    stays unmodified. Here is the scope:
    
    1. ADMISSION AUTH SEPARATION
       - In src/server/auth-cors.ts, modify hasValidApiAuth() to check
         x-opencodex-api-key header FIRST and independently from Authorization.
         Currently it falls through to Authorization, which conflicts with ChatGPT
         OAuth tokens on the same header.
       - When x-opencodex-api-key is present and valid, admission is satisfied
         regardless of the Authorization header value.
       - When x-opencodex-api-key is absent but isApiAuthRequired() is true,
         check if the Authorization bearer token matches the admission secret.
         If not, reject with 401 — do NOT accidentally accept a ChatGPT OAuth
         token as an admission token.
    
    2. FORWARDED AUTH PRESERVATION
       - In src/codex/auth-context.ts, headersForCodexAuthContext() must always
         preserve the original Authorization header from the incoming request for
         kind: "main" (passthrough) context, even when admission was satisfied
         via a separate x-opencodex-api-key header.
       - Verify that FORWARD_HEADERS in src/adapters/openai-responses.ts still
         includes "authorization" and that the ChatGPT OAuth token reaches
         upstream intact.
    
    3. REQUEST LOGGING — OPTIONAL USER TAGGING
       - Add an optional x-opencodex-user-id header (or extract from the
         chatgpt-account-id header) to tag request logs with a user identifier.
       - This is informational only — no authorization decisions based on it.
       - Surface the user tag in the dashboard request log table if present.
    
    4. DO NOT MODIFY
       - codex-rs client configuration beyond openai_base_url and
         model_catalog_json.
       - The ChatGPT OAuth flow or token storage.
       - The FORWARD_HEADERS list (it already covers what is needed).
       - Any provider adapter logic.
    
    5. TESTING
       - Verify: a request with valid x-opencodex-api-key + ChatGPT OAuth in
         Authorization reaches the ChatGPT backend with the OAuth token intact.
       - Verify: a request with NO x-opencodex-api-key and a ChatGPT OAuth token
         in Authorization is rejected (401) when hostname is non-loopback.
       - Verify: loopback mode still works with no admission token at all.
    
    The key files to modify are:
    - src/server/auth-cors.ts (hasValidApiAuth, requireApiAuth)
    - src/codex/auth-context.ts (headersForCodexAuthContext)
    - src/server/request-log.ts (optional user tagging)
    - gui/src/components/RequestLogTable (optional user column)
    

    What I'd like from you

    Could you describe your desired environment more concretely? Specifically:

    • How many users roughly, and are they all on the same LAN or distributed (VPN/Tailscale/public)?
    • Do you need per-user access control (e.g., some users can access certain LiteLLM models, others can't)?
    • Are you planning to manage the ChatGPT subscriptions centrally, or does each user bring their own logged-in Codex Desktop?
    • Any preference on the reverse proxy layer (nginx, Caddy, Cloudflare Tunnel, etc.)?

    This will help me scope the multi-user patch properly. I'll keep this issue open and use it to track the work.

  2. rafalkwol commented on Jul 11, 2026

    @rafalkwol
    Author

    Thank you for a very detailed answer.

    From what I see it basically has nearly everything I need. Currently the issue with dashboard being single user is not a problem as I'm not planning to use it as I only need the proxy functionallity and proper routing of requests between models and ChatGPT subscrption with handling of image-gen (which opencodex seems to already handle). The blocked you mentioned is something I guess will probably be fixed sooner or later so also not a big issue.

    The catalog distribution via file is the most "annoying" thing as I'm planning to change the models server by LiteLLM pretty frequently and would prefer users to not have to do anything at all to get the changes after initial config of the "openai_base_url" which I can set for them from the domain level probably.

    The whole idea of this is like this:

    • The company I work for uses ChatGPT Business for all our employees. This is a subscription that is quite fine for all the employees that don't use Codex very frequently and they are doing fine with their default limits. But more and more of our employees use Codex a lot and they often hit the default limits. We don't want to pay for the OpenAI credits as it's simply too expensive for the size of our company and also we can't switch to Enterprise subscription as it will also be too expensive.
    • To countermeasure this situation I provisioned some hardware that will allow us to host different local models that will have good enough quality to handle most of the typical Codex work for our power-users. I was planning to proxy all those models on different machines (we have 3 separate machines with GPUs of different quality) under one LiteLLM instance so I can manage this centally with proper user access permissions and also having some models that will not be available via Codex, but will be used for internal automations in n8n and some other tools.
    • The issue I encountered is the way Codex handles external models providers. As you for sure know it has this akward way of enabling other models via the *.toml file which also causes by default the ChatGPT subscription models to disappear. I really want to provide a solution for our employees that will be seamless for them so they will just see additional models in their model picker on top of the models they already have from their subscription. When their subscription limit runs out they can just simply switch to local models and continue their work with a bit worse quality.

    Now to answer your questions:

    • The number of users will be somewhere between 90 to 150, but it's probably never will be exactly at the same moment and requests will just come in smaller batches, because it's not like everyone use Codex 24/7. All of the employees work over LAN if they are in the office and those that are not will be connected via VPN. I'm also considering in the future I may make the endpoint for access public and just have some kind of API key authorization layer in the middle, but it's not a priority right now. We have all the infrastruction on-premise in our company and we don't use any cloud providers.
    • I would like to have the ability to give users access to specific models. I was planning to do this somehow via LiteLLM and I just need something that I can use to differentiate users and it should be a simple thing after I get something like this. I was planning to extract some kind of identifier of the user from their ChatGPT subscription token if it will be possible, but didn't analyze it yet if it's possible.
    • I don't want to manage ChatGPT subscriptions centally by default. I want them to normally login to Codex on their own and just use their auth credentials sent to the proxy to authenticate them in ChatGPT subscription and maybe as I mentioned in previous point also extract some identifying information from this token. In the future it may be nice to be able to buy some additional subscriptions for ChatGPT and give access to other users to a shared additional pool, but it's not something I was originally planning, but after I saw this functionallity in opencodex I though it's a nice idea and the only issue I see they threads being stuck to some other random subscription then the one assigned to the employee.
    • I like to work with nginx as a proxy as I know it best from the ones you mentioned.

    Again, thanks for your work. This is already a really good solution and if push comes to shove I will try to tinker with it myself if I will miss something that won't be planned by the roadmap.

    Best regards.

  3. Wibias commented on Jul 12, 2026

    @Wibias
    Contributor

    @rafalkwol

    I tested the suggested nginx workaround locally on Windows and can confirm that it works in practice.

    Test environment:

    • Windows 10.0.26200
    • Codex Desktop 0.144.0-alpha.4 / 26.707.51957
    • OpenCodex v2.7.8
    • nginx 1.27-alpine running in Docker Desktop
    • OpenCodex on port 10100
    • nginx exposed locally on 127.0.0.1:18080

    Configuration:

    • nginx injected x-opencodex-api-key
    • the original Authorization header was preserved
    • chatgpt-account-id was forwarded unchanged
    • Codex Desktop used the nginx endpoint instead of connecting to OpenCodex directly

    Successfully tested:

    • merged model catalog via GET /v1/models
    • routed Anthropic requests via POST /v1/responses
    • native OpenAI requests with gpt-5.4
    • switching from an Anthropic model to a native OpenAI model inside an existing thread
    • image generation via POST /v1/images/generations using gpt-image-2

    All tested requests returned HTTP 200.

    Example nginx log entries:

    GET /v1/models HTTP/1.1" 200
    POST /v1/responses HTTP/1.1" 200
    POST /v1/images/generations HTTP/1.1" 200
    

    This confirms that the proposed request flow works in a real single-user Windows setup:

    Codex Desktop -> nginx -> OpenCodex -> provider
    

    Limitations of this test:

    • single user only
    • no simultaneous multi-account testing
    • no per-user isolation or permission testing
    • no concurrency or load testing
    • no LAN, VPN, or public deployment testing
    • no dynamic remote model-catalog refresh testing

    So this does not validate the full multi-user production scenario yet, but it does confirm that the nginx admission-auth workaround works with native OpenAI models, routed providers, model switching, and image generation.

  4. Wibias commented on Jul 13, 2026

    @Wibias
    Contributor

    I also tested how provider and model catalog changes propagate from Ollama through OpenCodex into Codex Desktop.

    Test setup

    • Windows 10.0.26200
    • Codex Desktop 0.144.0-alpha.4 / 26.707.51957
    • OpenCodex v2.7.9
    • Ollama running locally on http://127.0.0.1:11434
    • nginx in front of OpenCodex as described in my previous comment

    Adding a new Ollama model

    Ollama immediately exposed the newly added model through its OpenAI-compatible endpoint:

    Invoke-RestMethod http://127.0.0.1:11434/v1/models

    However, the model did not appear immediately in the OpenCodex catalog:

    (Invoke-RestMethod http://127.0.0.1:18080/v1/models).data |
        Where-Object id -Like "ollama/*" |
        Select-Object id

    I then monitored the OpenCodex catalog without restarting OpenCodex and without running another sync.

    The new model appeared automatically after:

    264.8 seconds
    

    This suggests that OpenCodex refreshes provider model catalogs in the background, likely on an interval of approximately five minutes.

    ocx sync behaviour

    During the test, running:

    ocx sync

    did not cause the newly added Ollama model to appear immediately in /v1/models.

    The model appeared later through the background refresh.

    So ocx sync appears to write the model catalog currently known to OpenCodex into the local Codex catalog, but it does not necessarily force an immediate synchronous provider rediscovery first.

    Codex Desktop model picker

    Once the model appeared in OpenCodex /v1/models, the running Codex Desktop application still did not update its model picker.

    The new model appeared only after fully restarting Codex Desktop.

    The same behaviour was observed when removing a model:

    • the removed model disappeared from OpenCodex after the provider refresh
    • it remained visible in the running Codex Desktop model picker
    • it disappeared after restarting Codex Desktop

    The same restart requirement also applied when adding the Ollama provider itself.

    Observed refresh flow

    Provider or model change in Ollama
    -> OpenCodex background catalog refresh
       observed delay: 264.8 seconds
    -> Codex Desktop restart
    -> updated model picker
    

    Practical impact

    This means centralized model changes are possible without manually editing or redistributing a new catalog file for every individual model change.

    However, the experience is not fully dynamic from the end user's perspective:

    • provider changes can take several minutes to appear in OpenCodex
    • Codex Desktop does not refresh the model picker while running
    • users must restart Codex Desktop to see added or removed models

    For the multi-user scenario discussed in this issue, this is still workable, but users would need to be told to restart Codex Desktop after model catalog changes.

  5. rafalkwol commented on Jul 13, 2026

    @rafalkwol
    Author

    @Wibias Thanks for the tests you made. From their conclusion it seems to me the only things left to validate for MVP scenario is concurrent multi-user access while hosting opencodex on remote server and also the possibility of extracting some user identifying information from their ChatGPT auth token.

  6. Wibias commented on Jul 13, 2026

    @Wibias
    Contributor

    Agreed. I can validate concurrent request handling in a controlled local setup using multiple Codex Desktop/CLI clients against the same nginx and OpenCodex endpoint.

    That would cover concurrent native, routed and image-generation requests, but it would still use a single ChatGPT identity. A true multi-user validation would require at least two separate ChatGPT accounts and isolated Codex client profiles, so the original company environment may be better suited for that final test.

    For user identification, I think it would be safer to investigate the forwarded chatgpt-account-id header before attempting to parse the OAuth bearer token itself.

    The questions I would validate are:

    • whether chatgpt-account-id is present on forwarded ChatGPT requests
    • whether it is stable across sessions for one user
    • whether it differs between users
    • whether OpenCodex can derive and forward a non-reversible internal identifier, such as a salted hash
    • whether that identifier can be passed to LiteLLM for per-user access policies

    I would avoid logging or storing the raw Authorization header. If chatgpt-account-id is not sufficiently trustworthy or stable, the cleaner production design would probably be to establish user identity at nginx through company SSO or VPN authentication and pass a trusted internal user header to OpenCodex/LiteLLM.

  7. rafalkwol commented on Jul 13, 2026

    @rafalkwol
    Author

    Thanks for the summary. I was thinking about the chatgpt-account-id also, but i'm not sure it will be easy to map to user identities without some manual work. I'm also thinking about injecting the salted hash into the link of openai_base_url which I would populate for each user from domain so I would be able to embedd their identity into the link which I would than extract on the proxy level. OAuth token parsing may indeed not be a good idea to go for as it's probably not realiable and may not have any easly identifiable information.

    I will try out those ideas when I get to it in our internal environment. It's still some time to go as right now we are working on hosting the local model on the provisioned hardware, but after we get the LiteLLM gateway fully running we will go for opencodex next. Just like to plan ahead of time. :) Thanks the time spent on this.

  8. lidge-jun commented on Jul 22, 2026

    @lidge-jun
    Owner

    Status: this remains on the long-term roadmap. Admission auth and catalog refresh already work today (as covered earlier in this thread); the remaining gap is the genuinely hard part — tenant identity/isolation, per-user authorization and policy, trustworthy user attribution, and concurrency/load validation. That's project-scale design work rather than a patch, and the single-user assumptions run deep enough that we'd rather design it properly than ship a mode that leaks state across users. Keeping open as the tracking issue.

  9. added
    roadmapAccepted long-term work item; needs its own project cycle
    enhancementNew feature or request
    on Jul 22, 2026
  10. added
    platformOS/service/tray/ACL (Windows-heavy, not Windows-only)
    proxyHTTP proxy, routing, reverse-proxy / management auth
    on Aug 3, 2026
  11. Wibias commented on Aug 8, 2026

    @Wibias
    Contributor

    Follow-up tracking

    The original multi-machine hosting use case now has focused implementation issues.

    Current follow-up:

  12. github-actions commented on Aug 8, 2026

    @github-actions
    Contributor

    Automated translation bookkeeping — detected language: English.

  13. changed the title [-]Hosting opencodex as a proxy for multiple users with ChatGPT subscription passthrough and LiteLLM integration[/-] [+][Roadmap] Centrally hosted multi-user OpenCodex with tenant isolation[/+] on Aug 9, 2026
  14. removed
    platformOS/service/tray/ACL (Windows-heavy, not Windows-only)
    on Aug 9, 2026
  15. lidge-jun commented on Aug 19, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 54 / 80

    현재 dev는 중앙 호스팅의 조각은 있습니다. 비루프백 바인드, 데이터 플레인 인증, ChatGPT 자격 증명 전달, 라우트 프로바이더, 원격 /v1/models, 카탈로그 새로고침, 앞단 TLS 프록시가 그것입니다. 서버는 여전히 단일 사용자 프록시입니다. src/server/index.ts는 원격 클라이언트가 공유/멀티테넌트 호스트용이 아니라고 경고하고, 설정 주석도 기본 끔·멀티테넌트 아님을 반복합니다. 테넌트 신원·인가·자격 증명 소유권·사용 귀속·관리 평면 격리의 1급 모델은 없습니다.

    이슈가 원하는 것은 여러 Codex 클라이언트가 한 OpenCodex를 쓰되 자격 증명, 사용량, 요청 상태, 라우팅 정책, 관리 능력이 새지 않는 것입니다. 사용자는 자기 ChatGPT를 가져오고 운영자는 LiteLLM 같은 라우트 모델을 중앙 관리할 수 있습니다. 남은 문제는 도달 가능성이 아니라 테넌트 경계입니다.

    네이티브 신뢰성과 위생이 점수입니다. 호출자가 넣는 정보 헤더만으로 인가 신원을 만들면 안 됩니다. 계정 풀과 Usage는 지금 프로세스 전역이라 테넌트 필터 없이 열면 옆 사용자 사용량이 보입니다. Windows/서비스 동시성(#820)과 데이터 플레인 카탈로그(#809)는 자식이지, 이 우산을 한 PR로 닫지 않습니다.

    이미 있는 nginx x-opencodex-api-key 우회는 MVP 도달은 되지만 테넌트 격리는 아닙니다. 메인테이너가 말한 대로 프로젝트 규모 설계이고 단일 사용자 가정이 코드 곳곳에 있습니다. 해시된 URL 신원이나 chatgpt-account-id 매핑은 설계 메모이지 구현 허가가 아닙니다.

    해결방안은 테넌트 신원 계약과 관리/데이터 평면 분리를 먼저 고정하고, #809 같은 최소 권한 슬라이스만 지금 구현하는 것입니다. 전역 계정 풀과 Usage를 테넌트화하기 전에 멀티유저 UI를 열지 마십시오. 패치는 추측하지 않으며, 현재 트리를 멀티테넌트 아키텍처로 보지 않습니다.

    이 댓글은 grok-bot이 작성했습니다

  16. parkjs101 commented on Aug 21, 2026

    @parkjs101
    Contributor

    Code-grounded status: attribution exists, but nothing consumes it

    I walked the current dev tree against this issue's remaining checklist and had an
    independent reviewer verify every reference below. Posting the code-level detail because
    it narrows where the tenant contract has to be introduced.

    1. A server-derived key identity already exists for configured keys

    apiKeys is a multi-entry array with stable per-key ids, and admission already names the
    matched key instead of collapsing to a boolean:

    • src/types/config.ts:217-222 — OcxApiKeyEntry { id; name; key; createdAt }
    • src/server/auth-cors.ts:338-349 — resolveDataPlaneAdmissionSecret() compares with
      secretEquals/timingSafeEqual and returns { kind: "configured", keyId: k.id }
    • src/server/auth-cors.ts:357-370 — admissionFields() projects it to
      { admissionKind, apiKeyId }

    This matters for §1's constraint: for configured keys the principal comes from a
    server-held secret comparison, not a caller-asserted header, so no chatgpt-account-id
    trust and no OAuth introspection is required to name the caller.

    Two limits worth stating plainly. Environment-token and loopback admissions carry no
    key id at all (src/server/auth-cors.ts:345, and the loopback/environment variants at
    :324-327), so they cannot be tenants as-is. And this is identification only — there is
    no policy contract anywhere.

    Attribution already reaches storage: src/server/request-log.ts:55,133,373,944,
    src/usage/log.ts:77,442, indexed as api_key_id in src/routing/history/indexer.ts:62,164
    with a filter at :518.

    2. apiKeyId is recorded after the fact, not carried through the request

    • No policy fields exist. OcxApiKeyEntry (src/types/config.ts:217-222) has no
      allowedModels/allowedProviders.
    • History accepts it as a query filter.
      src/server/management/request-history-routes.ts:95-101 reads apiKeyId from the URL
      query (line 100) and passes it to the SQL filter at src/routing/history/indexer.ts:518.
      The cited code shows no authenticated-scope override, so the parameter constrains the
      query rather than the caller. That is correct for an operator surface; it is the piece
      that would need inverting before any tenant-facing history read.

    The shape this suggests is an immutable TenantContext derived at admission and threaded
    through authorization, routing, catalog projection, history/usage, pool state, and
    concurrency — rather than reusing apiKeyId as a log field.

    3. Process-global routing state, and where it would need tenant keying

    Each row below is a verified global declaration. The failure column is the consequence
    if that map's keying stays tenant-blind, not a demonstrated cross-tenant defect today
    — the proxy is single-user, so these are correct as written.

    State Location If left tenant-blind
    Codex thread affinity src/codex/routing.ts:160-164, :832-835 keyed threadId + quota scope, no tenant dimension
    Pool rotation src/codex/pool-rotation.ts:13-14 rotation cursor shared across callers
    Quota src/codex/quota.ts:88 counters shared across callers
    Anthropic health + session affinity src/oauth/anthropic-routing.ts:65-66 affinity may cross callers
    Key failover cooldown src/providers/key-failover.ts:38 one caller's cooldown may apply to another
    Request pacing src/providers/request-pacing.ts:68 one caller's rate may shape another

    I verified the declarations; I did not trace every key-use site, so treat the right
    column as the design obligation rather than a proven leak.

    Prior art exists in-tree: src/adapters/cursor.ts:84-86 namespaces thread scope by
    authenticated tenant so "shared-proxy tenants ... cannot collide". Codex-side routing has
    no equivalent dimension.

    4. Sequencing corrections

    Catalog projection should not ship ahead of enforcement. Filtering /v1/models first
    looks low-risk because it has no denial path, but hiding a model while the call still
    succeeds establishes that invisible means blocked when it does not. Landing visibility and
    admission together from one policy is also what makes §8's "catalog filtering and request
    admission must agree" hold by construction.

    A single shared resolver is not currently callable. /v1/models resolves admission at
    src/server/index.ts:898-905, then assembles models, entitlements, native rows, combos and
    account-qualified selectors in later branches. Threading context through those call sites
    is prerequisite work, not a side effect of adding one function.

    5. Two invariants any design has to preserve

    • Loopback policy must not become remotely inheritable. The loopback allowlist
      includes GET /v1/models (src/server/index.ts:610-634) while the remote path requires
      admission (:898-905). Those must stay distinct.
    • Bearer-domain separation must survive per-tenant keys. AUTH_MATRIX
      (src/server/auth-cors.ts:401) accepts a bearer that is one of the proxy's own secrets,
      resolved at :461-472; it is safe because pool/main credentials are substituted rather
      than forwarded (src/codex/auth-context.ts:585-605, noting the final branch returns
      selected headers for passthrough). Adding per-tenant keys must not blur that boundary.

    6. Suggested slice order

    1. Immutable admission-derived TenantContext + explicit policy contract
    2. Negative tests first: model authorization, catalog/admission agreement, history
      scoping, credential non-forwarding, cross-tenant affinity
    3. Fail-closed request authorization
    4. Catalog projection from the same policy — same deployable slice as (3)
    5. History/usage scoping, with authenticated scope overriding the query filter, and
      operator-only management reads kept distinct from any tenant self-service read
    6. Tenant-key the maps in the table above, after tracing their key-use sites
    7. Per-tenant concurrency limits at real dispatch boundaries (aligns with [architecture][memory] Make 32 concurrent tool-recall sessions protocol-safe and memory-bounded #820)

    On defaults: "absent policy means unrestricted" would contradict the fail-closed stance
    above, so it should be scoped as an explicit legacy exception — unrestricted only for
    existing loopback/single-user operation, while tenant mode rejects a key with no policy.

    Notes for adjacent issues

    Optional centrally managed pools. Out of scope already rules out OpenCodex centrally
    owning every user's ChatGPT credential, and the premise is that users forward their own.
    What still needs a decision for slice 6 is the optional operator-managed pool that #695
    covers: whether a centrally held account pool may coexist with forwarded per-user
    credentials, since tenant-keying the pool differs depending on that answer.

    For #809. Codex app-server reads the on-disk catalog once at startup and does not
    reread it (src/codex/app-server-restart-service.ts:3-7), so a single shared catalog file
    cannot express per-tenant visibility. A read-only data-plane endpoint looks like the only
    carrier that can.

  17. Ingwannu commented on Aug 21, 2026

    @Ingwannu
    Owner

    Thanks for the code-grounded follow-up. This is a useful narrowing of the roadmap.

    The key distinction is exactly the one you called out: the admission-derived apiKeyId gives us attribution, not authorization. It is currently a logging/query dimension and must not be treated as a tenant principal or policy decision by itself.

    The right first implementation slice is an immutable TenantContext derived only from trusted admission state, paired with one explicit policy contract. The negative tests should come first and cover at least model denial, catalog/admission agreement, history scoping, credential non-forwarding, and cross-tenant affinity isolation. Catalog filtering and request enforcement should land from the same policy boundary rather than as separate promises.

    This does not approve a specific implementation yet, and the broader multi-user roadmap remains open: the global routing/pool/usage/concurrency state still needs a complete ownership and isolation design before the proxy can be described as multi-tenant. But this analysis gives us a much better boundary for a future focused proposal.

  18. added
    priority: P3Low: new provider/client integration, large or experimental feature (>2000 LOC or >50 files), RFC/ro
    on Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpriority: P3Low: new provider/client integration, large or experimental feature (>2000 LOC or >50 files), RFC/roproxyHTTP proxy, routing, reverse-proxy / management authroadmapAccepted long-term work item; needs its own project cycle

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions