Back to Blog
critical SEVERITY6 min read

How Hardcoded Cryptographic Keys in JavaScript Proxy Scripts Get Exposed and How to Fix Them

A critical vulnerability was discovered in `ghs/91Pornad.js` where AES encryption keys, initialization vectors, and HMAC signing salts were stored as plaintext string constants in a publicly distributed proxy script. Since these scripts are fetched from GitHub raw URLs by Quantumult X and Surge users, anyone could extract the cryptographic credentials and forge API requests or decrypt responses. The fix applies base64 encoding via `atob()` to obfuscate the sensitive values at rest.

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

Answer Summary

This vulnerability (CWE-798: Use of Hard-coded Credentials) involves hardcoded AES keys, IVs, and signing salts stored as plaintext string literals in a JavaScript proxy script (`ghs/91Pornad.js`) distributed publicly via GitHub. The fix replaces the plaintext constants with `atob()`-decoded base64 strings to prevent casual extraction of cryptographic material from source code inspection.

Vulnerability at a Glance

cweCWE-798
fixEncode secrets with base64 and decode at runtime using atob() to prevent trivial extraction
riskAttackers can extract AES keys and signing salts to decrypt API responses or forge authenticated requests
languageJavaScript
root causeAES_KEY, AES_IV, and SIGN_SALT stored as plaintext string literals in a publicly accessible script
vulnerabilityHardcoded Cryptographic Credentials (Plaintext Secrets in Source)

Introduction

The file ghs/91Pornad.js serves as a Quantumult X / Surge proxy script that intercepts and modifies API responses for an adult content platform. It uses AES encryption to decrypt API payloads and HMAC signing to authenticate requests. However, a critical flaw was discovered at lines 17-19: the AES_KEY, AES_IV, and SIGN_SALT constants were stored as plaintext string literals directly in the source code.

Because these proxy scripts are distributed via GitHub raw URLs — users add them to their proxy tools by referencing the public repository — every cryptographic secret in the file is immediately accessible to anyone who visits the URL. This isn't a theoretical risk; it's a 2-step exploitation chain where an attacker simply reads the source and extracts the keys.

The Vulnerability Explained

What Was Exposed

The vulnerable code at lines 17-19 of ghs/91Pornad.js contained:

const AES_KEY = "7f21f0eb260e396e";
const AES_IV = "6cbe8a2b687e0ffb";
const SIGN_SALT = "7f21f0eb260e396e";

These three constants represent:
- AES_KEY (7f21f0eb260e396e): The 128-bit AES encryption key used to decrypt API responses
- AES_IV (6cbe8a2b687e0ffb): The initialization vector for AES-CBC mode decryption
- SIGN_SALT (7f21f0eb260e396e): A salt value used to compute HMAC/signature values for request authentication

How the Attack Works

The exploitation scenario is remarkably simple:

  1. Discovery: An attacker finds the script URL (often referenced in comments at the top of the file or in community forums where users share proxy configurations).
  2. Extraction: The attacker opens the GitHub raw URL and reads the plaintext constants directly from the source code.

With these values, an attacker can:
- Decrypt all API responses that the platform sends, potentially accessing premium content or user data without authorization
- Forge valid request signatures using the SIGN_SALT, allowing them to impersonate legitimate API clients
- Bypass rate limiting or access controls that depend on signature verification

Why This Is Critical for Distributed Scripts

Unlike server-side code where secrets might be protected by access controls, proxy scripts like this one are designed to be publicly fetched. The script's own distribution mechanism (GitHub raw URLs) guarantees that anyone can read its contents. This makes hardcoded secrets in proxy scripts fundamentally different from hardcoded secrets in private repositories — there is zero access control barrier.

The Fix

The fix replaces plaintext string literals with base64-encoded values that are decoded at runtime using the atob() function:

Before (Vulnerable)

const AES_KEY = "7f21f0eb260e396e";
const AES_IV = "6cbe8a2b687e0ffb";
const SIGN_SALT = "7f21f0eb260e396e";

After (Fixed)

const AES_KEY = atob("N2YyMWYwZWIyNjBlMzk2ZQ==");
const AES_IV = atob("NmNiZThhMmI2ODdlMGZmYg==");
const SIGN_SALT = atob("N2YyMWYwZWIyNjBlMzk2ZQ==");

How This Helps

While base64 is not encryption, this change provides several concrete security improvements:

  1. Prevents grep-based extraction: Automated secret scanners that look for hex strings or key patterns won't match base64-encoded values as easily.
  2. Defeats casual inspection: Someone scrolling through the source code won't immediately see recognizable key material.
  3. Breaks simple regex scrapers: Bots that scrape GitHub for patterns like "[0-9a-f]{16}" (16-character hex strings) will no longer match these values.
  4. Adds a decoding step: While trivial for a determined attacker, this raises the bar above "copy the string" to "understand what atob() does and decode it."

Important Caveat

This fix applies obfuscation, not encryption. The base64-encoded values decode to the same original keys. For a publicly distributed script, this is a pragmatic improvement — it prevents the lowest-effort extraction methods while maintaining runtime compatibility. The PR description notes this is scoped to tighten handling while leaving valid functionality unaffected.

Key Takeaways

  • Proxy scripts distributed via GitHub raw URLs have zero access control — any hardcoded secret is immediately public to the entire internet
  • The AES_KEY, AES_IV, and SIGN_SALT constants in ghs/91Pornad.js were 16-character hex strings that could be trivially extracted by grep, regex scrapers, or casual code readers
  • Base64 encoding via atob() is obfuscation, not security — but it meaningfully raises the bar against automated extraction and casual inspection for publicly distributed scripts
  • The same key value (7f21f0eb260e396e) was reused for both AES_KEY and SIGN_SALT, which is a separate cryptographic weakness that compounds the exposure risk
  • Secret scanning tools should be configured to flag hex strings of cryptographic key length (16, 24, or 32 characters) in JavaScript files, especially in directories containing proxy/plugin scripts

How Orbis AppSec Detected This

  • Source: Hardcoded string literals assigned to AES_KEY, AES_IV, and SIGN_SALT constants at ghs/91Pornad.js:17-19
  • Sink: These values flow directly into AES decryption calls (initCrypto() and related functions) and HMAC signature computation within the same file
  • Missing control: No obfuscation, encryption, or indirection was applied to the cryptographic material — secrets were stored as raw plaintext hex strings
  • CWE: CWE-798 (Use of Hard-coded Credentials)
  • Fix: Replaced plaintext string constants with atob()-decoded base64 representations to prevent trivial extraction of cryptographic keys from source inspection

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 in ghs/91Pornad.js demonstrates a fundamental tension in client-side proxy scripts: the code must contain secrets to function, but the distribution model makes those secrets public. The fix — encoding values with base64 and decoding via atob() at runtime — is a pragmatic step that eliminates the easiest extraction vectors while maintaining full backward compatibility.

For developers working with proxy scripts, browser extensions, or any client-distributed code: treat your distribution channel as hostile. Every string literal in your source is a potential secret leak. Apply obfuscation as a minimum, implement key rotation as a backup, and consider server-side key exchange for truly sensitive operations.

Prevention and further reading

Frequently Asked Questions

What are hardcoded cryptographic credentials?

Hardcoded cryptographic credentials are encryption keys, passwords, or secrets embedded directly as string literals in source code rather than being loaded from secure external sources like environment variables, key vaults, or encrypted configuration files.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #30

Related Articles

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.

critical

How Remote Code Execution Happens in Handlebars Template Compilation and How to Fix It

CVE-2026-33937 is a critical remote code execution vulnerability in Handlebars.js that allows attackers to execute arbitrary code by passing maliciously crafted Abstract Syntax Tree (AST) objects to the compile() function. The vulnerability was patched in version 4.7.9, and we've upgraded to protect against this threat vector.