Back to Blog
critical SEVERITY8 min read

How API Key Exposure in Request Bodies happens in React and how to fix it

The Chatbot component in gitforme was transmitting Azure OpenAI API keys inside JSON request bodies, causing them to be logged by servers, proxies, and middleware. By moving the apiKey from requestBody.apiKey to an Authorization header, credentials are now protected from persistence in generic request logging infrastructure.

O
By Orbis AppSec
Published September 9, 2026Reviewed September 9, 2026

Answer Summary

API Key Exposure in Request Bodies is a credential disclosure vulnerability (CWE-319) in React/JavaScript where sensitive tokens sent in HTTP POST bodies get logged by servers, proxies, and debugging tools. In gitforme's Chatbot.jsx at line 120, the Azure OpenAI apiKey was included in requestBody.apiKey and sent as JSON, making it visible in browser DevTools, server logs, and any intermediate systems. The fix moves the API key to an Authorization: Bearer header, which is typically excluded from body logging and treated as sensitive metadata by infrastructure.

Vulnerability at a Glance

cweCWE-319 (Cleartext Transmission of Sensitive Information)
fixMove apiKey from requestBody to Authorization: Bearer header
riskCredentials logged in server request logs, proxy logs, browser DevTools, and debug output
languageJavaScript (React/JSX)
root causeSensitive API key included in JSON request body instead of protected header
vulnerabilityAPI Key Exposure in Request Bodies

Title: How API Key Exposure in Request Bodies happens in React and how to fix it


VULNERABILITY_AT_A_GLANCE: API Key Exposure in Request Bodies (CWE-319) in React/JavaScript — Azure OpenAI credentials sent in JSON bodies were being logged by servers and proxies. Fixed by moving apiKey from requestBody to Authorization: Bearer header in Chatbot.jsx:123.


Introduction

In the gitforme repository, we discovered a critical credential exposure vulnerability in gitforme/src/components/Chatbot.jsx that put Azure OpenAI API keys at risk of compromise. The useChat hook's message submission logic (around line 120) was packing sensitive credentials directly into JSON request bodies, creating a dangerous paper trail across multiple systems.

The problematic pattern was deceptively simple: when users provided custom Azure OpenAI configuration, the code added their apiKey to the same object as chat messages and parameters:

// VULNERABLE CODE (lines 120-127, before fix)
if (azureEndpoint && apiKey && deployment) {
  requestBody.azureEndpoint = azureEndpoint;
  requestBody.apiKey = apiKey;  // ← CRITICAL: API key in body
  requestBody.deployment = deployment;
  requestBody.apiVersion = apiVersion;
}

This meant every chat request carried the raw API key through infrastructure explicitly designed to log and inspect request payloads. For developers building AI-powered applications, this pattern represents a common but serious architectural mistake: conflating application data with authentication credentials.


The Vulnerability Explained

The Specific Code Pattern

The vulnerability resided in the useChat custom hook within Chatbot.jsx. When constructing the requestBody object for the /api/chat endpoint, the code conditionally added Azure configuration:

// BEFORE (vulnerable)
const requestBody = {
  messages: updatedMessages,
  model: selectedModel,
  systemPrompt,
  temperature,
};

if (azureEndpoint && apiKey && deployment) {
  requestBody.azureEndpoint = azureEndpoint;
  requestBody.apiKey = apiKey;        // Line 124: Key added to body
  requestBody.deployment = deployment;
  requestBody.apiVersion = apiVersion;
}

const response = await fetch("https://gitforme-bot.onrender.com/api/chat", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(requestBody),  // Key serialized to JSON
});

Why This Creates Multiple Exposure Vectors

1. Server-Side Request Logging
Most web servers and application frameworks log incoming request bodies for debugging, monitoring, and audit purposes. Express.js with morgan, Python's logging middleware, and cloud platforms like AWS API Gateway all capture request payloads by default. The Azure OpenAI key would appear verbatim in these logs.

2. Proxy and Load Balancer Logs
Infrastructure components between client and server—NGINX, HAProxy, Cloudflare, AWS ALB—often log request bodies for traffic analysis. These systems typically have different retention policies and access controls than application databases.

3. Browser Developer Tools
Any user could open Chrome DevTools, navigate to the Network tab, and see their own API key in the request payload. This creates social engineering risks and complicates credential rotation.

4. Error Reporting and APM Tools
Services like Sentry, Datadog, and New Relic capture request context when errors occur. If an exception happened during chat processing, the API key would be bundled into the error report.

Real-World Attack Scenario

Consider this chain of events in the gitforme application:

  1. A user configures their own Azure OpenAI deployment with a corporate API key
  2. They send a chat message, which triggers useChat's sendMessage function
  3. The request passes through Cloudflare (logged), reaches the Render-hosted backend (logged by gitforme-bot.onrender.com), and potentially triggers an error
  4. An error report sent to Sentry contains the full request body with apiKey
  5. A compromised Sentry account or insider threat now has access to the Azure OpenAI key
  6. The attacker uses the key to exhaust quota, access proprietary model deployments, or pivot to other Azure resources

The key insight: HTTPS encrypts data in transit, but it does not control what happens after decryption. The receiving system and all intermediaries see the plaintext body.


The Fix

The Specific Changes

The fix modifies gitforme/src/components/Chatbot.jsx with a surgical three-line change that preserves all functionality while eliminating the exposure:

@@ -120,16 +120,19 @@ const useChat = () => {
       };

       // ✅ Only include Azure creds if all 3 are present
+      const headers = { "Content-Type": "application/json" };
       if (azureEndpoint && apiKey && deployment) {
         requestBody.azureEndpoint = azureEndpoint;
-        requestBody.apiKey = apiKey;
         requestBody.deployment = deployment;
         requestBody.apiVersion = apiVersion;
+        // 🔒 Send the API key via the Authorization header rather than the
+        // JSON body so it is not persisted alongside generic request-body logs.
+        headers.Authorization = `Bearer ${apiKey}`;
       }

       const response = await fetch("https://gitforme-bot.onrender.com/api/chat", {
         method: "POST",
-        headers: { "Content-Type": "application/json" },
+        headers,
         body: JSON.stringify(requestBody),
       });

How This Solves the Problem

Aspect Before (Vulnerable) After (Fixed)
API Key Location requestBody.apiKey in JSON payload Authorization: Bearer <key> header
Server Logging Captured in request body logs Typically excluded or redacted in header logs
DevTools Visibility Visible in "Payload" tab Visible in "Request Headers" section
Proxy Logging Logged by body-inspecting middleware Usually filtered by header-sensitive configurations

The fix leverages a fundamental HTTP architectural principle: headers and bodies have different semantics and handling conventions. Authentication credentials belong in headers because:

  1. Semantic correctness: The Authorization header was specifically designed for this purpose (RFC 7235)
  2. Infrastructure expectations: Logging systems routinely redact or exclude Authorization headers from output
  3. Security boundaries: Headers are processed by authentication layers before reaching application logic

Notably, the fix maintains backward compatibility: the backend can continue reading azureEndpoint, deployment, and apiVersion from the body while extracting the key from the header.


Prevention & Best Practices

For React/JavaScript Applications

  1. Never mix secrets with application data
    ```javascript
    // ❌ BAD: Secret in data object
    const body = { message, apiKey: 'sk-...' };

// ✅ GOOD: Separate concerns
const body = { message };
const headers = { 'Authorization': Bearer ${apiKey} };
```

  1. Use environment-specific credential handling
    - Development: Proxy requests through a backend that injects credentials
    - Production: Use token exchange patterns (OAuth 2.0, managed identities)

  2. Implement request sanitization in backend logging
    javascript // Express.js example: redact sensitive headers AND body fields const morgan = require('morgan'); morgan.token('body', (req) => { const sanitized = { ...req.body }; delete sanitized.apiKey; delete sanitized.password; return JSON.stringify(sanitized); });

  3. Audit third-party libraries
    The gitforme application uses a custom useChat hook. When using libraries like openai or @azure/openai, verify they don't expose keys in error messages or debug output.

Detection Tools

Tool Capability Rule/Pattern
Semgrep Detects API keys in fetch bodies pattern: $BODY.apiKey = $KEY with fetch(..., {body: JSON.stringify($BODY)})
GitHub Secret Scanning Detects committed Azure keys Azure OpenAI key patterns
ESLint Custom rule for fetch body inspection no-sensitive-body-properties
Orbis AppSec Full data-flow analysis Tracks apiKey from source to sink

Security Standards

  • CWE-319: Cleartext Transmission of Sensitive Information
  • CWE-532: Insertion of Sensitive Information into Log File
  • OWASP API Security Top 10 2023: API2:2023 — Broken Authentication
  • OWASP Cheat Sheet: Logging Cheat Sheet — specifically "Data to Exclude" section

Key Takeaways

  • Never place apiKey in requestBody objects sent via fetch() or XMLHttpRequest: The Chatbot.jsx vulnerability demonstrates that even seemingly temporary request objects create persistent exposure through logging infrastructure.

  • The Authorization header is the standard location for bearer tokens: The fix uses headers.Authorization = \Bearer ${apiKey}`` following RFC 6750, which infrastructure tools recognize and typically protect.

  • Conditional credential inclusion requires careful header construction: The fix creates a mutable headers object before the conditional block, allowing Authorization to be added only when Azure credentials are present—preserving the original "only include if all 3 are present" logic.

  • HTTPS is necessary but not sufficient for credential protection: Transport encryption prevents network eavesdropping, but the receiving system's logging behavior determines actual exposure risk.

  • Review all fetch() calls in React applications for body inspection: Search for patterns where variables named key, token, secret, or password appear in object literals passed to JSON.stringify() or body: properties.


How Orbis AppSec Detected This

Source: User input through React state variables apiKey (controlled by setApiKey in component state)

Sink: The fetch() call to https://gitforme-bot.onrender.com/api/chat in gitforme/src/components/Chatbot.jsx:127 where requestBody.apiKey was serialized into the JSON payload

Missing control: No transformation of the credential from body content to header metadata; absence of Authorization header usage for bearer token transmission

CWE: CWE-319 (Cleartext Transmission of Sensitive Information) — with secondary classification as CWE-532 (Insertion of Sensitive Information into Log File)

Fix: Replaced requestBody.apiKey = apiKey with headers.Authorization = \Bearer ${apiKey}``, removing the key from the logged request body while maintaining authentication capability

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

The Chatbot.jsx vulnerability illustrates how a single line—requestBody.apiKey = apiKey—can cascade into systemic credential exposure across multiple systems. The fix demonstrates that security improvements need not disrupt functionality: by understanding HTTP semantics and respecting the boundary between headers and bodies, we eliminated the exposure while preserving all Azure OpenAI integration capabilities.

For developers building AI-powered applications, this case serves as a reminder that credential handling deserves architectural attention. The convenience of packing all parameters into a single request object must be weighed against the reality of how infrastructure processes those requests. When in doubt: headers for identity, bodies for data.


References

  • CWE-319: Cleartext Transmission of Sensitive Information — https://cwe.mitre.org/data/definitions/319.html
  • CWE-532: Insertion of Sensitive Information into Log File — https://cwe.mitre.org/data/definitions/532.html
  • OWASP Logging Cheat Sheet — https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html
  • RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage — https://tools.ietf.org/html/rfc6750
  • MDN: Authorization header — https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
  • Semgrep rule: detect-secrets-in-body — https://semgrep.dev/r?q=javascript.lang.security.detect-secrets-in-body
  • GitHub PR: fix: fix security issue in Chatbot.jsx

Frequently Asked Questions

What is API Key Exposure in Request Bodies?

It's a vulnerability where sensitive API credentials are transmitted inside HTTP request bodies (typically JSON), causing them to be captured in server logs, proxy logs, browser developer tools, and debugging infrastructure that logs request payloads.

How do you prevent API Key Exposure in React/JavaScript?

Always send API keys and tokens in HTTP headers (typically Authorization: Bearer or custom headers like X-API-Key) rather than request bodies. Headers are excluded from body logging and treated as sensitive by most infrastructure.

What CWE is API Key Exposure in Request Bodies?

CWE-319: Cleartext Transmission of Sensitive Information. It can also relate to CWE-532 (Insertion of Sensitive Information into Log File) and CWE-312 (Cleartext Storage of Sensitive Information).

Is HTTPS enough to prevent API Key Exposure in Request Bodies?

No. HTTPS protects data in transit from eavesdropping, but it does not prevent the receiving server, proxies, load balancers, or debugging tools from logging the request body containing the key. Header-based transmission provides defense in depth.

Can static analysis detect API Key Exposure in Request Bodies?

Yes. Static analysis can flag patterns where variables named apiKey, token, or secret are assigned to request body objects, or where fetch/axios calls include sensitive data in the body instead of headers.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #63

Related Articles

critical

How Hardcoded API Keys in WASM Modules Happen in KAP and How to Fix Them

A critical security vulnerability in `wasm/kap/standard-lib/fhelp-impl.kap` exposed hardcoded Gemini API keys directly in source code distributed to end users via WASM modules. The fix replaces the embedded credential with secure environment variable retrieval, preventing credential extraction through browser developer tools or binary inspection.

critical

How Hardcoded API Key Exposure Happens in Node.js and How to Fix It

A critical vulnerability in `archive/open_claude_code/src/api/client.mjs` exposed five different API providers' credentials through direct `process.env` access. The fix introduced a centralized `readApiKey()` function to enforce secure credential retrieval across Anthropic, OpenAI, Google, and other integrations.

high

How Insufficiently Protected Credentials happens in Node.js and how to fix it

A regex-based guard in `skills/xmemo/scripts/xmemo-skill.mjs` was supposed to block sensitive credentials from being passed as CLI flags, but it only matched a handful of keyword patterns—missing `password`, `client-secret`, `access-token`, `xmemo-key`, and more. The fix expands the blocklist regex to cover these additional credential patterns, closing a gap that could let secrets end up in shell history and process listings.

critical

How Hardcoded Firebase API Keys Happen in JavaScript Service Workers and How to Fix Them

A production Firebase service worker file (`static/firebase-messaging-sw.js`) contained hardcoded API keys and project configuration directly in publicly accessible JavaScript — including a second, previously commented-out set of credentials from an older project. While Firebase web config values are technically public identifiers, pairing them with unrestricted Firebase projects or missing Security Rules turns them into an open door for push notification abuse, quota exhaustion, and unauthorize

critical

How hardcoded API key exposure happens in Node.js plugins and how to fix it

A critical hardcoded API key (`actor-studio-gpt-beta`) was discovered in the `src/plugins/llm/index.js` file of the Actor Studio application, granting anyone with source code access the ability to make unauthorized requests to the LLM service endpoints. The fix removes the default key from both the LLM class definition and the settings module, requiring the key to be explicitly configured through module settings instead.

critical

How insufficient PBKDF2 iterations happen in JavaScript and how to fix it

A critical vulnerability in `libs/wgs/pbkdf2.js` used only 1 iteration for PBKDF2 password hashing, making passwords trivially crackable. The fix increases iterations to 600,000, aligning with OWASP 2023 recommendations and preventing GPU-accelerated brute-force attacks.