Back to Blog
critical SEVERITY7 min read

How hardcoded API credentials in client-side JavaScript happens in userscripts and how to fix it

The jhs-enhance.user.js userscript contained a hardcoded Imgur API Client-ID embedded directly in client-side JavaScript code, exposing it to anyone who installed or viewed the script source. This critical vulnerability allowed unauthorized users to extract and abuse the API credentials for unlimited image uploads. The fix replaced the hardcoded credential with a user-prompt mechanism that requires each user to provide their own Imgur Client-ID.

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

Answer Summary

This is a hardcoded API credential vulnerability (CWE-798) in a JavaScript userscript where an Imgur Client-ID was embedded in plaintext at line 10812 of dist/jhs-enhance.user.js. The fix replaces the hardcoded `Authorization: "Client-ID d70305e7c3ac5c6"` with a prompt-based system that asks users to input their own Imgur Client-ID, storing it in localStorage for future use. This prevents credential theft and ensures each user operates under their own API quota.

Vulnerability at a Glance

cweCWE-798 (Use of Hard-coded Credentials)
fixReplace hardcoded credential with user-provided Client-ID stored in localStorage
riskUnauthorized API access, quota exhaustion, service abuse
languageJavaScript
root causeImgur Client-ID hardcoded in authorization header at line 10812
vulnerabilityHardcoded API credentials in client-side code

Introduction

In the jhs-enhance.user.js file, we discovered a critical hardcoded credential vulnerability at line 10812 where an Imgur API Client-ID (d70305e7c3ac5c6) was embedded directly in the authorization header of a fetch call. This userscript, distributed to all users, made the credential trivially extractable by anyone who installed the script or viewed its source code. The ImageRecognitionPlugin's image upload functionality unknowingly exposed this shared API key to potentially thousands of users, creating a perfect storm for API abuse.

The vulnerable code resided in both the source file (src/plugins/image-recognition.js) and the distributed userscript (dist/jhs-enhance.user.js), meaning every installation contained the plaintext credential. Unlike server-side API keys that can be protected with environment variables and access controls, client-side JavaScript is inherently public—making this a textbook case of why credentials should never be embedded in distributed code.

The Vulnerability Explained

The vulnerability existed in the image upload functionality where the code needed to authenticate with Imgur's API to upload images for recognition purposes. Here's the exact vulnerable code from line 10809-10816:

const response = await fetch("https://api.imgur.com/3/image", {
  method: "POST",
  headers: {
    Authorization: "Client-ID d70305e7c3ac5c6"  // ← Hardcoded credential!
  },
  body: formData
});

The problem is glaringly obvious: the Imgur Client-ID d70305e7c3ac5c6 is hardcoded as a string literal in the Authorization header. Since userscripts are distributed as plain JavaScript files, anyone could:

  1. Install the userscript and open their browser's developer tools
  2. Search the source for "Client-ID" or "Authorization"
  3. Extract the credential in under 10 seconds
  4. Use the stolen Client-ID to make unlimited Imgur API calls

Real-World Exploitation Scenario

Let's walk through a concrete attack using the actual code:

  1. Attacker installs jhs-enhance.user.js from the distribution repository
  2. Opens dist/jhs-enhance.user.js:10812 in any text editor
  3. Extracts d70305e7c3ac5c6 from the Authorization header
  4. Writes a simple script to upload thousands of images:
// Attacker's exploit script
for (let i = 0; i < 10000; i++) {
  fetch("https://api.imgur.com/3/image", {
    method: "POST",
    headers: {
      Authorization: "Client-ID d70305e7c3ac5c6"  // Stolen from userscript
    },
    body: generateSpamImage()
  });
}

The impact is severe:
- Quota exhaustion: The legitimate application's Imgur API quota gets consumed by attackers
- Service disruption: Real users can't upload images when the quota is exhausted
- Cost implications: If the API has paid tiers, unauthorized usage could incur charges
- Reputation damage: Imgur might ban the Client-ID for abuse, breaking the feature for all users

The ImageRecognitionPlugin's image-recognition.js file handles user-submitted images for reverse image search functionality. When a user wants to search for an image, the plugin uploads it to Imgur to generate a public URL, then uses that URL with search engines. This workflow requires Imgur API authentication—but sharing one credential across all users created a single point of failure.

The Fix

The fix fundamentally changes the credential management approach by shifting responsibility from the application to individual users. Here's the before-and-after comparison:

Before (Vulnerable):

const response = await fetch("https://api.imgur.com/3/image", {
  method: "POST",
  headers: {
    Authorization: "Client-ID d70305e7c3ac5c6"  // Shared, hardcoded credential
  },
  body: formData
});

After (Secure):

const idKey = "jhs_imgurClientId";
let clientId = localStorage.getItem(idKey);
if (!clientId) {
  clientId = window.prompt("请输入您自己的Imgur Client-ID(可前往 https://api.imgur.com/oauth2/addclient 免费申请)用于图片上传搜索:");
  if (!clientId) throw new Error("未提供Imgur Client-ID,无法上传图片");
  localStorage.setItem(idKey, clientId);
}
const response = await fetch("https://api.imgur.com/3/image", {
  method: "POST",
  headers: {
    Authorization: `Client-ID ${clientId}`  // User-provided credential
  },
  body: formData
});

How This Fix Solves the Problem

The fix implements a user-provided credential model with three key security improvements:

  1. No hardcoded secrets: The Client-ID is no longer embedded in the code. Line 10812 now uses a variable ${clientId} instead of the hardcoded string.

  2. User-specific credentials: Each user must provide their own Imgur Client-ID by visiting https://api.imgur.com/oauth2/addclient and registering for a free API key. The prompt message (in Chinese) guides users through this process.

  3. Persistent storage: The Client-ID is stored in localStorage under the key jhs_imgurClientId, so users only need to enter it once. Subsequent uploads reuse the stored credential.

  4. Fail-safe behavior: If the user cancels the prompt without providing a Client-ID, the code throws an error ("未提供Imgur Client-ID,无法上传图片") rather than attempting an unauthenticated request.

Why Both Files Were Changed

The fix modified two files because the codebase maintains both source and distribution versions:

  • src/plugins/image-recognition.js: The source file where developers work. This ensures future builds include the fix.
  • dist/jhs-enhance.user.js: The compiled/distributed userscript that users actually install. This provides immediate protection for existing installations.

Both files contained identical vulnerable code at their respective authorization header lines, so both required the same fix to eliminate the hardcoded credential completely.

Key Takeaways

  • The jhs-enhance.user.js userscript exposed the Imgur Client-ID d70305e7c3ac5c6 at line 10812, making it extractable by any user who installed or viewed the script source
  • Client-side JavaScript is inherently public—any credential embedded in a userscript, browser extension, or web application frontend should be considered compromised
  • The fix replaced the hardcoded Authorization header with a localStorage-backed user prompt, requiring each user to provide their own Imgur Client-ID from https://api.imgur.com/oauth2/addclient
  • Both src/plugins/image-recognition.js and dist/jhs-enhance.user.js required fixes because the codebase maintains source and distribution versions that both contained the vulnerable code
  • User-provided credentials shift API quota responsibility to individual users, preventing a single compromised key from disrupting service for all users

How Orbis AppSec Detected This

  • Source: The hardcoded string literal "Client-ID d70305e7c3ac5c6" embedded in the source code
  • Sink: The Authorization header in the fetch() call at dist/jhs-enhance.user.js:10812 and src/plugins/image-recognition.js:165
  • Missing control: No mechanism to separate credentials from code; the Client-ID was directly embedded rather than being user-provided or server-managed
  • CWE: CWE-798 (Use of Hard-coded Credentials)
  • Fix: Replaced the hardcoded Client-ID with a prompt-based system that requests user-provided credentials and stores them in localStorage

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 hardcoded Imgur Client-ID in jhs-enhance.user.js demonstrates why client-side code can never safely contain secrets. By moving to a user-provided credential model, the fix eliminates the shared secret vulnerability while maintaining functionality. The key lesson: treat all client-side code as public, design authentication flows accordingly, and use tools like Orbis AppSec to catch credential leaks before they reach production. Whether you're building userscripts, browser extensions, or web applications, never embed API keys in code that users can access—your security posture depends on it.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2

Related Articles

critical

How credential leakage through console logging happens in JavaScript browser extensions and how to fix it

A browser extension's `src/background/credentials.js` printed the full Strava authentication cookie string — including a signed JWT and CloudFront-Signature values — straight into the extension console via `console.debug`. Anyone who could open DevTools on the background page (or any tooling that scraped the console) could copy a live session and impersonate the user. The fix replaces the credential payload in both log statements with `Boolean(credentials)` and strips a realistic-looking JWT out

critical

How Hardcoded Secrets Compromise Authentication in JavaScript and How to Fix It

A critical vulnerability in `Tool/QuantumultX/Rewrite/RRSP.js` exposed hardcoded API authentication credentials—a TOKEN and UMID device identifier—directly in source code. Anyone with repository access could extract these credentials to impersonate the legitimate user and gain full account access to the RRTV API service. The fix replaced hardcoded secrets with empty placeholders, forcing users to manually configure credentials through secure channels.

critical

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.

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.