WebDefect
VulnerabilitiesSeptember 15, 202610 min read

Secrets and Source Maps in Client-Side JavaScript: Detection and Remediation

JavaScript files served to browsers are readable by anyone. Source maps expose original source code, and hardcoded credentials expose API keys, OAuth secrets, and service tokens. This article explains what the scanner looks for, how to detect these issues, and how to remove them.

W
WebDefect(Security Research)
Published September 15, 2026

Every JavaScript file your web application serves is readable by any visitor with a browser and developer tools. Minification and bundling make code harder to read but do not protect secrets. Source maps make bundled code fully readable. Any credential, API key, or secret embedded in client-side code is effectively public.

Despite this, hardcoded secrets in JavaScript and publicly accessible source maps are consistently found in production deployments. This article explains what these issues look like, what the scanner checks for, and how to remediate them.

JavaScript Served to Browsers Is Public

When a browser loads your application, it downloads every JavaScript file referenced by the HTML. The browser caches these files and they are readable in the Sources panel of developer tools, via direct URL access, and via any HTTP client. Authentication controls on the HTML page do not protect the JavaScript files themselves in most deployment configurations, because JavaScript files are typically served as static assets from a CDN or web server that does not check session state.

This means the security model for client-side JavaScript must assume public readability. Any secret that is in the JavaScript is a secret that is available to any visitor, any automated crawler, and any attacker who fetches the file.

Source Map Exposure

JavaScript source maps are files that map minified, bundled code back to the original source. They are used in development to make stack traces and debugging readable. Build tools (webpack, Vite, esbuild, Rollup) generate them automatically and often place them adjacent to the built JavaScript files as bundle.js.map or similar.

What Source Maps Expose

A source map file contains the original source code before minification. The sourcesContent field in a source map embeds the complete contents of every original source file. When a source map is publicly accessible:

  • The complete pre-minification source code is recoverable, including comments that were stripped during minification.
  • Internal file paths (absolute paths from the build machine) are exposed, revealing developer usernames, project directory structures, and framework configurations.
  • Any secrets that were in the original source files before bundling are exposed, even if they would have been harder to find in the minified output.
  • Application logic, proprietary algorithms, and internal API structures become readable.
# Check if a source map is accessible
curl -sI https://example.com/static/js/main.abc123.js.map

# If 200, view the contents
curl -s https://example.com/static/js/main.abc123.js.map | python3 -m json.tool | head -50

# Extract file list from source map
curl -s https://example.com/static/js/main.abc123.js.map |   python3 -c "import sys,json; d=json.load(sys.stdin); [print(s) for s in d.get('sources',[])]"

Removing Source Maps from Production

The cleanest solution is to not generate source maps for production builds, or to generate them but not deploy them to the public web server.

// webpack — disable source maps in production
module.exports = {
  devtool: process.env.NODE_ENV === 'production' ? false : 'source-map',
};

// Vite — disable source maps in production
export default {
  build: {
    sourcemap: false,  // or 'hidden' to generate but not reference from JS files
  },
};

The hidden source map option generates source map files but does not add the //# sourceMappingURL= comment to the JavaScript file. The maps can be stored privately (for error monitoring services like Sentry) without being accessible via the public URL. This is the recommended approach when source maps are needed for production error tracking.

If source maps are already deployed, add a deny rule to the web server for .map files:

# nginx
location ~* \.map$ {
  deny all;
  return 404;
}

Hardcoded Secrets in JavaScript

Secrets end up in JavaScript files when developers initialize third-party SDKs inline, store configuration directly in component code, or commit environment values directly into source files rather than reading them from build-time environment variables.

Common Exposed Secret Patterns

The secret patterns most commonly found in JavaScript files in production:

  • Private keys: PEM-encoded private keys (beginning with -----BEGIN RSA PRIVATE KEY-----) bundled into client-side code. Critical severity. The private key is fully exposed and must be revoked and reissued.
  • Stripe live secret keys: sk_live_ prefixed keys bundled into frontend code. A Stripe live secret key allows creating charges, accessing customer data, and issuing refunds.
  • GitHub personal access tokens: ghp_ prefixed tokens provide repository access at whatever scope was configured when the token was created.
  • OAuth client secrets: Client secrets assigned patterns like clientSecret: "long-random-value". These allow impersonating the application in OAuth flows.
  • AWS access key IDs and secret access keys: AKIA-prefixed access key IDs are not secret by themselves, but finding them alongside a secret key pattern indicates both are present.
  • Generic API keys: apiKey: "value" or api_key = "value" assignments. Severity depends on what the key controls.

Public Keys vs. Secret Keys

Some credentials are intentionally public. Stripe publishable keys (pk_live_ prefix) are designed to be embedded in client-side code. Google Maps API keys and Google Analytics IDs are also typically public identifiers. The scanner distinguishes between these and genuinely secret values: publishable keys are flagged at low severity for review rather than as confirmed exposures, and known-public patterns are excluded from secret detection entirely.

The distinction matters: if the scanner reports a potential secret, the first question to ask is whether that type of key is intended to be public. A Stripe publishable key in client-side code is expected. A Stripe secret key is not.

How Secrets End Up in Client-Side Code

The most common routes from secret to client-side bundle:

  • Environment variables not correctly scoped: Frameworks like Next.js and Create React App expose environment variables to the browser only when they are prefixed with a specific prefix (NEXT_PUBLIC_ for Next.js, REACT_APP_ for CRA). Variables without the prefix are server-side only. A developer who accidentally uses the wrong prefix or copies a configuration example without checking the prefix will bundle a server-side secret into the client-side build.
  • Inline SDK initialization: Third-party SDKs initialized with hardcoded keys directly in component files rather than reading from environment variables.
  • Configuration imported from a shared module: A shared configuration file that contains both server-side and client-side settings gets imported into a component, pulling server-side secrets into the bundle.
  • Debug code left in production: Temporary logging or debugging code that includes credential values for tracing API calls.

Remediation

If a confirmed secret is found in a deployed JavaScript file, the first action is to revoke and rotate the credential. Reading the file to understand how it was exposed is secondary to removing the active exposure.

To prevent recurrence:

  • Move all secrets to server-side environment variables. Secrets used in API calls should be proxied through a server-side route rather than called directly from client-side code with embedded credentials.
  • Audit framework-specific prefix requirements for client-side environment variable exposure. Verify that no server-side secrets are prefixed for browser exposure.
  • Add a pre-commit hook or CI step that scans for secret patterns before code is merged or deployed. Tools like git-secrets, gitleaks, and Semgrep can catch common patterns.
  • Disable or restrict source maps in production builds as described above.

How WebDefect Scans JavaScript Files

WebDefect crawls the target page and collects all referenced JavaScript file URLs. For each file, the scanner:

  • Checks for an adjacent .map file (js-002): Makes a HEAD request to filename.js.map. If it returns HTTP 200 and the content contains "sources" or sourcesContent, the finding is reported as medium severity.
  • Fetches the JavaScript file content and scans for secret patterns (js-003): Patterns are tiered by confidence. High-confidence patterns (private keys, Stripe live keys, GitHub tokens, OAuth client secrets) produce medium or high severity findings. Low-confidence patterns (generic apiKey= assignments, Stripe publishable keys) are flagged for manual review only.
  • Known public-key patterns are excluded: Stripe publishable keys (pk_live_, pk_test_) and patterns matching known public identifier formats are not reported as secret exposures.

The scanner fetches up to the first 400,000 characters of each JavaScript file. For very large bundles, secrets near the end of the file may not be detected. A clean scanner result for JavaScript does not guarantee the absence of all secrets in all files.

References

Research Topics & Taxonomy

#javascript#source-maps#secrets#api-keys#client-side-security
Automated Vulnerability Detection

Audit your perimeter for these security conditions

WebDefect automatically analyzes your target domain across TLS 1.3, CSP Level 3, security headers, CORS, and DNS with raw evidence and remediation instructions.

Run Free Scan →