Skip to content

fix: gateway-sign alias and legacy completion responses - #995

Open
hanakannzashi wants to merge 2 commits into
mainfrom
codex/gateway-sign-known-rewrites-993
Open

fix: gateway-sign alias and legacy completion responses#995
hanakannzashi wants to merge 2 commits into
mainfrom
codex/gateway-sign-known-rewrites-993

Conversation

@hanakannzashi

@hanakannzashi hanakannzashi commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • replace invalid provider signatures for alias-served chat completions with Gateway signatures over the public response
  • give legacy /v1/completions the same treatment, since it always converts an upstream chat completion
  • keep a Gateway signature available before the client observes the final SSE [DONE] frame

The Gateway signature binds the public request and response bytes. It does not establish the longer-term provider-to-Gateway provenance chain tracked in #986.

This PR is stacked on #992 because both paths use the same provider-signature suppression mechanism.

Testing

  • cargo test -p api --lib
  • cargo test -p api --test e2e_all --no-run
  • Focused E2E execution is blocked locally by PostgreSQL authentication during test bootstrap.

Fixes #993

@ironloopai

ironloopai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Review · Status

🟩 Completed

IronLoop completed the review and posted it to GitHub.

Result

Open submitted review →

Run details
  • Run: 149d4afa-22ad-4bfe-80b2-b49cbe040df9
  • Base: codex/fix-auto-redact-attestation-892 at bb50741
  • Head: codex/gateway-sign-known-rewrites-993 at 30e5176
  • Created: 2026-09-01 06:15 UTC
  • Updated: 2026-09-01 06:36 UTC

Automatic trigger · attempt 1 of 3 · completed in 21m 35s

@ironloopai ironloopai 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.

Review · Summary

🟢 No actionable findings

No actionable issues found in the gateway-signing changes for alias-served and legacy completion responses.

Validation
  • API end-to-end test compilation — The end-to-end test target containing the added gateway-signature coverage compiled successfully.
Review details
  • Run: 149d4afa-22ad-4bfe-80b2-b49cbe040df9
  • Attempts: 1

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

OpenCodeReview: Review failed: 0 finding(s); 3 of 3 selected item(s) failed.

@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown

Review — gateway-sign alias and legacy completion responses

Context: IronLoop's automated pass reported no actionable findings; the analysis below covers the alias/E2EE interaction it did not reach.

The direction is right. /v1/completions always reshapes the provider payload, and alias_served already disables byte-passthrough on chat, so a provider signature over those bytes was never verifiable. Holding [DONE] back until store_chat_signature_and_unpin returns (completions.rs:1708 plus the new legacy tail) closes the fetch-signature-at-[DONE] race, and I traced pin release on every branch: gateway-signed, non-attested, errored stream, serialize failure, and the !pinned cleanup in the pool. No leak found.

One correctness issue.

⚠️ public_response_rewritten over-suppresses for non-streaming E2EE + alias

crates/api/src/routes/completions.rs:1524

let public_response_rewritten = auto_redact_enabled || alias_canonical.is_some();

But the non-streaming body is only rewritten when E2EE is off (completions.rs:2166):

let body_bytes = match &alias_canonical {
    Some(canonical) if !e2ee_active => inject_warning_field(...).unwrap_or(body_bytes),
    _ => body_bytes,
};

So for POST /v1/chat/completions with stream: false, an aliased model and E2EE headers, the client receives response_with_bytes.raw_bytes verbatim. The provider signs X-Request-Hash : sha256(its response body), and we hand it the public body hash (services/src/completions/mod.rs:1810 -> chat_completion(params, request.body_hash)), so the provider signature was valid over exactly the bytes the client got. This PR now forces skip_provider_chat_signature = true there:

  • model_attestation_supported == Some(true) -> signature silently downgrades from provider_tee to gateway. E2EE callers are precisely the ones who want the model-side TEE key, not the gateway's.
  • model_attestation_supported == None (a transient get_models_with_pricing() failure, which only warns at completions.rs:1457) or Some(false) while the DB model is attested -> no signature at all, where before there was a valid one.

Same shape on the failure path of inject_warning_field(...).unwrap_or(body_bytes) (completions.rs:2171): injection returns None, bytes go out unchanged, provider signature already suppressed.

Suggested fix — make the flag track whether bytes actually change:

// Streaming always re-serializes for aliases (`alias_served` disables raw
// passthrough); non-streaming only injects the warning when E2EE is off.
let alias_rewrites_public_bytes =
    alias_canonical.is_some() && (request.stream == Some(true) || !e2ee_active);
let alias_requires_gateway_signature =
    alias_rewrites_public_bytes && model_attestation_supported.unwrap_or(false);
let public_response_rewritten = auto_redact_enabled || alias_rewrites_public_bytes;

Minor

  • completions.rs:2564 — the legacy stream takes public_signature_chat_id.lock().await on every chunk, even though the id is normally already set from the peek and no signature may be stored. The chat path guards this (if gateway_signature_enabled || public_response_rewritten, completions.rs:1748); mirroring it keeps an async mutex hop off the per-chunk hot path.
  • completions.rs:2758 — non-streaming /v1/completions now awaits store_chat_signature_and_unpin before building the response, so a stalled store adds up to STREAM_SIGNATURE_STORE_TIMEOUT (5s) to the request. Consistent with the chat auto-redact path and needed to avoid the fetch race — just flagging the new p99 exposure on this route.
  • Contract change worth a release note: GET /v1/signature/{id} for /v1/completions responses now returns signature_kind: "gateway" instead of "provider_tee", and returns nothing when the models-cache read fails (legacy_gateway_signature_enabled collapses Err and "model not found" into false while skip_provider_chat_signature is unconditional). Correct — the reshaped body was never provider-verifiable — but visible to existing callers.

⚠️


🤖 Generated with Claude Code

@think-in-universe think-in-universe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Found cancellation cleanup gaps in the newly gateway-signed streaming paths.

Comment thread crates/api/src/routes/completions.rs
Comment thread crates/api/src/routes/completions.rs
@think-in-universe

Copy link
Copy Markdown
Contributor

@ironloopai review

@ironloopai

ironloopai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Review · Status

🟩 Completed

IronLoop completed the review and posted it to GitHub.

Result

Open submitted review →

Run details
  • Run: 4473cc7c-c063-440a-a66b-b63603321356
  • Base: codex/fix-auto-redact-attestation-892 at bb50741
  • Head: codex/gateway-sign-known-rewrites-993 at 30e5176
  • Created: 2026-09-03 06:38 UTC
  • Updated: 2026-09-03 06:42 UTC

Manual command by think-in-universe · attempt 1 of 3 · completed in 4m 15s

@ironloopai ironloopai 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.

Review · Summary

🟢 No actionable findings

No additional actionable findings were identified in the reviewed change.

Validation
  • Review coverage — Static inspection covered the complete production and test diff, including normal-completion signature hashing for alias and legacy responses.
Review details
  • Run: 4473cc7c-c063-440a-a66b-b63603321356
  • Attempts: 1

@think-in-universe think-in-universe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved.

Please resolving the pending comments before merge

@hanakannzashi
hanakannzashi force-pushed the codex/fix-auto-redact-attestation-892 branch from bb50741 to 01a8835 Compare September 4, 2026 01:41
@hanakannzashi
hanakannzashi force-pushed the codex/gateway-sign-known-rewrites-993 branch from 34695b6 to eb19842 Compare September 4, 2026 01:50
@hanakannzashi
hanakannzashi changed the base branch from codex/fix-auto-redact-attestation-892 to main September 4, 2026 01:50
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.

Return verifiable signatures for alias and legacy completion responses

2 participants