Skip to main content
interlace
Plugin: anthropic-securityRules

no-browser-api-key-exposure

Forbid dangerouslyAllowBrowser, which exposes the Anthropic API key to the client

Forbid dangerouslyAllowBrowser, which exposes the Anthropic API key to the client.

Why

The Anthropic SDK refuses to run in a browser by default. dangerouslyAllowBrowser: true is the opt-in that lifts that refusal, and the SDK's own JSDoc explains what it costs โ€” client-side use "risks exposing your secret API credentials to attackers" (client.d.ts, v0.115.0). A key that reaches the browser is readable by anyone who opens devtools, and it is billable by whoever finds it.

This rule is that warning, enforced.

The rule is gated on the SDK being imported, so it stays silent in files that do not construct the client. An explicit dangerouslyAllowBrowser: false is the safe choice already made and reports nothing. A spread ({ ...base }) or a runtime-decided value ({ dangerouslyAllowBrowser: isBrowser }) is treated as unreadable rather than guessed at.

When the flag is not a leak

The flag is reported when it ships the app's own credential: a literal, an environment read (import.meta.env.VITE_โ€ฆ and NEXT_PUBLIC_โ€ฆ are inlined into the bundle), an imported or build-time-defined constant, or no key at all (the SDK then falls back to the environment variable). It stays silent when the browser obtains the credential per session, because then nothing of the app's ships:

  • a token provider โ€” azureADTokenProvider (the SDK turns the flag on itself when one is passed);
  • a key-provider function (apiKey: async () => โ€ฆ);
  • a key that is a parameter (the visitor's own key, bring-your-own-key) or the result of an await or call โ€” an ephemeral token fetched from your server, which is how OpenAI's Realtime docs tell browser clients to authenticate.

A mixture still ships: userKey || import.meta.env.VITE_KEY reports, because the fallback is the app's key. The flag and the options are followed through same-file consts, and only a client bound to this SDK counts โ€” the same flag on another vendor's client in the same file is that vendor's plugin's finding.

Incorrect

import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({ apiKey: KEY, dangerouslyAllowBrowser: true });

Correct

// Server route โ€” the key never leaves the server.
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

The browser calls your route; your route calls Anthropic and forwards the result.

Use this rule

npm install -D oxlint-plugin-anthropic-security
.oxlintrc.json
{  "jsPlugins": ["oxlint-plugin-anthropic-security"],  "rules": {    "anthropic-security/no-browser-api-key-exposure": "error"  }}
vite.config.ts
import { defineConfig } from 'vite-plus';export default defineConfig({  lint: {    jsPlugins: ['oxlint-plugin-anthropic-security'],    rules: {      'anthropic-security/no-browser-api-key-exposure': 'error',    },  },});

oxlint-plugin-anthropic-security re-exports eslint-plugin-anthropic-security/oxlint: the same rules under the same IDs, and no ESLint install. Why oxlint.

Did this rule catch something? Star the repo to get new CWE coverage as we ship it โ€” or follow the AI-code-security benchmarks behind these rules.