Security Notes
API key safety
Section titled “API key safety”- Keys are shown once at creation. Copy and store them immediately in a secure location.
- Never commit keys to version control. Use environment variables or a secrets manager.
- Never include keys in client-side code, browser bundles, or frontend JavaScript.
- Rotate a key immediately if it appears in logs, screenshots, chat, or any unsecured location.
Use the placeholder sk-bve-YOUR_KEY in documentation and examples — never a real key.
Admin key safety
Section titled “Admin key safety”The admin key (ADMIN_API_KEY) grants full control over all API keys and usage data.
-
Set it as a Cloudflare Worker secret, not a plain environment variable:
Terminal window bunx wrangler secret put ADMIN_API_KEY -
Grant access only to trusted operators.
-
Rotate via
bunx wrangler secret put ADMIN_API_KEY— this redeploys the Worker with the new value.
Use the placeholder admin_bve_YOUR_ADMIN_KEY in documentation and examples.
How keys are stored
Section titled “How keys are stored”BVE Gateway never stores API keys in plain text. Only sha256(key + pepper) is stored in Cloudflare D1. The pepper is a separate API_KEY_PEPPER Worker secret.
This means:
- A database breach does not expose raw keys.
- A lost key cannot be recovered — revoke it and create a new one.
BVE Gateway allows cross-origin requests from any origin (*). Allowed request headers:
AuthorizationContent-TypeX-Request-IdOpenAI-Beta— listed so browser preflight passes; stripped before upstream forwarding (see Upstream header sanitization)Anthropic-Version— required by browser clients calling/v1/messagesAnthropic-Beta— required by browser clients using Anthropic beta features (e.g.tools-2024-04-04)
Exposed response headers (accessible to browser JavaScript):
| Header | Description |
|---|---|
X-Request-Id | Gateway per-request UUID |
X-BVE-Client-Id | Echo of the client-supplied X-Request-Id (when present and valid: alphanumeric + -_., ≤ 128 chars). Absent when the client did not send X-Request-Id or the value failed validation. Lets clients confirm their trace ID was received without querying the log stream. |
X-BVE-Latency | Total gateway latency in milliseconds |
X-BVE-Model | Model ID the gateway processed this request with |
X-BVE-Cache | Model-list cache status: HIT, STALE, or MISS |
X-BVE-Worker | Worker version UUID — lets operators identify the exact deployed version from curl or browser dev tools without reading log streams |
X-Quota-Allowed, X-Quota-Available, X-Quota-Reset | Fuelix upstream quota headers |
X-RateLimit-Limit-Requests, X-RateLimit-Limit-Tokens | Upstream request/token limits |
X-RateLimit-Remaining-Requests, X-RateLimit-Remaining-Tokens | Upstream remaining capacity |
X-RateLimit-Reset-Requests, X-RateLimit-Reset-Tokens | Upstream window reset times |
X-RateLimit-Limit-Day | BVE Gateway per-day request cap for this key |
X-RateLimit-Remaining-Day | Requests remaining until UTC midnight |
X-RateLimit-Reset-Day | Seconds until the next UTC midnight |
X-RateLimit-Limit-Month | BVE Gateway per-month request cap for this key (when configured) |
X-RateLimit-Remaining-Month | Requests remaining in the current calendar month |
X-RateLimit-Reset-Month | Seconds until the start of the next calendar month |
Retry-After | Seconds until the rate-limit window resets |
Anthropic-RateLimit-Requests-Limit, Anthropic-RateLimit-Requests-Remaining, Anthropic-RateLimit-Requests-Reset | Anthropic Messages API request limits |
Anthropic-RateLimit-Tokens-Limit, Anthropic-RateLimit-Tokens-Remaining, Anthropic-RateLimit-Tokens-Reset | Anthropic Messages API combined token limits |
Anthropic-RateLimit-Input-Tokens-Limit, Anthropic-RateLimit-Input-Tokens-Remaining, Anthropic-RateLimit-Input-Tokens-Reset | Anthropic per-direction input-token limits |
Anthropic-RateLimit-Output-Tokens-Limit, Anthropic-RateLimit-Output-Tokens-Remaining, Anthropic-RateLimit-Output-Tokens-Reset | Anthropic per-direction output-token limits |
X-Groq-Request-Id | Groq request ID for log correlation and support tickets |
X-Groq-Processing-Time | Groq server-side inference duration in seconds (e.g. 0.4823) |
OpenAI-Processing-Ms | OpenAI server-side processing time in milliseconds |
X-Cohere-Request-Id | Cohere request correlation ID for support tickets |
X-OpenRouter-Model | Actual model ID selected by OpenRouter after provider routing (e.g. openai/gpt-4o) |
X-Or-Cache-Status | OpenRouter semantic cache result: HIT or MISS |
X-Or-Remaining-Tokens | Token budget remaining in the OpenRouter rate-limit window |
Preflight responses are cached for 24 hours.
If your use case requires origin restrictions, this must be changed at the Worker level.
Security response headers
Section titled “Security response headers”Every response from the gateway includes the following security headers:
| Header | Value | Purpose |
|---|---|---|
X-Content-Type-Options | nosniff | Prevents browsers from MIME-sniffing responses |
Referrer-Policy | no-referrer | Prevents the Referer header from leaking to upstream |
Content-Security-Policy | default-src 'none' | Prevents browsers from executing gateway responses as HTML or scripts |
Permissions-Policy | interest-cohort=() | Opts the gateway out of Privacy Sandbox FLoC cohort calculation |
Cache-Control | no-store | Prevents authenticated responses from being cached by intermediary proxies |
Strict-Transport-Security | max-age=63072000 | Instructs browsers to use HTTPS only for 2 years; prevents HTTP downgrade attacks |
X-Permitted-Cross-Domain-Policies | none | Prevents Adobe Flash, Adobe Reader, and similar plugin runtimes from making cross-domain policy requests to this API server |
Upstream header sanitization
Section titled “Upstream header sanitization”proxyToFuelix strips or rewrites the following client-supplied headers before forwarding to Fuelix, preventing credential injection, IP spoofing, and information leakage.
Credential and auth headers (stripped — prevent alternate auth injection and billing context manipulation):
x-api-key— Anthropic alternate auth headerapi-key— Azure OpenAI SDK credential header (nox-prefix; sending an Azure key here would expose it to an upstream provider)openai-organization,openai-project— OpenAI billing contextopenai-beta— OpenAI Assistants API feature flag (OpenAI-Beta: assistants=v2); forwarding this could enable experimental Fuelix features that BVE Gateway does not expose. Accepted by CORS preflight but stripped before upstream forwarding.x-basicllm-*— Fuelix internal routing headersx-goog-api-key— Google Gemini/VertexAI API keyanthropic-auth-token,x-anthropic-auth-token— Older Anthropic auth variantsanthropic-dangerous-direct-browser-only— Anthropic header that bypasses CORS for direct browser-to-Anthropic calls; if forwarded, would route the request under direct Anthropic billing rather than through Fuelix
Session and proxy credentials (stripped — must never leave the gateway):
cookie— Browser session cookies have no meaning to Fuelix and must not leave the gatewayproxy-authorization— Proxy credentials from intermediate HTTP proxies must not reach the upstream providerhost— Stripped so the Workers runtime derives the correctHostfrom the upstream URL; a client-suppliedHostcould affect virtual-host routing in Fuelix
Cloudflare metadata headers (stripped — prevent leaking Cloudflare-internal routing context to upstream):
CF-Connecting-IP— Cloudflare verified client IP (read by the gateway, then stripped)CF-Ray— Cloudflare internal request trace IDCF-Visitor— Cloudflare scheme/port metadataCF-IPCountry— Cloudflare GeoIP resultTrue-Client-IP— Cloudflare Enterprise real-IP header
Forwarding and IP headers (sanitized or stripped — prevent host/IP spoofing in Fuelix logs and routing):
X-Forwarded-For— overwritten (not stripped): replaced with the Cloudflare-verified client IP fromCF-Connecting-IP. A client-suppliedX-Forwarded-Forwould otherwise let an attacker spoof their apparent source IP in Fuelix’s access logs and abuse-detection systems.x-forwarded-host— Stripped: allows callers to inject a fake host; frameworks may use this to build absolute redirect URLs (e.g.Location: https://evil.com/callback).x-forwarded-proto— Stripped: the upstream connection is always HTTPS; a client-supplied value could mislead Fuelix into treating the connection as plain HTTP.x-forwarded-scheme— Stripped: scheme alias used by Nginx and Kubernetes Ingress controllers; same spoofing risk asx-forwarded-proto.x-forwarded-ssl— Stripped: boolean SSL-flag header used by some proxies (e.g. HAProxy); a client-suppliedoffvalue could mislead upstream services that use it to detect HTTPS.x-forwarded-server— Stripped: server-identity metadata set by some reverse proxies (e.g. Apache); could expose or spoof internal server hostnames in Fuelix’s logs.x-forwarded-port— Stripped: port metadata used by some proxies; a client-supplied value could mislead URL construction in frameworks that build absolute redirect targets.x-forwarded-prefix— Stripped: URL-prefix header used by Spring Boot and similar frameworks; could affect path rewriting or routing in the upstream application layer.x-real-ip— Stripped: redundant withX-Forwarded-For(which is overwritten fromCF-Connecting-IP); a client-supplied value would spoof their IP in Fuelix’s logs.forwarded— Stripped: RFC 7239 structured equivalent ofX-Forwarded-*; carries the same spoofing risk.
Browser request metadata headers (stripped by prefix — irrelevant to upstream and leak client context):
sec-fetch-site,sec-fetch-mode,sec-fetch-dest,sec-fetch-user— Browser security metadata; no meaning to an API upstream.sec-purpose,sec-gpc— Additional browser security/privacy metadata.- All
sec-*prefixed headers are stripped in a single pass.
SDK telemetry headers (stripped by prefix — prevent leaking client environment metadata to upstream):
x-stainless-lang,x-stainless-version,x-stainless-os,x-stainless-runtime,x-stainless-runtime-version,x-stainless-arch,x-stainless-async— Emitted by Anthropic, OpenAI, and other Stainless-generated SDK clients; they expose client OS, language, SDK version, and runtime to the upstream provider.- All
x-stainless-*prefixed headers are stripped in a single pass.
Gateway-internal headers (stripped by prefix — gateway annotations must not appear on upstream requests):
X-BVE-Worker,X-BVE-Model,X-BVE-Latency,X-BVE-Cache,X-BVE-Signature— BVE Gateway metadata set only on responses. A client injecting anyx-bve-*header into their request would have it forwarded to Fuelix, breaking the protocol separation between gateway-internal annotations and upstream requests.- All
x-bve-*prefixed headers are stripped in a single pass.
Response header filtering
Section titled “Response header filtering”Only a safe subset of upstream headers from Fuelix is forwarded to clients. All other upstream headers (e.g. cf-*, x-cloud-trace-context, set-cookie, via, x-powered-by, and all x-basicllm-*) are stripped. Forwarded headers:
| Header | Source | Notes |
|---|---|---|
content-type | Fuelix upstream | |
content-length | Fuelix upstream | |
content-disposition | Fuelix upstream | Present on file download responses |
cache-control | Fuelix upstream | |
x-request-id | Fuelix upstream | Fuelix’s own request ID |
request-id | Fuelix upstream | Anthropic Messages API uses this header name |
x-quota-allowed | Fuelix upstream | |
x-quota-available | Fuelix upstream | |
x-quota-reset | Fuelix upstream | |
x-ratelimit-limit-requests | Fuelix upstream | Reflects Fuelix upstream account limits |
x-ratelimit-limit-tokens | Fuelix upstream | Reflects Fuelix upstream account limits |
x-ratelimit-remaining-requests | Fuelix upstream | Reflects Fuelix upstream account limits |
x-ratelimit-remaining-tokens | Fuelix upstream | Reflects Fuelix upstream account limits |
x-ratelimit-reset-requests | Fuelix upstream | Reflects Fuelix upstream account limits |
x-ratelimit-reset-tokens | Fuelix upstream | Reflects Fuelix upstream account limits |
retry-after | Fuelix upstream | Forwarded from upstream 429/503 responses |
anthropic-ratelimit-requests-limit | Fuelix upstream | Anthropic Messages API |
anthropic-ratelimit-requests-remaining | Fuelix upstream | Anthropic Messages API |
anthropic-ratelimit-requests-reset | Fuelix upstream | Anthropic Messages API |
anthropic-ratelimit-tokens-limit | Fuelix upstream | Anthropic Messages API |
anthropic-ratelimit-tokens-remaining | Fuelix upstream | Anthropic Messages API |
anthropic-ratelimit-tokens-reset | Fuelix upstream | Anthropic Messages API |
x-groq-request-id | Fuelix upstream | Groq request ID for log correlation and support tickets |
x-groq-processing-time | Fuelix upstream | Groq server-side inference duration in seconds (e.g. 0.4823); validated and capped at 300 s |
openai-processing-ms | Fuelix upstream | OpenAI server-side processing time in milliseconds |
x-cohere-request-id | Fuelix upstream | Cohere request correlation ID for support tickets |
x-openrouter-model | Fuelix upstream | Actual model ID selected by OpenRouter after provider routing |
x-or-cache-status | Fuelix upstream | OpenRouter semantic cache result: HIT or MISS |
x-or-remaining-tokens | Fuelix upstream | Token budget remaining in the OpenRouter rate-limit window |
X-Request-Id | Added by gateway | Per-request UUID (distinct from upstream x-request-id) |
X-BVE-Latency | Added by gateway | Total gateway latency in milliseconds |
X-BVE-Model | Added by gateway | Model ID processed by the gateway (from request body or response JSON) |
X-BVE-Cache | Added by gateway | Model-list cache status for GET /v1/models: HIT, STALE, or MISS |
X-BVE-Worker | Added by gateway | Deployed Worker version UUID — lets operators identify the exact version from curl or browser dev tools (production only; absent in local dev) |
Structured log credential redaction
Section titled “Structured log credential redaction”The gateway’s structured request logger applies redactSecrets() to several user-controlled fields before emitting each log line. This prevents accidental credential leakage in wrangler tail and Logpush records.
Fields that are redacted
Section titled “Fields that are redacted”| Log field | Redaction applied | Why |
|---|---|---|
model | Provider key patterns replaced with [REDACTED] | A malicious client could set the model field to a key-like string (e.g. sk-proj-...). The field is validated and blocked, but the raw value is still set in context before the 403 is returned, and would appear in the log without redaction. |
ua (User-Agent) | Provider key patterns replaced with [REDACTED] | Some SDK versions and custom HTTP clients embed API key material in their User-Agent string (e.g. my-client/1.0 (api-key=sk-bve-...)). Without redaction these keys appear verbatim in every log record. |
query | Credential-like query param values replaced with [REDACTED] | A misconfigured SDK may pass auth via query string (?api_key=sk-bve-...). The gateway strips these before forwarding to Fuelix, but without log redaction the raw value would appear in the structured log. Redacted params: api_key, api-key, apikey, access_token, secret, token, key, password. |
Provider key patterns detected
Section titled “Provider key patterns detected”redactSecrets() matches the following key prefixes and replaces matched substrings with [REDACTED]:
| Prefix | Provider |
|---|---|
sk- | OpenAI / BVE Gateway API keys |
sk-ant- | Anthropic |
sk-proj- | OpenAI project keys |
fuelix- | Fuelix upstream keys |
gsk_ | Groq |
r8_ | Replicate |
AIzaSy | Google AI Studio / Vertex AI |
hf_ | Hugging Face |
nvapi- | NVIDIA NIM / NGC API keys |
xai- | xAI Grok |
co_ | Cohere (v2 API keys; appears in embedding and chat tool-call error messages) |
tvly- | Tavily AI (web search; appears in agent/RAG tool-call flows) |
pcsk_ | Pinecone serverless (vector DB; appears in RAG tool-call flows) |
wcs_ | Weaviate Cloud Service (vector DB; appears in RAG/hybrid-search retrieval flows) |
pa- | Voyage AI (embedding API keys; format: pa- + 32+ chars; appears in RAG retrieval flows) |
sk_ | ElevenLabs (underscore variant: sk_ + 8+ alphanum; distinct from OpenAI’s sk- hyphen form) |
pplx- | Perplexity AI (appears in agent/RAG pipelines that use Perplexity for web-grounded generation) |
fw_ | Fireworks AI (fw_ + 20+ alphanum; fast-inference provider for Llama/Mixtral; appears in agent pipelines) |
AKIA | AWS IAM access key IDs (AKIA + exactly 16 uppercase alphanum; surface when AWS-integrated services reject auth) |
dapi | Databricks personal access tokens (dapi + 32 alphanum; Databricks is a common LLM/embedding backend) |
ghp_, gho_, ghs_, ghu_ | GitHub classic tokens — personal (ghp_), OAuth (gho_), server-to-server (ghs_), user-to-server (ghu_); always 40 chars total |
github_pat_ | GitHub fine-grained personal access tokens (introduced 2022; 20+ chars after the 11-char prefix) |
glpat- | GitLab personal access tokens (glpat- + 20+ alphanum/hyphen; common in CI/CD pipelines) |
csk- | Cerebras Cloud API keys (csk- + 8+ alphanum/hyphen; fast-inference provider for large open-weight models) |
Non-JSON upstream 4xx response bodies (plain text, HTML error pages from upstream CDN/auth layers) also have redactSecrets() applied before being forwarded to the API client.
Worker secrets
Section titled “Worker secrets”All sensitive values must be set as Cloudflare Worker secrets (not plain vars):
| Secret | Purpose | Set with |
|---|---|---|
FUELIX_API_KEY | Upstream Fuelix API key | bunx wrangler secret put FUELIX_API_KEY |
ADMIN_API_KEY | Admin API authentication | bunx wrangler secret put ADMIN_API_KEY |
API_KEY_PEPPER | Key hash pepper | bunx wrangler secret put API_KEY_PEPPER |
WEBHOOK_URL | Alert webhook endpoint (optional) | bunx wrangler secret put WEBHOOK_URL |
WEBHOOK_SECRET | HMAC signing key for webhook requests (optional) | bunx wrangler secret put WEBHOOK_SECRET |
The WEBHOOK_URL secret is optional. When set, the queue consumer POSTs a structured JSON notification to this URL for every alert event (quota warnings, expiry alerts, cron failures) and every audit event (key lifecycle changes). Leave unset to disable webhook notifications.
The WEBHOOK_SECRET secret is optional. When set alongside WEBHOOK_URL, every webhook POST includes an X-BVE-Signature: sha256=<hmac-sha256-hex> header so your receiver can verify authenticity. See Deployment Notes for the verification pattern.
Rotate the first three if the Worker is compromised. Secrets are not visible in Wrangler config or logs after being set.
Key revocation
Section titled “Key revocation”Revocation is permanent and immediate. A revoked key:
- Returns
403 api_key_revokedon all subsequent requests. - Cannot be restored — create a new key if access is needed again.
To revoke:
curl -X POST https://api.bve.me/admin/api-keys/KEY_UUID/revoke \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"