Back to Blog
critical SEVERITY6 min read

How Sensitive Data Exposure happens in Python web applications and how to fix it

A critical sensitive data exposure vulnerability was discovered in `nodes/google_gemini.py` where the Google Gemini API key was returned in plaintext through a web endpoint. The fix masks the token in API responses, preventing credential theft from any client that queries the token endpoint. This protects downstream users of this Node.js library from unauthorized access to their Google Gemini services.

O
By Orbis AppSec
Published August 7, 2026Reviewed August 7, 2026

Answer Summary

This is a Sensitive Data Exposure vulnerability (CWE-200/CWE-312) in a Python web application where the `get_gemini_token` endpoint in `nodes/google_gemini.py` returned a stored Google Gemini API key in plaintext from a JSON file. The fix replaces the actual token value with a masked string ("****") in the API response, ensuring credentials are never exposed through the endpoint while still indicating whether a token is configured.

Vulnerability at a Glance

cweCWE-312 (Cleartext Storage of Sensitive Information)
fixMask the token in API responses by returning "****" instead of the actual credential
riskFull API key theft enabling unauthorized Google Gemini API access
languagePython
root causeThe `get_gemini_token` endpoint returned the raw API key value from the JSON token store
vulnerabilitySensitive Data Exposure / Plaintext Credential Storage

Introduction

The nodes/google_gemini.py file handles API key management for Google Gemini integration, but a critical flaw in the get_gemini_token function at line 29 created a direct credential exposure risk. The endpoint /academia/gemini_token was designed to check whether a token was configured, but instead it returned the actual plaintext API key to any client that made the request.

This vulnerability is particularly dangerous because it exists in a Node.js library consumed by downstream users. Any application integrating this package would unknowingly expose their Google Gemini API credentials through a simple HTTP GET request—no authentication required, no access controls enforced.

The Vulnerability Explained

The vulnerable code path works like this:

  1. A user stores their Google Gemini API key via the /academia/gemini_token POST endpoint
  2. The key is written in plaintext to academia_tokens.json on the filesystem
  3. When the GET endpoint is called, the full API key is read from the file and returned in the JSON response

Here's the vulnerable code:

async def get_gemini_token(request):
    try:
        if os.path.exists(TOKENS_FILE):
            with open(TOKENS_FILE, "r") as f:
                data = json.load(f)
                return web.json_response({"token": data.get("gemini", "")})
    except: pass
    return web.json_response({"token": ""})

The critical line is:

return web.json_response({"token": data.get("gemini", "")})

This returns the raw value of the gemini key from the JSON file—the actual API key in full plaintext.

Attack Scenario

An attacker targeting an application using this library could:

  1. Discover the endpoint: Make a GET request to /academia/gemini_token
  2. Receive the plaintext key: The response contains {"token": "AIzaSyB...actual_key_here..."}
  3. Abuse the key: Use the stolen Google Gemini API key to make unlimited API calls, potentially incurring significant charges or accessing sensitive AI-generated content

This is a 2-step attack chain with minimal complexity:
- Step 1: Access the unprotected endpoint (no authentication required)
- Step 2: Extract the API key from the JSON response

The impact is severe: unauthorized access to Google Gemini services, potential financial abuse through API call charges, and exposure of any data processed through the Gemini API.

Why the Storage is Also Problematic

The academia_tokens.json file stores the key without any encryption:

{
  "gemini": "AIzaSyB...plaintext_api_key..."
}

An attacker with filesystem access (through a path traversal vulnerability, server compromise, or misconfigured backups) could also directly read this file. However, the more immediate risk is the endpoint that actively serves this credential to any requester.

The Fix

The fix applied in nodes/google_gemini.py at line 29 masks the token value in the API response:

Before (Vulnerable):

return web.json_response({"token": data.get("gemini", "")})

After (Fixed):

return web.json_response({"token": "****" if data.get("gemini", "") else ""})

How This Solves the Problem

The fix implements token masking with a simple but effective ternary expression:

  1. If a token exists (data.get("gemini", "") is truthy): Return "****" instead of the actual key
  2. If no token is configured (empty string): Return an empty string ""

This preserves the endpoint's legitimate functionality—allowing the UI to check whether a token has been configured—while completely eliminating the credential exposure. The client can still determine "yes, a token is set" or "no, no token is configured" without ever receiving the actual secret.

The change is minimal (one line) and behavior-preserving for valid use cases. Any frontend code that only needs to know whether a token exists will continue to work correctly. Only code that was improperly relying on reading back the full token will be affected—and that's exactly the attack vector being closed.

Prevention & Best Practices

1. Never Return Secrets Through API Endpoints

API keys, passwords, and tokens should be write-only from the client's perspective. Endpoints should only confirm existence or validity, never return the raw value.

2. Encrypt Credentials at Rest

Even with the endpoint fix, the academia_tokens.json file still contains plaintext credentials. A defense-in-depth approach would encrypt the key before writing:

from cryptography.fernet import Fernet

# Encrypt before storing
cipher = Fernet(encryption_key)
encrypted_token = cipher.encrypt(api_key.encode())

3. Use Environment Variables or Secret Managers

Instead of JSON files, prefer:
- Environment variables (os.environ.get("GEMINI_API_KEY"))
- Secret management services (HashiCorp Vault, AWS Secrets Manager)
- OS-level credential stores

4. Implement Authentication on Sensitive Endpoints

The /academia/gemini_token endpoint should require authentication before returning any information about stored credentials.

5. Apply the Principle of Least Privilege

Even if a token check endpoint is needed, it should return the minimum information necessary—a boolean {"configured": true} rather than any representation of the token.

Relevant Standards

  • OWASP: A02:2021 – Cryptographic Failures
  • CWE-312: Cleartext Storage of Sensitive Information
  • CWE-200: Exposure of Sensitive Information to an Unauthorized Actor

Key Takeaways

  • The get_gemini_token endpoint was functioning as a credential oracle—any client could retrieve the full API key with a single GET request
  • Token presence checks should never return the token itself—the "****" masking pattern preserves UX while eliminating exposure
  • Plaintext JSON storage of API keys (academia_tokens.json) creates a secondary attack surface beyond the endpoint itself
  • One-line fixes can close critical vulnerabilities—the ternary "****" if data.get("gemini", "") else "" completely eliminates the data leak
  • Libraries that store credentials create inherited risk—all downstream consumers of this package were unknowingly exposing their Gemini API keys

How Orbis AppSec Detected This

  • Source: The academia_tokens.json file containing the plaintext Gemini API key, read via json.load(f) in get_gemini_token()
  • Sink: web.json_response({"token": data.get("gemini", "")}) in nodes/google_gemini.py:29 — returning the raw credential in the HTTP response
  • Missing control: No token masking, encryption, or access control between reading the stored credential and returning it in the API response
  • CWE: CWE-312 (Cleartext Storage of Sensitive Information)
  • Fix: Replace the plaintext token value with a masked string "****" in the API response while preserving the ability to check token configuration status

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

This vulnerability demonstrates how a seemingly helpful feature—an endpoint to check your configured API key—can become a critical security flaw when it returns the actual credential instead of just confirming its existence. The fix is elegant in its simplicity: one line of code that masks the token while preserving all legitimate functionality.

For developers building similar credential management features, remember: secrets should flow in one direction only. Accept them, store them securely, use them internally, but never echo them back through any interface. The get_gemini_token endpoint now correctly serves as a configuration status check rather than a credential dispensary.

References

Frequently Asked Questions

What is Sensitive Data Exposure?

Sensitive Data Exposure occurs when an application inadvertently reveals confidential information—such as API keys, passwords, or tokens—to unauthorized parties through insecure storage, transmission, or API responses.

How do you prevent Sensitive Data Exposure in Python?

Use encryption for stored credentials, never return raw secrets through API endpoints, implement access controls on token files, use environment variables or secret management services, and mask sensitive values in any user-facing responses.

What CWE is Sensitive Data Exposure?

CWE-312 (Cleartext Storage of Sensitive Information) and CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor) are the primary CWE identifiers for this vulnerability class.

Is file permission restriction enough to prevent Sensitive Data Exposure?

No. While filesystem permissions help, they don't prevent exposure through application endpoints that read and return the stored data. Defense in depth requires both secure storage AND controlled access through APIs.

Can static analysis detect Sensitive Data Exposure?

Yes. Static analysis tools can identify patterns where sensitive data (identified by variable names like "token", "key", "secret") is read from storage and returned directly in HTTP responses without masking or encryption.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #8

Related Articles

critical

How Sensitive Data Exposure in Error Logging happens in TypeScript/Deno and how to fix it

A critical vulnerability in Supabase Edge Functions allowed sensitive authentication errors and API credentials to leak through verbose error logging. The `cancel-subscription/index.ts` function logged full error objects to the console, potentially exposing Paddle API keys and auth tokens in deployment logs. The fix sanitizes all error messages to log only safe error text while preserving debugging capability.

critical

How HTTP Header Injection Happens in Go and How to Fix It

A critical vulnerability in the file upload handler allowed attackers to inject CRLF sequences into HTTP response headers through crafted filenames. The fix sanitizes user-supplied filenames before using them in Content-Disposition headers, preventing header injection attacks that could lead to cache poisoning, session fixation, or XSS.

high

How Path Traversal and Security Policy Bypass Happens in Node.js Dependencies and How to Fix It

A high-severity vulnerability in the fast-uri package (CVE-2026-6321) allowed attackers to bypass security policies through improper Unicode hostname canonicalization and path traversal. This issue affected the @apralabs/apra-fleet project through its dependency tree, and was resolved by upgrading fast-uri from version 3.1.0 to 4.1.2 using npm overrides.

high

How Command Injection happens in Node.js child_process calls and how to fix it

A high-severity command injection vulnerability was discovered in `tools/utils/lang/helpers.ts` where the `prettier()` function passed a user-controllable `fileName` argument directly into a shell command string via `exec()`. By replacing `exec()` with `execFile()` and passing arguments as an array, the fix eliminates shell interpolation entirely, preventing attackers from injecting arbitrary shell commands through malicious filenames.

high

How Quadratic CPU Consumption in YAML Parsing happens in JavaScript and how to fix it

A high-severity vulnerability in js-yaml versions 3.x and 4.x allowed attackers to cause quadratic CPU consumption through specially crafted YAML documents using the `!!omap` type. This denial-of-service vulnerability (GHSA-5p4m-2wfm-xmqj) was fixed by upgrading from js-yaml 4.3.0 to 4.3.1, protecting applications from algorithmic complexity attacks during YAML parsing.

high

How Arbitrary HTTP Header Injection via Prototype Pollution happens in JavaScript and how to fix it

A high-severity vulnerability (CVE-2026-42035) in axios version 1.13.5 allowed attackers to inject arbitrary HTTP headers through prototype pollution. The fix upgrades axios to version 1.18.0 in the frontend's dependency tree, which includes proper prototype chain validation when constructing HTTP request headers. This prevents attackers from manipulating outgoing requests to perform SSRF, session hijacking, or cache poisoning attacks.