eslint-plugin-anthropic-security 0.1.1 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-anthropic-security",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "ESLint plugin for Anthropic SDK security — catches hardcoded API keys in the Claude client options.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -75,7 +75,7 @@
75
75
  }
76
76
  },
77
77
  "dependencies": {
78
- "@interlace/eslint-devkit": "^1.8.0"
78
+ "@interlace/eslint-devkit": "^1.9.0"
79
79
  },
80
80
  "funding": {
81
81
  "type": "github",
package/src/index.js CHANGED
@@ -1,16 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.configs = exports.plugin = exports.rules = exports.noHardcodedApiKey = void 0;
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; } });
4
6
  var no_hardcoded_api_key_2 = require("./rules/no-hardcoded-api-key");
5
7
  Object.defineProperty(exports, "noHardcodedApiKey", { enumerable: true, get: function () { return no_hardcoded_api_key_2.noHardcodedApiKey; } });
6
8
  exports.rules = {
9
+ get 'no-browser-api-key-exposure'() { return require("./rules/no-browser-api-key-exposure").noBrowserApiKeyExposure; },
7
10
  get 'no-hardcoded-api-key'() { return require("./rules/no-hardcoded-api-key").noHardcodedApiKey; },
8
11
 
9
12
  };
10
13
  exports.plugin = {
11
14
  meta: {
12
15
  name: 'eslint-plugin-anthropic-security',
13
- version: '0.1.1',
16
+ version: '0.2.0',
14
17
  },
15
18
  rules: exports.rules,
16
19
  };
@@ -19,6 +22,7 @@ const enabled = {
19
22
  'anthropic-security': exports.plugin,
20
23
  },
21
24
  rules: {
25
+ 'anthropic-security/no-browser-api-key-exposure': 'error',
22
26
  'anthropic-security/no-hardcoded-api-key': 'error',
23
27
  },
24
28
  };
@@ -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
+ });
@@ -2,91 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.noHardcodedApiKey = void 0;
4
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
- const ANTHROPIC_MODULE_PREFIX = '@anthropic-ai/';
6
- const KEY_PROPS = new Set(['apiKey', 'authToken']);
7
- function readCredential(options) {
8
- for (const prop of options.properties) {
9
- if (prop.type === 'SpreadElement')
10
- return { kind: 'unreadable' };
11
- if (prop.computed)
12
- continue;
13
- const name = prop.key.type === 'Identifier' ? prop.key.name : String(prop.key.value);
14
- if (!KEY_PROPS.has(name))
15
- continue;
16
- if (prop.value.type !== 'Literal')
17
- return { kind: 'safe' };
18
- if (typeof prop.value.value === 'string' && prop.value.value.length > 0) {
19
- return { kind: 'literal', prop: name };
20
- }
21
- return { kind: 'safe' };
22
- }
23
- return { kind: 'safe' };
24
- }
25
- exports.noHardcodedApiKey = (0, eslint_devkit_1.createRule)({
26
- name: 'no-hardcoded-api-key',
27
- meta: {
28
- type: 'problem',
29
- docs: {
30
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-anthropic-security/docs/rules/no-hardcoded-api-key.md',
31
- description: 'Forbid a literal API key in the Anthropic client options',
32
- cwe: 'CWE-798',
33
- cvss: 9.1,
34
- },
35
- messages: {
36
- hardcodedApiKey: (0, eslint_devkit_1.formatLLMMessage)({
37
- icon: eslint_devkit_1.MessageIcons.SECURITY,
38
- issueName: 'Hardcoded Anthropic Credential',
39
- cwe: 'CWE-798',
40
- owasp: 'A07:2021',
41
- cvss: 9.1,
42
- description: '{{prop}} is a string literal, so the credential is committed to source control and readable by anyone with repository access',
43
- severity: 'CRITICAL',
44
- compliance: ['SOC2', 'PCI-DSS'],
45
- fix: 'Read it from the environment: new Anthropic({ {{prop}}: process.env.ANTHROPIC_API_KEY })',
46
- documentationLink: 'https://docs.anthropic.com/en/api/getting-started',
47
- }),
48
- },
49
- schema: [],
50
- },
51
- defaultOptions: [],
52
- create(context) {
53
- let importsAnthropic = false;
54
- const candidates = [];
55
- function inspect(node, args) {
56
- const options = args[0];
57
- if (options?.type !== 'ObjectExpression')
58
- return;
59
- const verdict = readCredential(options);
60
- if (verdict.kind === 'literal') {
61
- candidates.push({ node, prop: verdict.prop });
62
- }
63
- }
64
- return {
65
- ImportDeclaration(node) {
66
- if (node.source.value.startsWith(ANTHROPIC_MODULE_PREFIX))
67
- importsAnthropic = true;
68
- },
69
- NewExpression(node) {
70
- inspect(node, [...node.arguments]);
71
- },
72
- CallExpression(node) {
73
- if (node.callee.type === 'Identifier' &&
74
- node.callee.name === 'require' &&
75
- node.arguments[0]?.type === 'Literal' &&
76
- typeof node.arguments[0].value === 'string' &&
77
- node.arguments[0].value.startsWith(ANTHROPIC_MODULE_PREFIX)) {
78
- importsAnthropic = true;
79
- return;
80
- }
81
- inspect(node, [...node.arguments]);
82
- },
83
- 'Program:exit'() {
84
- if (!importsAnthropic)
85
- return;
86
- for (const { node, prop } of candidates) {
87
- context.report({ node, messageId: 'hardcodedApiKey', data: { prop } });
88
- }
89
- },
90
- };
91
- },
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',
92
13
  });