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.

Prevention & Best Practices

Never Embed Secrets in Client-Side Code

The fundamental principle: anything in client-side JavaScript is public. This includes:
- API keys and Client-IDs
- OAuth tokens
- Encryption keys
- Database credentials
- Service URLs with embedded authentication

Even obfuscation or minification provides zero security—deobfuscation tools can reverse any JavaScript transformation in seconds.

Secure Alternatives for API Authentication

When building userscripts or browser extensions that need API access, use these patterns:

  1. User-provided credentials (as implemented in this fix):
    - Each user registers for their own API key
    - Store credentials in localStorage or chrome.storage
    - Pros: Simple, no backend needed
    - Cons: Requires user effort, exposes individual keys

  2. Backend proxy:
    javascript // Instead of calling Imgur directly: const response = await fetch("https://your-backend.com/api/upload-image", { method: "POST", body: formData }); // Your backend handles Imgur authentication server-side
    - Pros: Credentials never leave your server
    - Cons: Requires infrastructure, adds latency

  3. OAuth flows:
    - Use OAuth 2.0 to let users authorize your app
    - Store refresh tokens securely (server-side or encrypted client-side)
    - Pros: Industry standard, user revocable
    - Cons: Complex implementation

Detection and Prevention Tools

Protect your codebase from credential leaks with these tools:

  • Secret scanners: Tools like TruffleHog, git-secrets, or detect-secrets scan commits for credential patterns
  • Pre-commit hooks: Block commits containing strings like "Client-ID", "Authorization: Bearer", or API key patterns
  • Static analysis: Use Semgrep rules to detect hardcoded credentials in authorization headers
  • Code review: Train developers to recognize credential patterns and flag them in reviews

OWASP Recommendations

This vulnerability maps to several OWASP guidelines:

  • OWASP Top 10 2021 A07:2021 – Identification and Authentication Failures: Hardcoded credentials represent a critical authentication failure
  • OWASP Application Security Verification Standard (ASVS) V2.10: "Verify that application secrets are not stored in client-side code"
  • OWASP Secrets Management Cheat Sheet: Recommends environment variables, vaults, or user-provided secrets over hardcoded values

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.

References

Frequently Asked Questions

What is hardcoded API credential exposure?

It's when API keys, tokens, or credentials are embedded directly in source code rather than being securely stored or user-provided. In client-side code, this makes credentials trivially extractable by anyone with access to the code.

How do you prevent hardcoded credentials in JavaScript userscripts?

Never embed credentials in client-side code. Instead, require users to provide their own API keys, use OAuth flows, or proxy requests through a secure backend that manages credentials server-side.

What CWE is hardcoded API credential exposure?

CWE-798 (Use of Hard-coded Credentials), which covers authentication credentials embedded in code that can be extracted by attackers.

Is obfuscation enough to prevent credential extraction from JavaScript?

No. JavaScript obfuscation provides minimal security since client-side code is always reversible. Any credential in client-side code should be considered publicly accessible regardless of obfuscation.

Can static analysis detect hardcoded credentials in userscripts?

Yes. Static analysis tools and secret scanners can detect patterns like "Client-ID", "Authorization" headers with string literals, and other credential patterns in JavaScript source code.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2

Related Articles

critical

How Hardcoded HMAC-SHA256 Keys Compromise API Authentication in HarmonyOS and How to Fix It

A critical vulnerability in the Bika application exposed a hardcoded HMAC-SHA256 signing key directly in the Constants.ets file, allowing attackers to forge valid API requests. The fix implements runtime key deobfuscation using XOR masking, removing the plaintext credential from both source code and compiled binaries. This change demonstrates why symmetric keys must never be embedded in client-side code.

critical

How API Key Exposure in URL Parameters happens in Python and how to fix it

The Wine Cellar Home Assistant integration exposed Gemini API keys by transmitting them as URL query parameters in HTTP requests. This critical vulnerability allowed API keys to be logged in server logs, proxy caches, and browser history. The fix moved authentication to the secure `x-goog-api-key` HTTP header, preventing credential leakage.

critical

How Hardcoded API Keys Happen in TOML Configuration Files and How to Fix Them

A hardcoded Google Maps API key was discovered in `exampleSite/config/_default/params.toml` at line 113, exposing a live credential that any attacker could extract from the repository and use to make unauthorized API calls. This critical vulnerability was automatically detected and fixed by replacing the hardcoded key with an empty placeholder, eliminating the risk of credential theft and unauthorized usage charges.

critical

How Plaintext Secret Storage Happens in Cloudflare Workers (wrangler.toml) and How to Fix It

A critical misconfiguration in `platforms/m365/wrangler.toml` left developers one copy-paste away from committing live API keys directly into git history. The fix adds an explicit warning comment blocking the `[vars]` anti-pattern and adds `.dev.vars` to `.gitignore`, ensuring secrets flow through Cloudflare's encrypted `wrangler secret` mechanism instead of plaintext config. This matters because git history is permanent — a key committed even once can be extracted long after it's "deleted."

critical

How Hardcoded API Keys Happen in JavaScript and How to Fix Them

A critical security vulnerability was discovered in `src/js/init.js` where a Bugsnag API key was hardcoded directly into client-side JavaScript, making it visible to anyone who inspects the page source or JavaScript bundle. The fix replaces the hardcoded string with an environment variable reference (`import.meta.env.VITE_BUGSNAG_API_KEY`), ensuring the key is injected at build time rather than baked into the shipped code. This pattern is one of the most common — and most avoidable — secrets exp

critical

How a vulnerable websocket-driver dependency happens in Node.js lockfiles and how to fix it

A Trivy scan flagged `websocket-driver@0.7.4` in this repository's `bun.lock` as affected by CVE-2026-54466, a critical issue in a WebSocket protocol handler that parses untrusted HTTP upgrade requests and frame data. The fix upgrades the package to `0.7.5` and adds an explicit `websocket-driver` entry to the lockfile's override block so every transitive consumer — webpack-dev-server, sockjs, faye-websocket — resolves to the patched build instead of the pinned vulnerable one.