Skip to content

🔐 feat: Recover Code API Machine Credentials After Outages - #260

Merged
danny-avila merged 3 commits into
mainfrom
lia/machine-credential-recovery
Sep 28, 2026
Merged

danny-avila merged 3 commits into
mainfrom
lia/machine-credential-recovery

Conversation

@lia-by-librechat

@lia-by-librechat lia-by-librechat Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Persist enrolled Ed25519 machine authorization separately from short-lived credentials, with explicit revocation, renewal after outages, and same-identity recovery. Disabled until a stable HTTPS Code API origin is configured.
  • Require a signed, replay-protected challenge start and single-use, signed recovery completion. Redis atomically fences revocation, replacement, and credential issuance across replicas. Never clear execution quarantine or assignment fences.
  • Keep existing pairing and refresh compatible, pass recovery settings through the primary Compose deployment, and persist Redis state with AOF and a named volume.

Codex findings addressed

  • First P1, worker budget exhaustion: Invalid proof attempts do not charge signed worker budgets. An enrolled key must sign challenge creation; only verified, unused challenges are eligible for credential issuance.
  • P2, Compose settings: API and service-worker containers receive recovery settings with defaults off. Compose regression tests cover enabled and disabled configurations.
  • Latest P1, unbounded invalid starts (head f148115): Both recovery routes enforce an additional shared Redis cap for all JSON requests per connection peer and operation before reading enrollment or verifying signatures. Peer identity comes from the socket, not attacker-controlled forwarded headers or worker IDs. Invalid requests cannot exhaust the signed machine budget. HTTP 429 sends Retry-After. A trusted ingress should enforce client-IP and global limits when many clients share one proxy peer.

Rollout and verification

Deploy compatible Code API replicas first, preserve and migrate Redis state before mounting a new volume, then enroll workers again. Browser device approval and automatic CLI reconnect are follow-up PRs; this PR does not implement user-facing login or broaden execution permissions.

  • Focused Bun recovery, pairing, and router tests: 64 passed.
  • packages/code and touched service TypeScript typechecks: passed.
  • Compose bridge configuration test: passed.
  • Changed service file lint: no errors with the existing pairing infinite-loop and router legacy indentation/nested-ternary rules excluded; eight existing warnings remain.
  • Full service ESM typecheck: still reports three pre-existing errors in egress-gateway, egress-grant, and replay-state. Full CommonJS typecheck and real Redis restart/CLI outage release gate are not run in this follow-up.

@danny-avila

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-28T04:21:40.065563Z f148115 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b4feb9aaf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread service/src/bridge/pairing.ts Outdated
Comment on lines +708 to +713
const allowed = await this.redis.eval(
LIMIT_RECOVERY_ATTEMPTS_SCRIPT,
1,
recoveryRateKey(workerId, 'complete'),
String(this.recovery.maxAttemptsPerMinute ?? DEFAULT_RECOVERY_ATTEMPTS_PER_MINUTE),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Prevent callers from exhausting another worker's recovery budget

Because the recovery endpoint is intentionally unauthenticated, this worker-wide counter is incremented before confirming that the supplied challenge exists or belongs to the enrollment. Anyone who knows a worker ID can continuously submit well-formed fabricated proofs (30 per minute by default), causing every legitimate signed recovery attempt to receive HTTP 429 precisely while the worker is offline. Validate and rate-limit against the stored challenge, or otherwise partition untrusted traffic so invalid requests cannot consume the enrolled worker's entire recovery budget.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed at 84608a27da8fbac9952803339bd019e626333b40. Challenge creation now requires the enrolled key to sign the server, worker, operation, timestamp, and fresh nonce; only verified requests consume the shared challenge budget. Fabricated completions do not consume the worker budget: invalid signatures are limited per unpredictable challenge, and the worker counter is charged inside the atomic Redis completion only after proof verification and the single-use check. Budgets are scoped to the enrollment generation. Tests cover repeated fabricated proofs, replay, concurrent replicas, revocation, and key replacement.

Comment thread service/src/config.ts
/** Enrollment and lease credential shared only with the configured worker. */
BRIDGE_TOKEN: process.env.CODEAPI_BRIDGE_TOKEN ?? '',
/** Opt-in stable HTTPS origin of this Code API deployment (shared by all replicas). */
BRIDGE_RECOVERY_SERVER_ID: process.env.CODEAPI_BRIDGE_RECOVERY_SERVER_ID ?? '',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Forward recovery settings into Docker Compose

The documented local deployment uses .env with docker-compose up, but docker-compose.yaml explicitly forwards only the existing bridge variables in its environment sections and does not pass this new server ID or any of the associated recovery settings. Consequently, setting the documented variables in .env leaves BRIDGE_RECOVERY_SERVER_ID empty inside the API container, so recovery remains disabled and its routes return 404. Add the recovery variables to the Compose service environment and its configuration test.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed at 84608a27da8fbac9952803339bd019e626333b40. docker-compose.yaml now forwards the recovery origin, enrollment TTL, challenge TTL, and both bounded rate settings to api and service-worker, with default-off origin and tested default/override values. The primary Compose Redis now has AOF plus a named /data volume; the rollout documentation warns operators to migrate existing in-memory Redis state before recreating a running container. node tests/compose-bridge-config.cjs passes and verifies the settings do not leak to the sandbox.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Exact-head review handoff: 84608a27da8fbac9952803339bd019e626333b40.

Both Codex findings on the previous head have fixes and verified inline replies. The follow-up signs recovery starts before charging a worker's quota, isolates invalid completion attempts, and forwards all recovery settings in Docker Compose with persistent Redis. Focused bridge tests (62), package identity tests (2), Compose config tests, and targeted TypeScript checks passed. Full service TypeScript still reports the existing unrelated baseline diagnostics. CI is running for this head.

A maintainer can trigger a new Codex review for this exact head if desired; the Lia GitHub App cannot trigger that review. I will monitor new findings and CI results.

@danny-avila

Copy link
Copy Markdown
Collaborator

@codex review the latest head

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 84608a27da

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

!/^[A-Za-z0-9_-]{43}$/.test(request.nonce) ||
!Number.isFinite(proofTime) ||
Math.abs(Date.now() - proofTime) > PROOF_CLOCK_SKEW_MS ||
!verifyBridgeRecoveryStart(enrollment.publicKey, request, signature)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Rate-limit invalid recovery-start proofs

In the latest head, any internet client that knows an enrolled worker ID can submit unlimited syntactically valid requests with random signatures; each request performs Redis reads and an Ed25519 verification here, but the only start-rate counter is incremented later, after successful verification. Because the bridge router is mounted before API authentication and has no outer limiter, invalid proofs never consume any bounded bucket, allowing sustained traffic to exhaust API/Redis capacity and prevent legitimate recovery or other requests. Add a separate untrusted-traffic limit that applies before the signature work without consuming the enrolled worker's authenticated recovery budget.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed at f14811531236d49c98a246ce9795a0215a85832b. Both recovery endpoints now apply a separate Redis-backed per-connection-peer and per-operation limit before reading enrollment or verifying an Ed25519 signature. The peer comes from req.socket.remoteAddress, never X-Forwarded-For or a caller-supplied worker ID, so forged requests cannot evade the limit by changing headers or IDs. This untrusted bucket is independent of the signed worker budgets. HTTP 429 includes Retry-After; the Compose default is 240 requests/minute per peer per endpoint. Tests prove forged starts are throttled across two replicas before signature work, bogus completions are limited separately, and valid machine recovery is unaffected by the worker's signed budget. Reverse-proxy peer sharing and ingress-level client-IP/global limits are documented. 64 focused bridge tests, scoped TypeScript checks, and Compose configuration tests passed.

@danny-avila

Copy link
Copy Markdown
Collaborator

@codex review the latest head

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: f148115312

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danny-avila
danny-avila merged commit 2a31002 into main Sep 28, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants