eslint-plugin-anthropic-security 0.1.0 โ†’ 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,11 @@
1
1
  <p align="center">
2
- <a href="https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security" target="blank"><img src="https://eslint.interlace.tools/icon-light.svg" alt="Interlace" height="90" /></a>
2
+ <a href="https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security" target="blank"><img src="https://eslint.interlace.tools/logos/interlace.svg" alt="Interlace" height="90" /></a>
3
3
  &nbsp;&nbsp;
4
- <a href="https://eslint.org" target="_blank"><img src="https://eslint.interlace.tools/eslint-logo.svg" alt="ESLint" height="90" /></a>
4
+ <a href="https://docs.anthropic.com" target="_blank"><img src="https://eslint.interlace.tools/logos/claude.svg" alt="Anthropic Claude SDK" height="90" /></a>
5
+ &nbsp;&nbsp;
6
+ <a href="https://oxc.rs" target="_blank"><img src="https://eslint.interlace.tools/logos/oxlint.svg" alt="oxlint" height="90" /></a>
7
+ &nbsp;&nbsp;
8
+ <a href="https://eslint.org" target="_blank"><img src="https://eslint.interlace.tools/logos/eslint.svg" alt="ESLint" height="90" /></a>
5
9
  </p>
6
10
 
7
11
  <p align="center">
@@ -22,12 +26,31 @@ Security rules for [`@anthropic-ai/sdk`](https://www.npmjs.com/package/@anthropi
22
26
 
23
27
  Every rule gates on the SDK actually being imported, so the plugin stays silent in files that don't use it.
24
28
 
25
- ## Installation
29
+ ## Philosophy
30
+
31
+ **Interlace** fosters **strength through integration**. Instead of stacking isolated rules, we **interlace** security directly into your workflow to create a resilient fabric of code. We believe tools should **guide rather than gatekeep**, providing educational feedback that strengthens the developer with every interaction.
32
+
33
+ ## Getting Started
34
+
35
+ - To check out the [guide](https://eslint.interlace.tools/docs/security/plugin-anthropic-security?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security), visit [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security). ๐Ÿ“š
36
+ - ่ฆๆŸฅ็œ‹ไธญๆ–‡ [ๆŒ‡ๅ—](https://eslint.interlace.tools/docs/security/plugin-anthropic-security?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security), ่ฏท่ฎฟ้—ฎ [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security). ๐Ÿ“š
37
+ - [๊ฐ€์ด๋“œ](https://eslint.interlace.tools/docs/security/plugin-anthropic-security?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security) ๋ฌธ์„œ๋Š” [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security)์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ“š
38
+ - [ใ‚ฌใ‚คใƒ‰](https://eslint.interlace.tools/docs/security/plugin-anthropic-security?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security)ใฏ [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security)ใงใ”็ขบ่ชใใ ใ•ใ„ใ€‚ ๐Ÿ“š
39
+ - Para ver la [guรญa](https://eslint.interlace.tools/docs/security/plugin-anthropic-security?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security), visita [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security). ๐Ÿ“š
40
+ - ู„ู„ุงุทู„ุงุน ุนู„ู‰ [ุงู„ุฏู„ูŠู„](https://eslint.interlace.tools/docs/security/plugin-anthropic-security?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security)ุŒ ู‚ู… ุจุฒูŠุงุฑุฉ [eslint.interlace.tools](https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security). ๐Ÿ“š
26
41
 
27
42
  ```bash
28
- npm install --save-dev eslint-plugin-anthropic-security
43
+ npm install eslint-plugin-anthropic-security --save-dev
29
44
  ```
30
45
 
46
+ ## โš™๏ธ Configuration Presets
47
+
48
+ | Preset | Description |
49
+ | :--- | :--- |
50
+ | `recommended` | Enables every rule at `error`. |
51
+ | `strict` | Same set as `recommended`; reserved for rules that are not yet safe by default. |
52
+ | `minimal` | Same set as `recommended`; reserved for a reduced high-signal subset. |
53
+
31
54
  ## Usage
32
55
 
33
56
  ```js
@@ -47,16 +70,67 @@ Every rule runs on [oxlint](https://oxc.rs) as well as ESLint:
47
70
  { "jsPlugins": ["eslint-plugin-anthropic-security/oxlint"] }
48
71
  ```
49
72
 
73
+ ## ๐Ÿ“ฆ Compatibility
74
+
75
+ | Package | Version |
76
+ | :--- | :--- |
77
+ | `@anthropic-ai/sdk` | [![npm](https://img.shields.io/npm/v/@anthropic-ai/sdk.svg?style=flat-square)](https://www.npmjs.com/package/@anthropic-ai/sdk) |
78
+ | `@anthropic-ai/claude-agent-sdk` | [![npm](https://img.shields.io/npm/v/@anthropic-ai/claude-agent-sdk.svg?style=flat-square)](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) |
79
+ | ESLint | [![npm](https://img.shields.io/npm/v/eslint.svg?style=flat-square)](https://www.npmjs.com/package/eslint) |
80
+ | Node.js | [![node](https://img.shields.io/badge/node-%5E18.0.0-green?style=flat-square)](https://nodejs.org/) |
81
+
82
+ See the [ESLint Version Support Policy](../../docs/ESLINT_VERSION_SUPPORT.md) for the full matrix.
83
+
50
84
  ## Rules
51
85
 
52
- | Rule | Description | CWE | Recommended |
53
- | --- | --- | --- | --- |
54
- | [no-hardcoded-api-key](./docs/rules/no-hardcoded-api-key.md) | Forbid a literal API key in the Anthropic client options | CWE-798 | โœ… error |
86
+ **Legend**
87
+
88
+ | Icon | Description |
89
+ | :---: | :--- |
90
+ | ๐Ÿ’ผ | **Recommended**: Included in the recommended preset. |
91
+ | โš ๏ธ | **Warns**: Set to warn in recommended preset. |
92
+ | ๐Ÿ”ง | **Auto-fixable**: Automatically fixable by the `--fix` CLI option. |
93
+ | ๐Ÿ’ก | **Suggestions**: Providing code suggestions in IDE. |
94
+ | ๐Ÿšซ | **Deprecated**: This rule is deprecated. |
95
+ | ๐ŸŸข | **Type-unaware**: AST-only, runs in oxlint JS-plugin tier. |
96
+ | ๐ŸŸก | **Type-aware (refining)**: pure-AST primary path; types refine precision. |
97
+ | ๐ŸŸ  | **Type-aware (graceful)**: requires TS program; silent without it. |
98
+
99
+ | Rule | CWE | OWASP | CVSS | Description | ๐Ÿง  | ๐Ÿ’ผ | โš ๏ธ | ๐Ÿ”ง | ๐Ÿ’ก | ๐Ÿšซ |
100
+ | :--- | :--- | :--- | :--- | :--- | :---: | :---: | :---: | :---: | :---: | :---: |
101
+ | [no-hardcoded-api-key](./docs/rules/no-hardcoded-api-key.md) | CWE-798 | A07:2021 | 9.1 | Forbid a literal API key in the Anthropic client options | ๐ŸŸข | ๐Ÿ’ผ | | | | |
102
+
103
+ <!-- INTERLACE:STAR_CTA:START -->
104
+ ## โญ Support & follow
105
+
106
+ If this plugin caught a real bug for you, **[star the repo](https://github.com/ofri-peretz/eslint)** โ€” stars are the signal that keeps the Interlace ESLint ecosystem maintained โ€” and **[follow the writeups on Dev.to](https://dev.to/ofri-peretz)** for the benchmarks and security research behind these rules.
107
+
108
+ [![GitHub stars](https://img.shields.io/github/stars/ofri-peretz/eslint?style=social)](https://github.com/ofri-peretz/eslint)
109
+
110
+ <!-- INTERLACE:STAR_CTA:END -->
111
+
112
+ ## ๐Ÿ”— Related ESLint Plugins
113
+
114
+ Part of the **Interlace ESLint Ecosystem** โ€” AI-native security plugins with LLM-optimized error messages:
115
+
116
+ | Plugin | Downloads | Description |
117
+ | :--- | :---: | :--- |
118
+ | [`eslint-plugin-vercel-ai-security`](https://www.npmjs.com/package/eslint-plugin-vercel-ai-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-vercel-ai-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-vercel-ai-security) | Vercel AI SDK security โ€” prompt injection, output handling. |
119
+ | [`eslint-plugin-openai-security`](https://www.npmjs.com/package/eslint-plugin-openai-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-openai-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-openai-security) | OpenAI SDK security. |
120
+ | [`eslint-plugin-anthropic-security`](https://www.npmjs.com/package/eslint-plugin-anthropic-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-anthropic-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-anthropic-security) | Anthropic SDK and Claude Agent SDK security. |
121
+ | [`eslint-plugin-gemini-security`](https://www.npmjs.com/package/eslint-plugin-gemini-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-gemini-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-gemini-security) | Google Gemini SDK security. |
122
+ | [`eslint-plugin-mcp-sdk-security`](https://www.npmjs.com/package/eslint-plugin-mcp-sdk-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-mcp-sdk-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-mcp-sdk-security) | Model Context Protocol SDK security. |
123
+ | [`eslint-plugin-secure-coding`](https://www.npmjs.com/package/eslint-plugin-secure-coding) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-secure-coding.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-secure-coding) | General security rules & OWASP guidelines. |
124
+ | [`eslint-plugin-node-security`](https://www.npmjs.com/package/eslint-plugin-node-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-node-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-node-security) | Node.js core-module security (fs, child_process, vm, crypto, Buffer). |
55
125
 
56
126
  ## ๐Ÿ“„ License
57
127
 
58
128
  MIT ยฉ [Ofri Peretz](https://github.com/ofri-peretz)
59
129
 
130
+ <p align="center">
131
+ <a href="https://eslint.interlace.tools/docs/security/plugin-anthropic-security?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security"><img src="https://eslint.interlace.tools/images/og-anthropic-security.png" alt="ESLint Interlace Plugin" width="100%" /></a>
132
+ </p>
133
+
60
134
  <p align="center">
61
135
  <a href="https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security" target="blank"><img src="https://eslint.interlace.tools/icon-light.svg" alt="Interlace" height="70" /></a>
62
136
  </p>
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "eslint-plugin-anthropic-security",
3
- "version": "0.1.0",
4
- "description": "ESLint plugin for Anthropic SDK security \u2014 catches hardcoded API keys in the Claude client options.",
3
+ "version": "0.2.0",
4
+ "description": "ESLint plugin for Anthropic SDK security โ€” catches hardcoded API keys in the Claude client options.",
5
5
  "type": "commonjs",
6
- "main": "./dist/src/index.js",
7
- "types": "./dist/src/index.d.ts",
6
+ "main": "./src/index.js",
7
+ "types": "./src/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "types": "./dist/src/index.d.ts",
11
- "default": "./dist/src/index.js"
10
+ "types": "./src/index.d.ts",
11
+ "default": "./src/index.js"
12
12
  },
13
13
  "./oxlint": {
14
14
  "types": "./src/oxlint.d.ts",
@@ -20,7 +20,7 @@
20
20
  "homepage": "https://eslint.interlace.tools/docs/security/plugin-anthropic-security?utm_source=npm&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security",
21
21
  "repository": {
22
22
  "type": "git",
23
- "url": "https://github.com/ofri-peretz/eslint",
23
+ "url": "git+https://github.com/ofri-peretz/eslint.git",
24
24
  "directory": "packages/eslint-plugin-anthropic-security"
25
25
  },
26
26
  "bugs": {
@@ -31,10 +31,8 @@
31
31
  },
32
32
  "files": [
33
33
  "src/",
34
- "dist/",
35
34
  "README.md",
36
- "LICENSE",
37
- "CHANGELOG.md"
35
+ "LICENSE"
38
36
  ],
39
37
  "keywords": [
40
38
  "eslint",
@@ -64,7 +62,9 @@
64
62
  "node": ">=18.0.0"
65
63
  },
66
64
  "peerDependencies": {
67
- "eslint": "^8.0.0 || ^9.0.0 || ^10.0.0"
65
+ "eslint": "^8.0.0 || ^9.0.0 || ^10.0.0",
66
+ "@anthropic-ai/sdk": ">=0.1.0 <1.0.0",
67
+ "@anthropic-ai/claude-agent-sdk": ">=0.1.0 <1.0.0"
68
68
  },
69
69
  "peerDependenciesMeta": {
70
70
  "@anthropic-ai/sdk": {
@@ -75,18 +75,7 @@
75
75
  }
76
76
  },
77
77
  "dependencies": {
78
- "tslib": "^2.3.0",
79
- "@interlace/eslint-devkit": "^1.4.4"
80
- },
81
- "devDependencies": {
82
- "@typescript-eslint/parser": "^8.46.2",
83
- "@typescript-eslint/rule-tester": "^8.46.2"
84
- },
85
- "scripts": {
86
- "build": "tsx ../../scripts/build-package.ts",
87
- "test": "vitest run",
88
- "test:coverage": "vitest run --coverage",
89
- "typecheck": "tsc -p tsconfig.lib.json --noEmit"
78
+ "@interlace/eslint-devkit": "^1.9.0"
90
79
  },
91
80
  "funding": {
92
81
  "type": "github",
package/src/index.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ // Minimal declaration written by scripts/build-package.ts.
2
+ //
3
+ // tsc's generated entry declaration inlined every inferred rule-option type
4
+ // (up to 166 kB per plugin) for an API no consumer calls directly. This types
5
+ // what a flat config actually touches. Per-rule option types remain available
6
+ // from the "./types" subpath export where a plugin provides one.
7
+ import type { Linter, Rule } from 'eslint';
8
+
9
+ export declare const rules: Record<string, Rule.RuleModule>;
10
+ export declare const configs: Record<string, Linter.Config>;
11
+ export declare const plugin: {
12
+ meta: { name: string; version: string };
13
+ rules: Record<string, Rule.RuleModule>;
14
+ };
15
+
16
+ declare const _default: typeof plugin;
17
+ export default _default;
@@ -1,22 +1,19 @@
1
1
  "use strict";
2
- /**
3
- * Copyright (c) 2025 Ofri Peretz
4
- * Licensed under the MIT License. Use of this source code is governed by the
5
- * MIT license that can be found in the LICENSE file.
6
- */
7
2
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.configs = exports.plugin = exports.rules = exports.noHardcodedApiKey = void 0;
9
- const no_hardcoded_api_key_1 = require("./rules/no-hardcoded-api-key");
3
+ exports.configs = exports.plugin = exports.rules = exports.noHardcodedApiKey = exports.noBrowserApiKeyExposure = void 0;
4
+ var no_browser_api_key_exposure_2 = require("./rules/no-browser-api-key-exposure");
5
+ Object.defineProperty(exports, "noBrowserApiKeyExposure", { enumerable: true, get: function () { return no_browser_api_key_exposure_2.noBrowserApiKeyExposure; } });
10
6
  var no_hardcoded_api_key_2 = require("./rules/no-hardcoded-api-key");
11
7
  Object.defineProperty(exports, "noHardcodedApiKey", { enumerable: true, get: function () { return no_hardcoded_api_key_2.noHardcodedApiKey; } });
12
8
  exports.rules = {
13
- // CWE-798
14
- 'no-hardcoded-api-key': no_hardcoded_api_key_1.noHardcodedApiKey,
9
+ get 'no-browser-api-key-exposure'() { return require("./rules/no-browser-api-key-exposure").noBrowserApiKeyExposure; },
10
+ get 'no-hardcoded-api-key'() { return require("./rules/no-hardcoded-api-key").noHardcodedApiKey; },
11
+
15
12
  };
16
13
  exports.plugin = {
17
14
  meta: {
18
15
  name: 'eslint-plugin-anthropic-security',
19
- version: '0.1.0',
16
+ version: '0.2.0',
20
17
  },
21
18
  rules: exports.rules,
22
19
  };
@@ -25,6 +22,7 @@ const enabled = {
25
22
  'anthropic-security': exports.plugin,
26
23
  },
27
24
  rules: {
25
+ 'anthropic-security/no-browser-api-key-exposure': 'error',
28
26
  'anthropic-security/no-hardcoded-api-key': 'error',
29
27
  },
30
28
  };
@@ -0,0 +1,17 @@
1
+ // Minimal declaration written by scripts/build-package.ts.
2
+ //
3
+ // tsc's generated entry declaration inlined every inferred rule-option type
4
+ // (up to 166 kB per plugin) for an API no consumer calls directly. This types
5
+ // what a flat config actually touches. Per-rule option types remain available
6
+ // from the "./types" subpath export where a plugin provides one.
7
+ import type { Linter, Rule } from 'eslint';
8
+
9
+ export declare const rules: Record<string, Rule.RuleModule>;
10
+ export declare const configs: Record<string, Linter.Config>;
11
+ export declare const plugin: {
12
+ meta: { name: string; version: string };
13
+ rules: Record<string, Rule.RuleModule>;
14
+ };
15
+
16
+ declare const _default: typeof plugin;
17
+ export default _default;
package/src/oxlint.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ const index_1 = require("./index");
3
+ module.exports = index_1.plugin;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noBrowserApiKeyExposure = void 0;
4
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
+ exports.noBrowserApiKeyExposure = (0, eslint_devkit_1.createBrowserEscapeHatchRule)({
6
+ ruleName: 'no-browser-api-key-exposure',
7
+ vendor: 'Anthropic',
8
+ modules: ['@anthropic-ai'],
9
+ docsUrl: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-anthropic-security/docs/rules/no-browser-api-key-exposure.md',
10
+ documentationLink: 'https://docs.anthropic.com/en/api/getting-started',
11
+ });
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noHardcodedApiKey = void 0;
4
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
+ exports.noHardcodedApiKey = (0, eslint_devkit_1.createSdkApiKeyRule)({
6
+ ruleName: 'no-hardcoded-api-key',
7
+ vendor: 'Anthropic',
8
+ modules: ['@anthropic-ai'],
9
+ keyProps: ['apiKey', 'authToken'],
10
+ fixTemplate: 'new Anthropic({ {{prop}}: process.env.ANTHROPIC_API_KEY })',
11
+ docsUrl: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-anthropic-security/docs/rules/no-hardcoded-api-key.md',
12
+ documentationLink: 'https://docs.anthropic.com/en/api/getting-started',
13
+ });
package/dist/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 Ofri Peretz
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
package/dist/README.md DELETED
@@ -1,66 +0,0 @@
1
- <p align="center">
2
- <a href="https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security" target="blank"><img src="https://eslint.interlace.tools/icon-light.svg" alt="Interlace" height="90" /></a>
3
- &nbsp;&nbsp;
4
- <a href="https://eslint.org" target="_blank"><img src="https://eslint.interlace.tools/eslint-logo.svg" alt="ESLint" height="90" /></a>
5
- &nbsp;&nbsp;
6
- <a href="https://oxc.rs" target="_blank"><img src="https://eslint.interlace.tools/oxlint-logo.svg" alt="oxlint" height="90" /></a>
7
- &nbsp;&nbsp;
8
- <a href="https://www.npmjs.com/package/@anthropic-ai/sdk" target="_blank"><img src="https://eslint.interlace.tools/anthropic-logo.svg" alt="Anthropic" height="90" /></a>
9
- </p>
10
-
11
- <p align="center">
12
- Security rules for the Anthropic SDK and Claude Agent SDK.
13
- </p>
14
-
15
- <p align="center">
16
- <a href="https://www.npmjs.com/package/eslint-plugin-anthropic-security" target="_blank"><img src="https://img.shields.io/npm/v/eslint-plugin-anthropic-security.svg" alt="NPM Version" /></a>
17
- <a href="https://www.npmjs.com/package/eslint-plugin-anthropic-security" target="_blank"><img src="https://img.shields.io/npm/dm/eslint-plugin-anthropic-security.svg" alt="NPM Downloads" /></a>
18
- <a href="https://opensource.org/licenses/MIT" target="_blank"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="Package License" /></a>
19
- </p>
20
-
21
- > โญ If this plugin caught a real bug for you, [**star the repo**](https://github.com/ofri-peretz/eslint) โ€” it's the signal that keeps these rules maintained.
22
-
23
- ## Description
24
-
25
- Security rules for [`@anthropic-ai/sdk`](https://www.npmjs.com/package/@anthropic-ai/sdk).
26
-
27
- Every rule gates on the SDK actually being imported, so the plugin stays silent in files that don't use it.
28
-
29
- ## Installation
30
-
31
- ```bash
32
- npm install --save-dev eslint-plugin-anthropic-security
33
- ```
34
-
35
- ## Usage
36
-
37
- ```js
38
- // eslint.config.js
39
- import anthropicSecurity from 'eslint-plugin-anthropic-security';
40
-
41
- export default [
42
- anthropicSecurity.configs.recommended,
43
- ];
44
- ```
45
-
46
- ### oxlint
47
-
48
- Every rule runs on [oxlint](https://oxc.rs) as well as ESLint:
49
-
50
- ```json
51
- { "jsPlugins": ["eslint-plugin-anthropic-security/oxlint"] }
52
- ```
53
-
54
- ## Rules
55
-
56
- | Rule | Description | CWE | Recommended |
57
- | --- | --- | --- | --- |
58
- | [no-hardcoded-api-key](./docs/rules/no-hardcoded-api-key.md) | Forbid a literal API key in the Anthropic client options | CWE-798 | โœ… error |
59
-
60
- ## ๐Ÿ“„ License
61
-
62
- MIT ยฉ [Ofri Peretz](https://github.com/ofri-peretz)
63
-
64
- <p align="center">
65
- <a href="https://eslint.interlace.tools/?utm_source=github&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security" target="blank"><img src="https://eslint.interlace.tools/icon-light.svg" alt="Interlace" height="70" /></a>
66
- </p>
package/dist/package.json DELETED
@@ -1,95 +0,0 @@
1
- {
2
- "name": "eslint-plugin-anthropic-security",
3
- "version": "0.1.0",
4
- "description": "ESLint plugin for Anthropic SDK security โ€” catches hardcoded API keys in the Claude client options.",
5
- "type": "commonjs",
6
- "main": "./src/index.js",
7
- "types": "./src/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./src/index.d.ts",
11
- "default": "./src/index.js"
12
- },
13
- "./oxlint": {
14
- "types": "./src/oxlint.d.ts",
15
- "default": "./src/oxlint.js"
16
- }
17
- },
18
- "author": "Ofri Peretz <ofriperetzdev@gmail.com>",
19
- "license": "MIT",
20
- "homepage": "https://eslint.interlace.tools/docs/security/plugin-anthropic-security?utm_source=npm&utm_medium=referral&utm_campaign=eslint-plugin-anthropic-security",
21
- "repository": {
22
- "type": "git",
23
- "url": "https://github.com/ofri-peretz/eslint",
24
- "directory": "packages/eslint-plugin-anthropic-security"
25
- },
26
- "bugs": {
27
- "url": "https://github.com/ofri-peretz/eslint/issues"
28
- },
29
- "publishConfig": {
30
- "access": "public"
31
- },
32
- "files": [
33
- "src/",
34
- "dist/",
35
- "README.md",
36
- "LICENSE",
37
- "CHANGELOG.md"
38
- ],
39
- "keywords": [
40
- "eslint",
41
- "eslintplugin",
42
- "eslint-plugin",
43
- "static-analysis",
44
- "linting",
45
- "code-quality",
46
- "security",
47
- "sast",
48
- "appsec",
49
- "vulnerability",
50
- "owasp",
51
- "cwe",
52
- "interlace-security",
53
- "ai-security",
54
- "llm",
55
- "llm-security",
56
- "typescript",
57
- "anthropic",
58
- "claude",
59
- "anthropic-sdk",
60
- "claude-agent-sdk",
61
- "agents"
62
- ],
63
- "engines": {
64
- "node": ">=18.0.0"
65
- },
66
- "peerDependencies": {
67
- "eslint": "^8.0.0 || ^9.0.0 || ^10.0.0"
68
- },
69
- "peerDependenciesMeta": {
70
- "@anthropic-ai/sdk": {
71
- "optional": true
72
- },
73
- "@anthropic-ai/claude-agent-sdk": {
74
- "optional": true
75
- }
76
- },
77
- "dependencies": {
78
- "tslib": "^2.3.0",
79
- "@interlace/eslint-devkit": "^1.4.4"
80
- },
81
- "devDependencies": {
82
- "@typescript-eslint/parser": "^8.46.2",
83
- "@typescript-eslint/rule-tester": "^8.46.2"
84
- },
85
- "scripts": {
86
- "build": "tsx ../../scripts/build-package.ts",
87
- "test": "vitest run",
88
- "test:coverage": "vitest run --coverage",
89
- "typecheck": "tsc -p tsconfig.lib.json --noEmit"
90
- },
91
- "funding": {
92
- "type": "github",
93
- "url": "https://github.com/ofri-peretz/eslint"
94
- }
95
- }
@@ -1,33 +0,0 @@
1
- /**
2
- * Copyright (c) 2025 Ofri Peretz
3
- * Licensed under the MIT License. Use of this source code is governed by the
4
- * MIT license that can be found in the LICENSE file.
5
- */
6
- /**
7
- * eslint-plugin-anthropic-security
8
- *
9
- * Security rules for the Anthropic SDK and Claude Agent SDK.
10
- *
11
- * Scope promise: every rule gates on the SDK being imported, so the plugin
12
- * stays silent in files that do not use it.
13
- */
14
- import type { TSESLint } from '@interlace/eslint-devkit';
15
- export { noHardcodedApiKey } from './rules/no-hardcoded-api-key';
16
- export declare const rules: Record<string, TSESLint.RuleModule<string, readonly unknown[]>>;
17
- export declare const plugin: TSESLint.FlatConfig.Plugin;
18
- export declare const configs: {
19
- minimal: TSESLint.FlatConfig.Config;
20
- recommended: TSESLint.FlatConfig.Config;
21
- strict: TSESLint.FlatConfig.Config;
22
- };
23
- declare const _default: {
24
- configs: {
25
- minimal: TSESLint.FlatConfig.Config;
26
- recommended: TSESLint.FlatConfig.Config;
27
- strict: TSESLint.FlatConfig.Config;
28
- };
29
- meta?: { [K in keyof TSESLint.FlatConfig.PluginMeta]?: TSESLint.FlatConfig.PluginMeta[K] | undefined; };
30
- processors?: Partial<Record<string, TSESLint.FlatConfig.Processor>> | undefined;
31
- rules?: Record<string, TSESLint.LooseRuleDefinition> | undefined;
32
- };
33
- export default _default;
@@ -1,20 +0,0 @@
1
- /**
2
- * Copyright (c) 2025 Ofri Peretz
3
- * Licensed under the MIT License. Use of this source code is governed by the
4
- * MIT license that can be found in the LICENSE file.
5
- */
6
- /**
7
- * oxlint sub-export.
8
- *
9
- * Consumers wire oxlint to this entry via:
10
- * { "jsPlugins": ["eslint-plugin-anthropic-security/oxlint"] }
11
- *
12
- * oxlint's JS plugin loader does `require('eslint-plugin-anthropic-security/oxlint')` and reads
13
- * `.rules` off the result. The compiled output of `export = plugin` is
14
- * `module.exports = plugin`, which gives oxlint the plugin object directly
15
- * (`{ meta, rules }`) โ€” no wrapper, no `.default` indirection.
16
- *
17
- * Generated by scripts/generate-oxlint-shims.mjs. Do not edit by hand.
18
- */
19
- import { plugin } from './index';
20
- export = plugin;
@@ -1,21 +0,0 @@
1
- "use strict";
2
- /**
3
- * Copyright (c) 2025 Ofri Peretz
4
- * Licensed under the MIT License. Use of this source code is governed by the
5
- * MIT license that can be found in the LICENSE file.
6
- */
7
- /**
8
- * oxlint sub-export.
9
- *
10
- * Consumers wire oxlint to this entry via:
11
- * { "jsPlugins": ["eslint-plugin-anthropic-security/oxlint"] }
12
- *
13
- * oxlint's JS plugin loader does `require('eslint-plugin-anthropic-security/oxlint')` and reads
14
- * `.rules` off the result. The compiled output of `export = plugin` is
15
- * `module.exports = plugin`, which gives oxlint the plugin object directly
16
- * (`{ meta, rules }`) โ€” no wrapper, no `.default` indirection.
17
- *
18
- * Generated by scripts/generate-oxlint-shims.mjs. Do not edit by hand.
19
- */
20
- const index_1 = require("./index");
21
- module.exports = index_1.plugin;
@@ -1,8 +0,0 @@
1
- /**
2
- * Copyright (c) 2025 Ofri Peretz
3
- * Licensed under the MIT License. Use of this source code is governed by the
4
- * MIT license that can be found in the LICENSE file.
5
- */
6
- export declare const noHardcodedApiKey: import("@typescript-eslint/utils/ts-eslint").RuleModule<"hardcodedApiKey", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
7
- name: string;
8
- };
@@ -1,113 +0,0 @@
1
- "use strict";
2
- /**
3
- * Copyright (c) 2025 Ofri Peretz
4
- * Licensed under the MIT License. Use of this source code is governed by the
5
- * MIT license that can be found in the LICENSE file.
6
- */
7
- Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.noHardcodedApiKey = void 0;
9
- /**
10
- * @fileoverview Forbid a literal API key in the Anthropic client options
11
- * @description A key written into source is committed, pushed, mirrored into
12
- * every clone and CI cache, and is billable by anyone who reads it. Rotating it
13
- * means a code change, so leaked keys tend to stay live.
14
- * @see https://docs.anthropic.com/en/api/getting-started
15
- */
16
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
17
- /** Covers the base SDK and the agent SDK, which share the client options. */
18
- const ANTHROPIC_MODULE_PREFIX = '@anthropic-ai/';
19
- const KEY_PROPS = new Set(['apiKey', 'authToken']);
20
- /**
21
- * Whether the client options literal carries an inline credential.
22
- *
23
- * `process.env.ANTHROPIC_API_KEY` and any other non-literal expression are
24
- * `safe` โ€” reading a key from the environment is the correct pattern. A spread
25
- * is `unreadable`: the key may be in the spread source, and guessing there
26
- * would flag correct code.
27
- */
28
- function readCredential(options) {
29
- for (const prop of options.properties) {
30
- if (prop.type === 'SpreadElement')
31
- return 'unreadable';
32
- if (prop.computed)
33
- continue;
34
- const isCredential = (prop.key.type === 'Identifier' && KEY_PROPS.has(prop.key.name)) ||
35
- (prop.key.type === 'Literal' && KEY_PROPS.has(String(prop.key.value)));
36
- if (!isCredential)
37
- continue;
38
- if (prop.value.type !== 'Literal')
39
- return 'safe';
40
- // An empty string is a placeholder, not a credential.
41
- if (typeof prop.value.value === 'string' && prop.value.value.length > 0)
42
- return 'literal';
43
- return 'safe';
44
- }
45
- return 'safe';
46
- }
47
- exports.noHardcodedApiKey = (0, eslint_devkit_1.createRule)({
48
- name: 'no-hardcoded-api-key',
49
- meta: {
50
- type: 'problem',
51
- docs: {
52
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-anthropic-security/docs/rules/no-hardcoded-api-key.md',
53
- description: 'Forbid a literal API key in the Anthropic client options',
54
- cwe: 'CWE-798',
55
- cvss: 9.1,
56
- },
57
- messages: {
58
- hardcodedApiKey: (0, eslint_devkit_1.formatLLMMessage)({
59
- icon: eslint_devkit_1.MessageIcons.SECURITY,
60
- issueName: 'Hardcoded Anthropic Credential',
61
- cwe: 'CWE-798',
62
- owasp: 'A07:2021',
63
- cvss: 9.1,
64
- description: '{{prop}} is a string literal, so the credential is committed to source control and readable by anyone with repository access',
65
- severity: 'CRITICAL',
66
- compliance: ['SOC2', 'PCI-DSS'],
67
- fix: 'Read it from the environment: new Anthropic({ {{prop}}: process.env.ANTHROPIC_API_KEY })',
68
- documentationLink: 'https://docs.anthropic.com/en/api/getting-started',
69
- }),
70
- },
71
- schema: [],
72
- },
73
- defaultOptions: [],
74
- create(context) {
75
- let importsAnthropic = false;
76
- const candidates = [];
77
- function inspect(node, args) {
78
- const options = args[0];
79
- if (options?.type !== 'ObjectExpression')
80
- return;
81
- if (readCredential(options) === 'literal') {
82
- candidates.push({ node, prop: 'apiKey' });
83
- }
84
- }
85
- return {
86
- ImportDeclaration(node) {
87
- if (node.source.value.startsWith(ANTHROPIC_MODULE_PREFIX))
88
- importsAnthropic = true;
89
- },
90
- NewExpression(node) {
91
- inspect(node, [...node.arguments]);
92
- },
93
- CallExpression(node) {
94
- if (node.callee.type === 'Identifier' &&
95
- node.callee.name === 'require' &&
96
- node.arguments[0]?.type === 'Literal' &&
97
- typeof node.arguments[0].value === 'string' &&
98
- node.arguments[0].value.startsWith(ANTHROPIC_MODULE_PREFIX)) {
99
- importsAnthropic = true;
100
- return;
101
- }
102
- inspect(node, [...node.arguments]);
103
- },
104
- 'Program:exit'() {
105
- if (!importsAnthropic)
106
- return;
107
- for (const { node, prop } of candidates) {
108
- context.report({ node, messageId: 'hardcodedApiKey', data: { prop } });
109
- }
110
- },
111
- };
112
- },
113
- });
package/src/index.test.ts DELETED
@@ -1,32 +0,0 @@
1
- /**
2
- * @fileoverview Plugin surface lock for eslint-plugin-anthropic-security.
3
- */
4
-
5
- import { describe, it, expect } from 'vitest';
6
-
7
- import plugin, { rules, configs, plugin as namedPlugin, noHardcodedApiKey } from './index';
8
-
9
- describe('eslint-plugin-anthropic-security', () => {
10
- it('exposes every rule under its documented id', () => {
11
- expect(Object.keys(rules).sort()).toEqual(['no-hardcoded-api-key']);
12
- expect(rules['no-hardcoded-api-key']).toBe(noHardcodedApiKey);
13
- });
14
-
15
- it('names itself for the oxlint loader', () => {
16
- expect(namedPlugin.meta?.name).toBe('eslint-plugin-anthropic-security');
17
- expect(namedPlugin.rules).toBe(rules);
18
- });
19
-
20
- it('ships minimal, recommended and strict configs', () => {
21
- expect(Object.keys(configs).sort()).toEqual(['minimal', 'recommended', 'strict']);
22
- for (const config of Object.values(configs)) {
23
- expect(config.plugins).toHaveProperty('anthropic-security');
24
- expect(config.rules?.['anthropic-security/no-hardcoded-api-key']).toBe('error');
25
- }
26
- });
27
-
28
- it('default-exports the plugin with its configs attached', () => {
29
- expect(plugin.rules).toBe(rules);
30
- expect(plugin.configs).toBe(configs);
31
- });
32
- });
package/src/index.ts DELETED
@@ -1,53 +0,0 @@
1
- /**
2
- * Copyright (c) 2025 Ofri Peretz
3
- * Licensed under the MIT License. Use of this source code is governed by the
4
- * MIT license that can be found in the LICENSE file.
5
- */
6
-
7
- /**
8
- * eslint-plugin-anthropic-security
9
- *
10
- * Security rules for the Anthropic SDK and Claude Agent SDK.
11
- *
12
- * Scope promise: every rule gates on the SDK being imported, so the plugin
13
- * stays silent in files that do not use it.
14
- */
15
-
16
- import type { TSESLint } from '@interlace/eslint-devkit';
17
-
18
- import { noHardcodedApiKey } from './rules/no-hardcoded-api-key';
19
-
20
- export { noHardcodedApiKey } from './rules/no-hardcoded-api-key';
21
-
22
- export const rules: Record<string, TSESLint.RuleModule<string, readonly unknown[]>> = {
23
- // CWE-798
24
- 'no-hardcoded-api-key': noHardcodedApiKey,
25
- } satisfies Record<string, TSESLint.RuleModule<string, readonly unknown[]>>;
26
-
27
- export const plugin: TSESLint.FlatConfig.Plugin = {
28
- meta: {
29
- name: 'eslint-plugin-anthropic-security',
30
- version: '0.1.0',
31
- },
32
- rules,
33
- } satisfies TSESLint.FlatConfig.Plugin;
34
-
35
- const enabled: TSESLint.FlatConfig.Config = {
36
- plugins: {
37
- 'anthropic-security': plugin,
38
- },
39
- rules: {
40
- 'anthropic-security/no-hardcoded-api-key': 'error',
41
- },
42
- } satisfies TSESLint.FlatConfig.Config;
43
-
44
- export const configs = {
45
- minimal: enabled,
46
- recommended: enabled,
47
- strict: enabled,
48
- };
49
-
50
- export default {
51
- ...plugin,
52
- configs,
53
- };
package/src/oxlint.ts DELETED
@@ -1,23 +0,0 @@
1
- /**
2
- * Copyright (c) 2025 Ofri Peretz
3
- * Licensed under the MIT License. Use of this source code is governed by the
4
- * MIT license that can be found in the LICENSE file.
5
- */
6
-
7
- /**
8
- * oxlint sub-export.
9
- *
10
- * Consumers wire oxlint to this entry via:
11
- * { "jsPlugins": ["eslint-plugin-anthropic-security/oxlint"] }
12
- *
13
- * oxlint's JS plugin loader does `require('eslint-plugin-anthropic-security/oxlint')` and reads
14
- * `.rules` off the result. The compiled output of `export = plugin` is
15
- * `module.exports = plugin`, which gives oxlint the plugin object directly
16
- * (`{ meta, rules }`) โ€” no wrapper, no `.default` indirection.
17
- *
18
- * Generated by scripts/generate-oxlint-shims.mjs. Do not edit by hand.
19
- */
20
-
21
- import { plugin } from './index';
22
-
23
- export = plugin;
@@ -1,121 +0,0 @@
1
- /**
2
- * Copyright (c) 2025 Ofri Peretz
3
- * Licensed under the MIT License. Use of this source code is governed by the
4
- * MIT license that can be found in the LICENSE file.
5
- */
6
-
7
- /**
8
- * @fileoverview Forbid a literal API key in the Anthropic client options
9
- * @description A key written into source is committed, pushed, mirrored into
10
- * every clone and CI cache, and is billable by anyone who reads it. Rotating it
11
- * means a code change, so leaked keys tend to stay live.
12
- * @see https://docs.anthropic.com/en/api/getting-started
13
- */
14
-
15
- import { TSESTree, createRule, formatLLMMessage, MessageIcons } from '@interlace/eslint-devkit';
16
-
17
- type MessageIds = 'hardcodedApiKey';
18
-
19
- /** Covers the base SDK and the agent SDK, which share the client options. */
20
- const ANTHROPIC_MODULE_PREFIX = '@anthropic-ai/';
21
-
22
- const KEY_PROPS = new Set(['apiKey', 'authToken']);
23
-
24
- type KeyVerdict = 'literal' | 'safe' | 'unreadable';
25
-
26
- /**
27
- * Whether the client options literal carries an inline credential.
28
- *
29
- * `process.env.ANTHROPIC_API_KEY` and any other non-literal expression are
30
- * `safe` โ€” reading a key from the environment is the correct pattern. A spread
31
- * is `unreadable`: the key may be in the spread source, and guessing there
32
- * would flag correct code.
33
- */
34
- function readCredential(options: TSESTree.ObjectExpression): KeyVerdict {
35
- for (const prop of options.properties) {
36
- if (prop.type === 'SpreadElement') return 'unreadable';
37
- if (prop.computed) continue;
38
- const isCredential =
39
- (prop.key.type === 'Identifier' && KEY_PROPS.has(prop.key.name)) ||
40
- (prop.key.type === 'Literal' && KEY_PROPS.has(String(prop.key.value)));
41
- if (!isCredential) continue;
42
- if (prop.value.type !== 'Literal') return 'safe';
43
- // An empty string is a placeholder, not a credential.
44
- if (typeof prop.value.value === 'string' && prop.value.value.length > 0) return 'literal';
45
- return 'safe';
46
- }
47
- return 'safe';
48
- }
49
-
50
- export const noHardcodedApiKey = createRule<[], MessageIds>({
51
- name: 'no-hardcoded-api-key',
52
- meta: {
53
- type: 'problem',
54
- docs: {
55
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-anthropic-security/docs/rules/no-hardcoded-api-key.md',
56
- description: 'Forbid a literal API key in the Anthropic client options',
57
- cwe: 'CWE-798',
58
- cvss: 9.1,
59
- },
60
- messages: {
61
- hardcodedApiKey: formatLLMMessage({
62
- icon: MessageIcons.SECURITY,
63
- issueName: 'Hardcoded Anthropic Credential',
64
- cwe: 'CWE-798',
65
- owasp: 'A07:2021',
66
- cvss: 9.1,
67
- description:
68
- '{{prop}} is a string literal, so the credential is committed to source control and readable by anyone with repository access',
69
- severity: 'CRITICAL',
70
- compliance: ['SOC2', 'PCI-DSS'],
71
- fix: 'Read it from the environment: new Anthropic({ {{prop}}: process.env.ANTHROPIC_API_KEY })',
72
- documentationLink: 'https://docs.anthropic.com/en/api/getting-started',
73
- }),
74
- },
75
- schema: [],
76
- },
77
- defaultOptions: [],
78
- create(context) {
79
- let importsAnthropic = false;
80
- const candidates: Array<{ node: TSESTree.Node; prop: string }> = [];
81
-
82
- function inspect(node: TSESTree.Node, args: TSESTree.CallExpressionArgument[]): void {
83
- const options = args[0];
84
- if (options?.type !== 'ObjectExpression') return;
85
- if (readCredential(options) === 'literal') {
86
- candidates.push({ node, prop: 'apiKey' });
87
- }
88
- }
89
-
90
- return {
91
- ImportDeclaration(node: TSESTree.ImportDeclaration) {
92
- if (node.source.value.startsWith(ANTHROPIC_MODULE_PREFIX)) importsAnthropic = true;
93
- },
94
-
95
- NewExpression(node: TSESTree.NewExpression) {
96
- inspect(node, [...node.arguments]);
97
- },
98
-
99
- CallExpression(node: TSESTree.CallExpression) {
100
- if (
101
- node.callee.type === 'Identifier' &&
102
- node.callee.name === 'require' &&
103
- node.arguments[0]?.type === 'Literal' &&
104
- typeof node.arguments[0].value === 'string' &&
105
- node.arguments[0].value.startsWith(ANTHROPIC_MODULE_PREFIX)
106
- ) {
107
- importsAnthropic = true;
108
- return;
109
- }
110
- inspect(node, [...node.arguments]);
111
- },
112
-
113
- 'Program:exit'() {
114
- if (!importsAnthropic) return;
115
- for (const { node, prop } of candidates) {
116
- context.report({ node, messageId: 'hardcodedApiKey', data: { prop } });
117
- }
118
- },
119
- };
120
- },
121
- });
@@ -1,74 +0,0 @@
1
- /**
2
- * @fileoverview Tests for no-hardcoded-api-key (Anthropic)
3
- */
4
-
5
- import { RuleTester } from '@typescript-eslint/rule-tester';
6
- import { noHardcodedApiKey } from './index';
7
-
8
- const ruleTester = new RuleTester({
9
- languageOptions: { ecmaVersion: 2022, sourceType: 'module' },
10
- });
11
-
12
- const IMPORT = `import Anthropic from '@anthropic-ai/sdk';`;
13
-
14
- ruleTester.run('no-hardcoded-api-key', noHardcodedApiKey, {
15
- valid: [
16
- { code: `${IMPORT}\nconst c = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });` },
17
- // Empty string is a placeholder, not a credential
18
- { code: `${IMPORT}\nconst c = new Anthropic({ apiKey: '' });` },
19
- // Non-string literal
20
- { code: `${IMPORT}\nconst c = new Anthropic({ apiKey: null });` },
21
- // Spread could carry the key
22
- { code: `${IMPORT}\nconst c = new Anthropic({ ...opts });` },
23
- // Computed key
24
- { code: `${IMPORT}\nconst c = new Anthropic({ [k]: 'sk-ant-xxx' });` },
25
- // Unrelated property holding a literal
26
- { code: `${IMPORT}\nconst c = new Anthropic({ baseURL: 'https://api.anthropic.com' });` },
27
- // No options / non-object options
28
- { code: `${IMPORT}\nconst c = new Anthropic();` },
29
- { code: `${IMPORT}\nconst c = new Anthropic(opts);` },
30
- // Scope promise: no Anthropic import, no finding
31
- { code: `const c = new Anthropic({ apiKey: 'sk-ant-hardcoded' });` },
32
- // require of an unrelated module
33
- { code: `const z = require('zod');\nnew Anthropic({ apiKey: 'sk-ant-hardcoded' });` },
34
- // require with a non-string argument
35
- { code: `const m = require(dyn);\nnew Anthropic({ apiKey: 'sk-ant-hardcoded' });` },
36
- // Zero-argument call โ€” the require gate must not choke on it
37
- { code: `${IMPORT}\ninitialise();` },
38
- // Quoted key that is not a credential
39
- { code: `${IMPORT}\nnew Anthropic({ 'baseURL': 'https://api.anthropic.com' });` },
40
- // An import of an unrelated module must not arm the rule
41
- { code: `import { z } from 'zod';\nnew Anthropic({ apiKey: 'sk-ant-x' });` },
42
- ],
43
- invalid: [
44
- {
45
- code: `${IMPORT}\nconst c = new Anthropic({ apiKey: 'sk-ant-api03-hardcoded' });`,
46
- errors: [{ messageId: 'hardcodedApiKey' }],
47
- },
48
- // authToken is the same class of credential
49
- {
50
- code: `${IMPORT}\nconst c = new Anthropic({ authToken: 'tok-live-123' });`,
51
- errors: [{ messageId: 'hardcodedApiKey' }],
52
- },
53
- // Agent SDK shares the client options
54
- {
55
- code: `import { query } from '@anthropic-ai/claude-agent-sdk';\nnew Anthropic({ apiKey: 'sk-ant-x' });`,
56
- errors: [{ messageId: 'hardcodedApiKey' }],
57
- },
58
- // require() arms it
59
- {
60
- code: `const Anthropic = require('@anthropic-ai/sdk');\nnew Anthropic({ apiKey: 'sk-ant-x' });`,
61
- errors: [{ messageId: 'hardcodedApiKey' }],
62
- },
63
- // String-literal key
64
- {
65
- code: `${IMPORT}\nnew Anthropic({ 'apiKey': 'sk-ant-x' });`,
66
- errors: [{ messageId: 'hardcodedApiKey' }],
67
- },
68
- // Factory-style call
69
- {
70
- code: `${IMPORT}\nconst c = createAnthropic({ apiKey: 'sk-ant-x' });`,
71
- errors: [{ messageId: 'hardcodedApiKey' }],
72
- },
73
- ],
74
- });