Skip to content

fix(gateway): harden AI provider HTTP error handling and context propagation#258

Open
tlnarayana005 wants to merge 1 commit into
AnkanMisra:mainfrom
tlnarayana005:fix/harden-http-clients-and-response-handling
Open

fix(gateway): harden AI provider HTTP error handling and context propagation#258
tlnarayana005 wants to merge 1 commit into
AnkanMisra:mainfrom
tlnarayana005:fix/harden-http-clients-and-response-handling

Conversation

@tlnarayana005

@tlnarayana005 tlnarayana005 commented Jul 7, 2026

Copy link
Copy Markdown

fix(gateway): harden AI provider HTTP error handling and context propagation

  • Cap error response body reads to 4KB in both OpenRouter and Ollama providers. Previously, io.ReadAll(resp.Body) on non-2xx responses could read unlimited data from a malicious/misconfigured upstream, causing OOM. Now uses io.LimitReader to cap at 4096 bytes, matching the existing pattern in verifyPayment().

  • Handle context.Canceled alongside context.DeadlineExceeded in AI provider error paths. During graceful shutdown, Go cancels contexts (not deadline-exceeds them). Without this fix, shutdown cancellations were misclassified and wrapped in connection-error messages instead of being returned as clean context errors.

  • Apply the same context.Canceled fix to handleSummarize() in main.go so the gateway returns 504 (not 502) during shutdown-induced cancellations, giving clients actionable retry signals.

  • Add unit tests covering both the body-limit cap and context.Canceled behavior for both providers.

Summary

  • Capped error response body reads to 4KB in AI providers (io.LimitReader) to prevent potential OOM DOS vulnerabilities from malicious or misconfigured upstreams.
  • Added context.Canceled handling in openrouter.go, ollama.go, and main.go to properly return 504 Gateway Timeout instead of 502 Bad Gateway during graceful shutdowns.
  • Added comprehensive unit tests for the new error handling behaviors.

Type Of Change

  • Bug fix
  • Feature
  • Documentation
  • Tests
  • Refactor
  • Deployment/config

Affected Areas

  • Gateway (gateway/)
  • Verifier (verifier/)
  • Web (web/)
  • E2E/tests (tests/, run_e2e.sh)
  • Benchmarks (bench/)
  • Deployment/config (deploy/, Docker, env, workflows)
  • Documentation/community files

Contributor Checklist

  • I kept the change focused and avoided unrelated refactors.
  • I updated README/service docs/OpenAPI/env examples when behavior, config, headers, status codes, or public APIs changed.
  • I did not commit secrets, private keys, funded wallets, API keys, or real production URLs.
  • I checked x402/EIP-712 field parity when touching payment context, signatures, timestamps, nonces, chain IDs, receipts, or wallet flow.
  • I checked Docker/Compose/Fly/Vercel docs when touching ports, service names, health checks, or environment variables.

Verification

$ cd gateway
$ go test ./internal/ai/ -v -run "TestOpenRouterErrorBodyLimit|TestOllamaErrorBodyLimit|TestOpenRouterContextCanceled|TestOllamaContextCanceled"
=== RUN   TestOpenRouterErrorBodyLimit
--- PASS: TestOpenRouterErrorBodyLimit (0.00s)
=== RUN   TestOllamaErrorBodyLimit
--- PASS: TestOllamaErrorBodyLimit (0.00s)
=== RUN   TestOpenRouterContextCanceled
--- PASS: TestOpenRouterContextCanceled (0.00s)
=== RUN   TestOllamaContextCanceled
--- PASS: TestOllamaContextCanceled (0.00s)
PASS
ok      gateway/internal/ai     0.015s


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **Bug Fixes**
  * Improved AI request error handling so canceled or timed-out requests now return the correct cancellation error.
  * Limited how much of large upstream error responses is read, helping prevent oversized error messages and reducing memory spikes.
  * Applied the same more reliable cancellation handling across supported AI providers.

* **Tests**
  * Added coverage for large error responses and context cancellation cases to verify the new behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

…agation

- Cap error response body reads to 4KB in both OpenRouter and Ollama
  providers. Previously, io.ReadAll(resp.Body) on non-2xx responses
  could read unlimited data from a malicious/misconfigured upstream,
  causing OOM. Now uses io.LimitReader to cap at 4096 bytes, matching
  the existing pattern in verifyPayment().

- Handle context.Canceled alongside context.DeadlineExceeded in AI
  provider error paths. During graceful shutdown, Go cancels contexts
  (not deadline-exceeds them). Without this fix, shutdown cancellations
  were misclassified and wrapped in connection-error messages instead
  of being returned as clean context errors.

- Apply the same context.Canceled fix to handleSummarize() in main.go
  so the gateway returns 504 (not 502) during shutdown-induced
  cancellations, giving clients actionable retry signals.

- Add unit tests covering both the body-limit cap and
  context.Canceled behavior for both providers.
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

@tlnarayana005 is attempting to deploy a commit to the ankanmisra's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Hi @tlnarayana005, thanks for opening this PR.

Every contribution helps MicroAI-Paygate grow. If you find the project useful, consider starring the repository — it helps others discover it.

Star MicroAI-Paygate on GitHub

Looking forward to reviewing this PR.

@github-actions github-actions Bot added go Pull requests that update go code type:testing Tests, coverage, fixtures, or validation-only work. labels Jul 7, 2026
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds byte limits on error response bodies read from OpenRouter and Ollama providers, refines context deadline/cancellation detection in both providers' Generate methods and in gateway's handleSummarize, and adds tests validating bounded error messages and correct context.Canceled propagation.

Changes

AI provider error handling improvements

Layer / File(s) Summary
OpenRouter error body cap and context handling
gateway/internal/ai/openrouter.go
Adds maxErrorResponseBytes constant and updates Generate to return ctx.Err() on deadline/cancellation and to bound error-body reads via io.LimitReader.
Ollama error body cap and context handling
gateway/internal/ai/ollama.go
Adds maxOllamaErrorResponseBytes constant and applies the same bounded-read and context-error handling pattern to Ollama's Generate.
handleSummarize timeout/cancel classification
gateway/main.go
Updates upstream error classification to treat both context.DeadlineExceeded and context.Canceled as upstream_timeout via request context checks.
Error handling tests
gateway/internal/ai/error_handling_test.go
Adds four tests verifying bounded error message length on oversized 500 responses and exact context.Canceled propagation for both providers.

Estimated code review effort: 2 (Simple) | ~12 minutes

Possibly related PRs

Suggested labels: level:intermediate

Suggested reviewers: AnkanMisra

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: hardened AI provider HTTP error handling and context propagation.
Description check ✅ Passed The description follows the template with Summary, Type Of Change, Affected Areas, Checklist, and Verification filled in.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (3)
gateway/main.go (1)

484-494: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Verifier-call error classification wasn't updated for context.Canceled, unlike the AI-provider path just below.

Line 488 still only checks errors.Is(err, context.DeadlineExceeded); a canceled context (e.g., graceful shutdown) during verifyPayment would fall into the 502 verification_unavailable branch instead of the 504 verifier_timeout branch that the AI path now gets via the update at Lines 526-528. As per coding guidelines, "Every outbound verifier/AI/Redis call in the gateway should respect context deadlines and handle cancellation" — worth aligning this branch for consistency.

♻️ Suggested alignment
-		if errors.Is(err, context.DeadlineExceeded) {
+		if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
 			respondError(c, 504, "verifier_timeout", err)
 		} else {
 			respondError(c, 502, "verification_unavailable", err)
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gateway/main.go` around lines 484 - 494, In the verifyPayment error handling
inside the gateway main flow, the classification only treats
context.DeadlineExceeded as a timeout, so context.Canceled still falls through
to verification_unavailable. Update the error branch around verifyPayment to
treat both context.DeadlineExceeded and context.Canceled as verifier_timeout,
matching the AI-provider handling below and keeping cancellation semantics
consistent.

Source: Coding guidelines

gateway/internal/ai/openrouter.go (2)

68-73: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant condition — simplify to ctx.Err() != nil.

context.Context.Err() only ever returns nil, context.Canceled, or context.DeadlineExceeded, and once non-nil it stays non-nil. Since ctx here is the exact context attached via http.NewRequestWithContext(ctx, ...), any err from Do() wrapping either sentinel implies ctx.Err() is already set. The errors.Is(...) checks add no coverage beyond ctx.Err() != nil.

♻️ Simplification
-		if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) ||
-			ctx.Err() == context.DeadlineExceeded || ctx.Err() == context.Canceled {
+		if ctx.Err() != nil {
 			return "", ctx.Err()
 		}
 		return "", err
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gateway/internal/ai/openrouter.go` around lines 68 - 73, The error handling
in the OpenRouter request path is overly defensive and duplicates cancellation
checks. In the function that calls http.NewRequestWithContext and Do in
openrouter.go, simplify the branch by relying on ctx.Err() != nil instead of
checking both errors.Is against context.DeadlineExceeded/context.Canceled and
ctx.Err() values; keep the behavior of returning ctx.Err() when the request was
canceled or timed out, otherwise return err unchanged.

74-80: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Bounded error-body read looks correct; consider draining remainder for connection reuse.

The io.LimitReader(resp.Body, maxErrorResponseBytes) cap correctly prevents unbounded reads. Since only up to 4KB is consumed before Close(), the underlying net/http transport will typically close rather than reuse the TCP connection when the upstream error body exceeds the cap.

♻️ Optional: drain remaining body before close
 	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
 		body, _ := io.ReadAll(io.LimitReader(resp.Body, maxErrorResponseBytes))
+		io.Copy(io.Discard, resp.Body) // drain to allow connection reuse
 		return "", fmt.Errorf("openrouter returned status %d: %s", resp.StatusCode, string(body))
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gateway/internal/ai/openrouter.go` around lines 74 - 80, The error path in
openrouter.go only reads a capped portion of resp.Body before closing it, which
can prevent HTTP connection reuse on oversized upstream error responses. Update
the status-check block in the OpenRouter request handling to explicitly drain
any remaining response body after reading the limited error bytes, while keeping
the maxErrorResponseBytes cap and the existing error message in the same
resp.StatusCode handling path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@gateway/internal/ai/openrouter.go`:
- Around line 68-73: The error handling in the OpenRouter request path is overly
defensive and duplicates cancellation checks. In the function that calls
http.NewRequestWithContext and Do in openrouter.go, simplify the branch by
relying on ctx.Err() != nil instead of checking both errors.Is against
context.DeadlineExceeded/context.Canceled and ctx.Err() values; keep the
behavior of returning ctx.Err() when the request was canceled or timed out,
otherwise return err unchanged.
- Around line 74-80: The error path in openrouter.go only reads a capped portion
of resp.Body before closing it, which can prevent HTTP connection reuse on
oversized upstream error responses. Update the status-check block in the
OpenRouter request handling to explicitly drain any remaining response body
after reading the limited error bytes, while keeping the maxErrorResponseBytes
cap and the existing error message in the same resp.StatusCode handling path.

In `@gateway/main.go`:
- Around line 484-494: In the verifyPayment error handling inside the gateway
main flow, the classification only treats context.DeadlineExceeded as a timeout,
so context.Canceled still falls through to verification_unavailable. Update the
error branch around verifyPayment to treat both context.DeadlineExceeded and
context.Canceled as verifier_timeout, matching the AI-provider handling below
and keeping cancellation semantics consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2411a38d-7728-42ca-a3ec-6937828f15dc

📥 Commits

Reviewing files that changed from the base of the PR and between d8d9bb9 and e6ab8f1.

📒 Files selected for processing (4)
  • gateway/internal/ai/error_handling_test.go
  • gateway/internal/ai/ollama.go
  • gateway/internal/ai/openrouter.go
  • gateway/main.go

@AnkanMisra

Copy link
Copy Markdown
Owner

@codex review

@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
microai-paygate Ready Ready Preview, Comment Jul 8, 2026 4:08am

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6ab8f128c

ℹ️ 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 on lines +68 to +70
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) ||
ctx.Err() == context.DeadlineExceeded || ctx.Err() == context.Canceled {
return "", ctx.Err()

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.

P1 Badge Preserve transport timeout errors when context is active

When http.DefaultClient.Do returns a deadline/cancellation error from the client or transport rather than from the passed request context, ctx.Err() is still nil; for example, setting http.DefaultClient = &http.Client{Timeout: ...} makes this branch return ("", nil). That makes Generate look successful with an empty summary, so a paid summarize request can get a 200/receipt instead of an upstream error. Return ctx.Err() only when it is non-nil, otherwise return the matched err or the appropriate context sentinel; the identical Ollama branch needs the same treatment.

Useful? React with 👍 / 👎.

@AnkanMisra AnkanMisra left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@AnkanMisra AnkanMisra added bug Something isn't working level:intermediate Moderate scope requiring project familiarity or cross-file changes. type:bug A defect or regression in existing behavior. type:security Security, abuse resistance, secret safety, or payment integrity work. triage Needs maintainer triage. gssoc:approved Approved for GSSoC contribution labels Jul 8, 2026 — with ChatGPT Codex Connector
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working go Pull requests that update go code gssoc:approved Approved for GSSoC contribution level:intermediate Moderate scope requiring project familiarity or cross-file changes. triage Needs maintainer triage. type:bug A defect or regression in existing behavior. type:security Security, abuse resistance, secret safety, or payment integrity work. type:testing Tests, coverage, fixtures, or validation-only work.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants