Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
25c784f
feat(hardening): add recoverable data key rotation API
Aug 27, 2026
b829320
feat(hardening): bound pairing attempts and add session logout
Aug 27, 2026
10a3198
feat(hardening): validate remote protocol catalog and relay
Aug 27, 2026
2ea6313
feat(hardening): recover client key rotation through token backup
Aug 27, 2026
b12093b
feat(hardening): add api key rotation controls
Aug 27, 2026
be2ee7d
test(hardening): cover rotation pairing catalog and relay negatives
Aug 27, 2026
913d2a5
docs(i18n): synchronize remote hub hardening across locales
Aug 27, 2026
88d9889
fix(hardening): gate startup on rotation recovery state
Aug 27, 2026
9bded9c
feat(hardening): expose remote session logout control
Aug 27, 2026
ad7acdb
fix(hardening): keep one-time rotation secret visible
Aug 27, 2026
c693700
fix(hardening): reject mismatched catalog validators
Aug 27, 2026
e5bca8d
test(hardening): cover subprocess protocol skew matrix
Aug 27, 2026
bd0064d
test(hardening): reject rotation secrets and revoke ids in argv
Aug 27, 2026
9088d60
fix(hardening): bound remote session logout request
Aug 27, 2026
54d799c
test(hardening): keep rotation evidence secret-free
Aug 27, 2026
c22792e
fix(hardening): drop a stray import fragment and guard an undefined r…
Aug 27, 2026
5da9f17
fix(hardening): repair phase six full-suite regressions
Aug 27, 2026
6a71a41
fix(hardening): confirm the abort before rewinding, and never delete …
Sep 1, 2026
0e443d8
fix(hardening): declare the four routes this phase adds
Sep 1, 2026
dd8c3e6
feat(cli): declare ocx connect rotate as a capability
Sep 1, 2026
efefe36
fix(hardening): keep the remaining two-plane UI off a standalone install
Sep 1, 2026
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/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export default defineConfig({
label: "Guides",
translations: { fr: "Guides", ko: "가이드", "zh-CN": "指南", "zh-TW": "指南", ru: "Руководства", ja: "ガイド", tr: "Kılavuzlar" },
items: [
{ label: "Remote Hub Deployment", slug: "guides/remote-hub" },
{ label: "Remote Hub Deployment", translations: { fr: "Déploiement Remote Hub", ko: "Remote Hub 배포", "zh-CN": "Remote Hub 部署", "zh-TW": "Remote Hub 部署", ru: "Развёртывание Remote Hub", ja: "Remote Hub のデプロイ", tr: "Remote Hub Dağıtımı" }, slug: "guides/remote-hub" },
{ label: "Providers", translations: { fr: "Fournisseurs", ko: "프로바이더", "zh-CN": "提供商", "zh-TW": "供應商", ru: "Провайдеры", ja: "プロバイダー", tr: "Sağlayıcılar" }, slug: "guides/providers" },
{ label: "Factory Droid Bridge", translations: { fr: "Pont Factory Droid", ko: "Factory Droid 브리지" }, slug: "guides/factory-droid" },
{ label: "Model Routing", translations: { fr: "Routage des modèles", ko: "모델 라우팅", "zh-CN": "模型路由", "zh-TW": "模型路由", ru: "Маршрутизация моделей", ja: "モデルルーティング", tr: "Model Yönlendirme" }, slug: "guides/model-routing" },
Expand Down
72 changes: 72 additions & 0 deletions docs-site/src/content/docs/fr/guides/remote-hub.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
title: Déploiement Remote Hub
description: Déployer un hub opencodex avec une gestion locale, Tailscale Serve et OAuth sans interface locale.
---

Un hub conserve les identifiants fournisseur, le catalogue et l’usage sur un hôte. Les clients authentifiés appellent directement son plan de données. Le plan de gestion est distinct : son écoute facultative reste sur `127.0.0.1` et ne sert que le tableau de bord et `/api/*`. Elle ne sert jamais `/v1/*`, `/healthz`, `/readyz` ni WebSocket. Ne publiez pas le port `10101` et n’utilisez pas Tailscale Funnel.

## Rôles, connexion et sécurité

`standalone` réunit données et gestion. `hub` possède les secrets fournisseur et l’usage. `client` ne conserve que l’état de connexion et une clé de données dédiée.

```bash
ocx connect https://hub-name.tailnet-name.ts.net --pairing-code-stdin
ocx connect status
ocx sync
```

La clé client est écrite dans le fichier privé `service-api-token`, jamais dans `config.json`. En mode connecté, l’usage provient du hub et est filtré par `apiKeyId`; après déconnexion, il provient du stockage local. Il n’existe aucune réplication entre les deux.

Le jeton admin permet la gestion ordinaire mais ne peut jamais créer une session de consentement. Les actions de consentement exigent une `gui-session`, une Origin correspondante et un jeton CSRF. `Tailscale-User-Login` n’est fiable que sur l’entrée de gestion dédiée; renseignez les identités exactes dans `remoteGui.allowedTailscaleUsers`.

## Service et Tailscale Serve

```bash
ocx config set runtimeRole hub
ocx config set hostname 100.64.0.10
ocx config set hub.managementPublicOrigin '"https://hub-name.tailnet-name.ts.net"'
ocx config set corsAllowOrigins '["http://localhost:10100"]'
ocx config set hub.managementIngress '{"enabled":true,"port":10101}'
ocx config set remoteGui.allowedTailscaleUsers '["operator@example.com"]'
export OPENCODEX_API_AUTH_TOKEN="$(openssl rand -hex 32)"
ocx service install
```

Le service lit le secret depuis `service-api-token`; le plist ou l’unité systemd ne contient pas sa valeur.

```bash
curl --fail --silent http://100.64.0.10:10100/healthz
curl --fail --silent http://100.64.0.10:10100/readyz
tailscale serve --bg --https=443 http://127.0.0.1:10101
tailscale serve status
```

`/healthz` ne prouve que la vie du processus. Validez aussi `/readyz`, `GET /v1/catalog` authentifié et une vraie réponse routée. Le port de gestion doit écouter uniquement sur `127.0.0.1`. Pour un proxy TLS privé, utilisez `tailscale cert hub-name.tailnet-name.ts.net` et ne fabriquez jamais d’en-têtes `Tailscale-User-*`; utilisez l’association à usage unique.

## OAuth, rotation et déconnexion

```bash
ocx config set oauthOpenBrowser false
ocx connect rotate --pairing-code-stdin
# uniquement en HTTPS :
ocx connect rotate --admin-token-stdin
```

Démarrez OAuth avec `POST /api/oauth/login`; si le rappel ne rejoint pas le hub, envoyez l’URL finale ou le code à `POST /api/oauth/login/code` sous `{provider,input}`. Ne placez jamais le code OAuth dans argv ou les journaux.

La rotation garde les deux clés valides sous le même `apiKeyId` pendant dix minutes au plus. L’ancienne clé est sauvegardée dans `service-api-token.prev`, la nouvelle est installée atomiquement et vérifiée avec `/v1/catalog`, puis validée. Si le résultat est incertain, relancez `ocx connect rotate` avec une autorité transitoire; ne supprimez aucun candidat.

`ocx disconnect` restaure l’état local même hors ligne et ne révoque pas la clé du hub. Après déconnexion, la seule voie de révocation est **Integrations → API Keys** sur le hub. `ocx connect revoke --admin-token-stdin` fonctionne uniquement tant que le client est connecté.

## Docker, retour arrière et dépannage

Il n’existe pas d’image Docker officielle. Épinglez l’image Bun par digest, conservez `/home/bun/.opencodex` dans un volume et montez le secret sur `/run/secrets/ocx_api_token`. Publiez seulement `10100`, jamais `10101`. Ne placez aucun secret dans `ARG`, `ENV`, `COPY`, Compose, l’historique d’image ou argv. Après le healthcheck, vérifiez séparément `/readyz`, le catalogue authentifié et une réponse réelle.

- Hub indisponible : `ocx disconnect` restaure localement, mais la révocation reste à faire.
- Catalogue périmé : seul un dernier catalogue validé est conservé après une panne transitoire; aucune substitution locale après erreur d’authentification, schéma, taille ou protocole.
- Récupération `.prev` : conservez les deux fichiers et relancez la rotation avec une autorité transitoire.
- `hub-too-new`/`hub-too-old` : mettez à niveau le côté indiqué avant toute écriture locale.
- Code d’association perdu ou épuisé : créez-en un nouveau; les essais sont limités avec 429.
- HTTP non local exige `--allow-insecure-http`; un jeton admin n’est jamais envoyé en HTTP.
- Déconnexion/expiration de session navigateur n’affecte pas la clé de données.
- Avant `tailscale serve reset`, inspectez `tailscale serve status`, car reset supprime tous les mappages.
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/fr/guides/web-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ OpenCodex demandent à Codex de transmettre les remplacements à `spawn_agent` ;
[Surface des sous-agents](/fr/guides/sub-agent-surface/) pour le comportement canonique v1/base/v2.
:::

## Sessions, clés et usage Remote Hub

Le plan de gestion du tableau de bord est séparé du trafic modèle direct client→hub. **Integrations → API Keys** affiche les rotations en attente, montre le secret de remplacement une seule fois et exige une validation ou une annulation explicite. La déconnexion du navigateur n'invalide que la session courante. L'usage connecté vient du hub filtré par `apiKeyId`; l'usage déconnecté est local, sans réplication.

La garantie de remplacement lors d'une création de sous-agent s'applique au texte de consignes v2 **intégré**.
Un `injectionPrompt` personnalisé remplace entièrement ce texte et doit contenir les espaces réservés
`{{model}}` et `{{effort}}` — et facultativement `{{roster}}` — sans quoi ces valeurs n'apparaîtront pas dans
Expand Down
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/fr/reference/cli/lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,7 @@ ocx update --tag preview
```

Les nouvelles versions deviennent disponibles lorsque le [workflow de publication](https://github.com/lidge-jun/opencodex/actions/workflows/release.yml) les publie sur npm.

## Cycle de vie du client Remote Hub

Utilisez `ocx connect <url> --pairing-code-stdin`, `ocx connect status`, `ocx sync` et `ocx connect rotate --pairing-code-stdin`. `ocx disconnect` restaure l'état local hors ligne sans révoquer la clé du hub. Tant que le client est connecté, `ocx connect revoke --admin-token-stdin` révoque l'`apiKeyId` enregistré; après déconnexion, utilisez **Integrations → API Keys** sur le hub. Les secrets passent uniquement par stdin, jamais par argv.
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,7 @@ Les images `https:` distantes et les descriptions échouées ou vides ne sont pa

Les services auxiliaires Anthropic OAuth réutilisent l'empreinte OAuth Claude Code existante d'opencodex. Effectuez un test d'endurance avec le
compte et la charge de travail prévus.

## Clés Remote Hub et valeurs par défaut

`runtimeRole` vaut `standalone` par défaut. Un hub utilise `hub.managementPublicOrigin`, `hub.managementIngress` limité au loopback (`enabled:false` si absent) et les identités exactes de `remoteGui.allowedTailscaleUsers` (liste vide si absente). La clé client reste dans `service-api-token`, jamais dans `config.json`; `service-api-token.prev` peut exister pendant une rotation. Les usages ne sont pas répliqués.
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/fr/reference/management-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,7 @@ Pour l'administration courante, le [tableau de bord web](/fr/guides/web-dashboar
Pour les hôtes sans interface graphique et l'automatisation, utilisez les commandes `ocx` correspondantes : elles appellent cette
même API active et renvoient un code différent de zéro lorsque le proxy est inaccessible ou que l'opération échoue.
L'accès HTTP direct est surtout utile aux intégrations qui exigent les contrats exacts des points de terminaison ci-dessus.

## Sessions distantes et rotation des clés de données

`POST /api/keys/rotate {id}` démarre un chevauchement de dix minutes et renvoie le nouveau secret une seule fois. `POST /api/keys/rotate/commit {id,rotationId}` valide; `DELETE /api/keys/rotate {id,rotationId}` annule. L'authentification de gestion est obligatoire et une clé de données ne suffit pas. `POST /api/session/logout` exige la `gui-session` courante, l'Origin correspondante et CSRF. Un jeton admin reçoit 403 et ne peut jamais créer une session de consentement.
61 changes: 61 additions & 0 deletions docs-site/src/content/docs/guides/remote-hub.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,47 @@ public-internet surface and is outside this deployment model.
on the public listener is ignored. `remoteGui.allowedTailscaleUsers` controls session issuance; it
does not create a new general-purpose principal.

## Roles and direct data flow

`standalone` keeps data and management on one machine. A `hub` owns provider credentials, the
catalog, and usage records. A `client` stores only its connection metadata and one per-client data
key. Codex and Claude traffic goes directly from the client to the hub data listener; it is not
tunneled through the dashboard or the loopback management relay.

Connect with exactly one transient authority source. The authority is read from stdin and is never
written to config or the token file:

```bash
ocx connect https://hub-name.tailnet-name.ts.net --pairing-code-stdin
ocx connect status
ocx sync
```

The hub automatically issues a per-client key. The client writes it to the existing owner-only
`service-api-token` file, never `config.json`. While connected, usage comes from the hub usage store
filtered to that client's stable `apiKeyId`. After disconnect, usage comes from the local store.
OpenCodex does not mirror usage between the two stores.

Rotate a connected client with a fresh transient authority:

```bash
ocx connect rotate --pairing-code-stdin
# or, only over HTTPS:
ocx connect rotate --admin-token-stdin
```

Rotation keeps the old and new data keys valid for at most ten minutes under the same `apiKeyId`.
The client backs up the old token as `service-api-token.prev`, atomically installs and probes the new
key, then commits. If a commit response is uncertain, rerun the rotate command with transient
authority; recovery probes both files before committing or restoring. Never delete either file when
recovery reports that both candidates were rejected.

`ocx disconnect` is local and works while the hub is offline. It restores local client state and
does not revoke the hub key. After disconnect, revoke that key from **Integrations → API Keys** on
the hub. `ocx connect revoke --admin-token-stdin` is available only while still connected and uses
the persisted `apiKeyId`; it accepts no id override. Browser session logout/expiry is separate from
data-key rotation, revocation, and disconnect.

## Linux systemd or macOS launchd

Choose the hub's Tailscale address for the data listener and the exact browser-visible HTTPS origin
Expand Down Expand Up @@ -227,3 +268,23 @@ ocx service repair
For a container rollback, remove or replace the container while retaining the named state volume.
For a service rollback, stop the branch service and repair the prior release against the same
`OPENCODEX_HOME`. Disabling management ingress or Serve does not require changing the data listener.

## Troubleshooting

- **Hub down:** `ocx connect status` still shows the saved connection. `ocx disconnect` can restore
local state offline; it cannot revoke the remote key.
- **Stale catalog:** `ocx sync` keeps a validated last-known-good catalog only for transient hub
failures. Authentication, schema, size, and protocol failures are hard errors and never fall back
to local providers.
- **Rotated token or `.prev` recovery:** rerun `ocx connect rotate` with a pairing code or admin token.
Do not edit or remove either token candidate before the recovery probe finishes.
- **Protocol mismatch:** upgrade the older side named by the `hub-too-new` or `hub-too-old` message.
Negotiation fails before token, catalog, journal, or client-state writes.
- **Lost or burned pairing code:** create a new short-lived code. Grants are one-use and repeated
failures are rate-limited without revealing whether a code exists.
- **Plain HTTP warning:** pairing over non-loopback HTTP requires the explicit
`--allow-insecure-http` opt-in. Admin tokens are never sent over HTTP.
- **Remote session ended:** sign in or pair again. Logout and expiry invalidate only the browser
session, not a client data key.
- **Outstanding revocation after disconnect:** use the hub dashboard's **Integrations → API Keys**
page. It is the sole post-disconnect revocation path.
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/guides/web-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ they have been synchronized. See
[Sub-agent Surface](/guides/sub-agent-surface/) for the canonical v1/base/v2 behavior.
:::

## Remote Hub sessions, keys, and usage

The dashboard's management plane is separate from direct client→hub model traffic. **Integrations → API Keys** shows pending rotations, displays a replacement secret only once, and requires explicit commit or abort. Browser logout invalidates only the current remote session. Connected usage is the hub store filtered by the client's `apiKeyId`; disconnected usage is local, with no mirroring.

The spawn override guarantee applies to the **built-in** v2 guidance text. A custom
`injectionPrompt` replaces that text entirely and must include `{{model}}` and `{{effort}}`
placeholders (and optionally `{{roster}}`) or those values will not appear in the injected
Expand Down
Loading
Loading