fix(llm): override OpenAI SDK User-Agent to bypass Cloudflare WAF#151
Merged
fix(llm): override OpenAI SDK User-Agent to bypass Cloudflare WAF#151
Conversation
promote: dev -> staging
promote: staging -> main
First tagged release. Contents of community-improvements project: M1 — Security Hardening (A2A bearer auth, audit redaction, origin verification) M2 — Memory On By Default (session persistence + load-on-start) M3 — Skill Loop (skill-v1 emission + SQLite FTS5 index + curator) Plus: .gitignore cleanup for .automaker-lock + .worktrees, docs coverage of security layer, skill-loop architecture, and new env vars. Manual bump because prepare-release.yml requires GH_PAT secret (not configured). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
promote: dev -> staging (bug fixes v0.2.1)
promote: staging -> main (bug fixes v0.2.1)
Bug fixes from v0.2.0 smoke testing: - Agent card now advertises bearer scheme when A2A_AUTH_TOKEN is set - Session memory persistence actually fires (moved from unreachable on_session_end to after_agent) - Test suite collects cleanly in fresh Docker env - MemoryMiddleware activates standalone (without knowledge_store) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Writes the deployed GitHub Pages URL back to the repo's `homepage` field so it renders in the About sidebar on the repo page. Co-authored-by: Automaker <automaker@localhost> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cloudflare's managed WAF on the proto-labs.ai zone returns 403 "Your
request was blocked" for any request whose User-Agent starts with
`OpenAI/Python` or `AsyncOpenAI/Python` — which is exactly what
langchain_openai.ChatOpenAI sends by default via the bundled OpenAI
SDK. /v1/models succeeded (different SDK path / UA) while
/v1/chat/completions failed, making the break look like a key/ACL
issue when it was a header signature match.
Reproduction (before fix):
curl -H 'User-Agent: OpenAI/Python 1.54.0' -H 'Authorization: Bearer <key>' \
https://api.proto-labs.ai/v1/chat/completions -d '{...}'
-> HTTP 403 "Your request was blocked."
The same call with User-Agent: curl/*, python-httpx/*, or any
non-OpenAI string returns 200. `tools/lg_tools.py:226` already sets a
protoAgent UA for outbound HTTP fetches — reuse that identifier here
so every egress presents a consistent, allowlisted UA.
Alternative fixes considered:
- A Cloudflare Custom WAF Skip rule on the hostname: cleaner at the
edge but requires a zone-scoped token and couples agent operability
to infra config.
- Stripping the UA header at cloudflared: not possible; WAF fires
before the tunnel sees the request.
The in-client override is the most portable fix: self-hosters on a
different edge keep working, operators behind Cloudflare stop getting
403s.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
WalkthroughThe pull request makes three configuration changes: updating the docs workflow to automatically sync GitHub Pages deployment metadata to the repository homepage via GitHub CLI, adding a custom User-Agent header to the LLM client configuration, and bumping the project version from 0.1.0 to 0.2.1. Changes
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~3 minutes ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
This was referenced Apr 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ChatOpenAIsent the defaultOpenAI/Python <ver>User-Agent, which the managed WAF on theproto-labs.aizone returns HTTP 403 "Your request was blocked." for./v1/modelshappened to succeed (different SDK path / UA), so the failure looked like a key/ACL problem when it was a header signature match.default_headers={\"User-Agent\": \"protoAgent/0.1 (+...)\"}on theChatOpenAIinstance ingraph/llm.py, matching the identifiertools/lg_tools.py:226already uses for outbound HTTP fetches.Reproduction (before fix)
```
curl -H 'User-Agent: OpenAI/Python 1.54.0' -H 'Authorization: Bearer '
-H 'Content-Type: application/json'
https://api.proto-labs.ai/v1/chat/completions
-d '{"model":"groq-llama-70b","messages":[{"role":"user","content":"hi"}]}'
HTTP 403 "Your request was blocked."
```
Same call with any non-OpenAI UA (e.g.
curl/8.5,python-httpx/*,MyApp/1.0, empty) →200.Why this path (not a WAF skip rule)
A Cloudflare Custom Rule skipping managed rules on
api.proto-labs.aiis cleaner at the edge, but couples agent operability to zone config and the currentCLOUDFLARE_API_TOKENis DNS-only. The in-client override keeps self-hosters behind other edges working and removes the per-deployment Cloudflare dependency.Test plan
graph/llm.pyin runningprotoagent-localcontainer;llm.invoke(\"reply with: pong\")→pong. Captured outboundUser-Agent: protoAgent/0.1 (+https://github.com/protoLabsAI/protoAgent)on the wire.🤖 Generated with Claude Code
Summary by CodeRabbit
Version
Improvements
Chores