no-hardcoded-api-key
Forbid a literal API key in the Gemini client construction
Forbid a literal API key in the Gemini client construction.
- CWE: CWE-798 — Use of Hard-coded Credentials
- OWASP: A07:2021 — Identification and Authentication Failures
- CVSS: 9.1 (Critical) · Recommended:
error
Why
A key written into source is committed, pushed, mirrored into every clone and CI cache, and is billable by anyone who reads it. Because rotating it means a code change, leaked keys tend to stay live far longer than they should.
Gemini is the one SDK of the three where the key is commonly a positional argument: the legacy @google/generative-ai client is constructed as new GoogleGenerativeAI(apiKey), with no options object to inspect, and so are its server-side GoogleAIFileManager and GoogleAICacheManager. The current @google/genai client takes { apiKey } like the others. Both shapes are checked. The positional constructors are recognised by the name the SDK exports, resolved through the import, so import { GoogleGenerativeAI as G } is still checked.
The rule is gated on the SDK being imported, so it stays silent in files that do not construct the client. A spread whose source this file cannot see makes the options unreadable rather than guessed at.
What counts as a hardcoded key
- Only the SDK's own client. The callee must resolve, through its import or
require(), to the SDK — a constructor, a factory the SDK exports,client.withOptions(...), a same-file subclass (super(...)included), or a same-file wrapper that passes its options parameter straight into the client.new Pinecone({ apiKey })in the same file, a test matcher such asexpect.objectContaining({ apiKey }), and the Vercel AI SDK's provider factories (owned byvercel-ai-security) are not this rule's. - Key-shaped values only. A credential is a generated token: at least 20 characters, no whitespace, letters and digits mixed. Stand-ins the SDKs' own tests and docs use —
'fake-api-key','test-key','...','ollama','<YOUR_API_KEY>','sk-xxxxxxxx…'— are not reported. - Followed within the file. A key written as an environment fallback (
process.env.KEY ?? 'sk-…',|| 'sk-…', or a ternary branch) is committed exactly like a bare one and is reported. A key held in aconst, an options object held in aconst, shorthand{ apiKey }, and a same-file spread ({ ...base }) are read. A reassignedlet, a parameter, or an import is not followed.
Incorrect
import { GoogleGenerativeAI } from '@google/generative-ai';
const client = new GoogleGenerativeAI('AIza...');import { GoogleGenAI } from '@google/genai';
const client = new GoogleGenAI({ apiKey: 'AIza...' });Correct
import { GoogleGenerativeAI } from '@google/generative-ai';
const client = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);import { GoogleGenAI } from '@google/genai';
const client = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });Use this rule
npm install -D oxlint-plugin-gemini-security{ "jsPlugins": ["oxlint-plugin-gemini-security"], "rules": { "gemini-security/no-hardcoded-api-key": "error" }}import { defineConfig } from 'vite-plus';export default defineConfig({ lint: { jsPlugins: ['oxlint-plugin-gemini-security'], rules: { 'gemini-security/no-hardcoded-api-key': 'error', }, },});oxlint-plugin-gemini-security re-exports eslint-plugin-gemini-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.