eslint-plugin-secure-coding 3.1.0 โ†’ 3.1.1

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/AGENTS.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  ```bash
8
8
  # Install dependencies (from monorepo root)
9
- pnpm install
9
+ npm install
10
10
 
11
11
  # Build this package
12
12
  nx build eslint-plugin-secure-coding
package/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 3.0.3 (2026-02-09)
2
+
3
+ This was a version bump only for eslint-plugin-secure-coding to align it with other projects, there were no code changes.
4
+
1
5
  # Changelog
2
6
 
3
7
  All notable changes to this project will be documented in this file.
package/README.md CHANGED
@@ -10,13 +10,14 @@
10
10
  <a href="https://www.npmjs.com/package/eslint-plugin-secure-coding" target="_blank"><img src="https://img.shields.io/npm/v/eslint-plugin-secure-coding.svg" alt="NPM Version" /></a>
11
11
  <a href="https://www.npmjs.com/package/eslint-plugin-secure-coding" target="_blank"><img src="https://img.shields.io/npm/dm/eslint-plugin-secure-coding.svg" alt="NPM Downloads" /></a>
12
12
  <a href="https://opensource.org/licenses/MIT" target="_blank"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="Package License" /></a>
13
- <a href="https://app.codecov.io/gh/ofri-peretz/eslint/components?components%5B0%5D=secure-coding" target="_blank"><img src="https://codecov.io/gh/ofri-peretz/eslint/graph/badge.svg?component=secure-coding" alt="Codecov" /></a>
13
+ <a href="https://app.codecov.io/gh/ofri-peretz/eslint/components?components%5B0%5D=eslint-plugin-secure-coding" target="_blank"><img src="https://codecov.io/gh/ofri-peretz/eslint/graph/badge.svg?component=eslint-plugin-secure-coding" alt="Codecov" /></a>
14
14
  <a href="https://github.com/ofri-peretz/eslint" target="_blank"><img src="https://img.shields.io/badge/Since-Dec_2025-blue?logo=rocket&logoColor=white" alt="Since Dec 2025" /></a>
15
15
  </p>
16
16
 
17
17
  ## Description
18
18
 
19
- This plugin provides a comprehensive set of security rules for JavaScript and TypeScript, ensuring alignment with OWASP compliance standards. It covers a wide range of vulnerabilities, from injection attacks to insecure data handling, offering a solid foundation for secure application development. By using this plugin, you can proactively identify and mitigate security risks across your entire codebase.
19
+ This plugin provides General secure coding practices and OWASP compliance for JavaScript/TypeScript.
20
+ By using this plugin, you can proactively identify and mitigate security risks across your entire codebase.
20
21
 
21
22
  ## Philosophy
22
23
 
@@ -24,12 +25,12 @@ This plugin provides a comprehensive set of security rules for JavaScript and Ty
24
25
 
25
26
  ## Getting Started
26
27
 
27
- - To check out the [guide](https://eslint.interlace.tools/docs/secure-coding), visit [eslint.interlace.tools](https://eslint.interlace.tools). ๐Ÿ“š
28
- - ่ฆๆŸฅ็œ‹ไธญๆ–‡ [ๆŒ‡ๅ—](https://eslint.interlace.tools/docs/secure-coding), ่ฏท่ฎฟ้—ฎ [eslint.interlace.tools](https://eslint.interlace.tools). ๐Ÿ“š
29
- - [๊ฐ€์ด๋“œ](https://eslint.interlace.tools/docs/secure-coding) ๋ฌธ์„œ๋Š” [eslint.interlace.tools](https://eslint.interlace.tools)์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ“š
30
- - [ใ‚ฌใ‚คใƒ‰](https://eslint.interlace.tools/docs/secure-coding)ใฏ [eslint.interlace.tools](https://eslint.interlace.tools)ใงใ”็ขบ่ชใใ ใ•ใ„ใ€‚ ๐Ÿ“š
31
- - Para ver la [guรญa](https://eslint.interlace.tools/docs/secure-coding), visita [eslint.interlace.tools](https://eslint.interlace.tools). ๐Ÿ“š
32
- - ู„ู„ุงุทู„ุงุน ุนู„ู‰ [ุงู„ุฏู„ูŠู„](https://eslint.interlace.tools/docs/secure-coding)ุŒ ู‚ู… ุจุฒูŠุงุฑุฉ [eslint.interlace.tools](https://eslint.interlace.tools). ๐Ÿ“š
28
+ - To check out the [guide](https://eslint.interlace.tools/docs/security/plugin-secure-coding), visit [eslint.interlace.tools](https://eslint.interlace.tools). ๐Ÿ“š
29
+ - ่ฆๆŸฅ็œ‹ไธญๆ–‡ [ๆŒ‡ๅ—](https://eslint.interlace.tools/docs/security/plugin-secure-coding), ่ฏท่ฎฟ้—ฎ [eslint.interlace.tools](https://eslint.interlace.tools). ๐Ÿ“š
30
+ - [๊ฐ€์ด๋“œ](https://eslint.interlace.tools/docs/security/plugin-secure-coding) ๋ฌธ์„œ๋Š” [eslint.interlace.tools](https://eslint.interlace.tools)์—์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ“š
31
+ - [ใ‚ฌใ‚คใƒ‰](https://eslint.interlace.tools/docs/security/plugin-secure-coding)ใฏ [eslint.interlace.tools](https://eslint.interlace.tools)ใงใ”็ขบ่ชใใ ใ•ใ„ใ€‚ ๐Ÿ“š
32
+ - Para ver la [guรญa](https://eslint.interlace.tools/docs/security/plugin-secure-coding), visita [eslint.interlace.tools](https://eslint.interlace.tools). ๐Ÿ“š
33
+ - ู„ู„ุงุทู„ุงุน ุนู„ู‰ [ุงู„ุฏู„ูŠู„](https://eslint.interlace.tools/docs/security/plugin-secure-coding)ุŒ ู‚ู… ุจุฒูŠุงุฑุฉ [eslint.interlace.tools](https://eslint.interlace.tools). ๐Ÿ“š
33
34
 
34
35
  ```bash
35
36
  npm install eslint-plugin-secure-coding --save-dev
@@ -46,122 +47,6 @@ npm install eslint-plugin-secure-coding --save-dev
46
47
 
47
48
  ---
48
49
 
49
- ## ๐Ÿข Enterprise Integration Example
50
-
51
- ```bash
52
- # Install once at the repo root
53
- pnpm add -D eslint-plugin-secure-coding
54
-
55
- # eslint.config.js (org-standard)
56
- import secureCoding from 'eslint-plugin-secure-coding';
57
-
58
- export default [
59
- // Baseline for all services (balanced)
60
- secureCoding.configs.recommended,
61
-
62
- // Add OWASP Top 10 enforcement for internet-facing apps
63
- {
64
- files: ['apps/web/**'],
65
- ...secureCoding.configs['owasp-top-10'],
66
- },
67
-
68
- // Add OWASP Mobile Top 10 for mobile/native apps
69
- {
70
- files: ['apps/mobile/**'],
71
- ...secureCoding.configs['owasp-mobile-top-10'],
72
- },
73
-
74
- // Force strict mode for critical backend services
75
- {
76
- files: ['services/payments/**', 'services/auth/**'],
77
- ...secureCoding.configs.strict,
78
- },
79
- ];
80
- ```
81
-
82
- What this gives organizations:
83
-
84
- - OWASP/CWE/CVSS metadata in every finding for compliance mapping
85
- - Consistent, LLM-ready fixes that teammates and AI can apply safely
86
- - Tiered policies (baseline, OWASP-focused, strict) per surface area
87
-
88
- ---
89
-
90
- ## ๐Ÿงญ Type-safe rule configuration (eslint.config.ts)
91
-
92
- This package ships rule option types to keep flat configs type-safe.
93
-
94
- ```ts
95
- import type { Linter } from 'eslint';
96
- import type { AllSecurityRulesOptions } from 'eslint-plugin-secure-coding/types';
97
- import secureCoding from 'eslint-plugin-secure-coding';
98
-
99
- const secureCodingRuleOptions: AllSecurityRulesOptions = {
100
- 'no-sql-injection': { strategy: 'parameterize' },
101
- 'no-unsafe-deserialization': { allowJSON: false },
102
- };
103
-
104
- export default [
105
- {
106
- ...secureCoding.configs.recommended,
107
- rules: {
108
- ...secureCoding.configs.recommended.rules,
109
- 'secure-coding/no-sql-injection': [
110
- 'error',
111
- secureCodingRuleOptions['no-sql-injection'],
112
- ],
113
- 'secure-coding/no-unsafe-deserialization': [
114
- 'error',
115
- secureCodingRuleOptions['no-unsafe-deserialization'],
116
- ],
117
- },
118
- },
119
- secureCoding.configs['owasp-top-10'],
120
- secureCoding.configs.strict,
121
- ] satisfies Linter.FlatConfig[];
122
- ```
123
-
124
- ---
125
-
126
- ## AI-Optimized Messages
127
-
128
- This plugin is optimized for ESLint's [Model Context Protocol (MCP)](https://eslint.org/docs/latest/use/mcp), enabling AI assistants like **Cursor**, **GitHub Copilot**, and **Claude** to:
129
-
130
- - Understand the exact vulnerability type via CWE references
131
- - Apply the correct fix using structured guidance
132
- - Provide educational context to developers
133
-
134
- ```bash
135
- src/api.ts
136
- 42:15 error ๐Ÿ”’ CWE-89 OWASP:A03-Injection CVSS:9.8 | SQL Injection detected | CRITICAL [SOC2,PCI-DSS,HIPAA]
137
- Fix: Use parameterized query: db.query("SELECT * FROM users WHERE id = ?", [userId]) | https://owasp.org/...
138
- ```
139
-
140
- ```json
141
- // .cursor/mcp.json
142
- {
143
- "mcpServers": {
144
- "eslint": {
145
- "command": "npx",
146
- "args": ["@eslint/mcp@latest"]
147
- }
148
- }
149
- }
150
- ```
151
-
152
- By providing this structured context (CWE, OWASP, Fix), we enable AI tools to **reason** about the security flaw rather than hallucinating. This allows Copilot/Cursor to suggest the _exact_ correct fix immediately.
153
-
154
- ---
155
-
156
- ## ๐Ÿ”’ Privacy
157
-
158
- This plugin runs **100% locally**. No data ever leaves your machine.
159
-
160
- ---
161
-
162
- **Q: Does it work with ESLint 9 flat config?**
163
- A: Yes, fully compatible.
164
-
165
50
  ## Rules
166
51
 
167
52
  **Legend**
@@ -174,86 +59,35 @@ A: Yes, fully compatible.
174
59
  | ๐Ÿ’ก | **Suggestions**: Providing code suggestions in IDE. |
175
60
  | ๐Ÿšซ | **Deprecated**: This rule is deprecated. |
176
61
 
177
- | Rule | CWE | OWASP | CVSS | Description | ๐Ÿ’ผ | โš ๏ธ | ๐Ÿ”ง | ๐Ÿ’ก | ๐Ÿšซ |
178
- | :--------------------------------------------------------------------------------------------------------------------------------------- | :------: | :---: | :--: | :----------------------------------------------------------------------------------------------- | :-: | :-: | :-: | :-: | :-: |
179
- | [no-sql-injection](https://eslint.interlace.tools/docs/secure-coding/rules/no-sql-injection) | CWE-89 | | 9.8 | [no-sql-injection](./docs/rules/no-sql-injection.md) | ๐Ÿ’ผ | | | | |
180
- | [database-injection](https://eslint.interlace.tools/docs/secure-coding/rules/database-injection) | CWE-89 | | 9.8 | [database-injection](./docs/rules/database-injection.md) | ๐Ÿ’ผ | | | | |
181
- | [detect-eval-with-expression](https://eslint.interlace.tools/docs/secure-coding/rules/detect-eval-with-expression) | CWE-95 | | 9.8 | [detect-eval-with-expression](./docs/rules/detect-eval-with-expression.md) | ๐Ÿ’ผ | | | | |
182
- | [detect-child-process](https://eslint.interlace.tools/docs/secure-coding/rules/detect-child-process) | CWE-78 | | 9.8 | [detect-child-process](./docs/rules/detect-child-process.md) | ๐Ÿ’ผ | | | | |
183
- | [no-unsafe-dynamic-require](https://eslint.interlace.tools/docs/secure-coding/rules/no-unsafe-dynamic-require) | CWE-95 | | 7.5 | [no-unsafe-dynamic-require](./docs/rules/no-unsafe-dynamic-require.md) | ๐Ÿ’ผ | | | | |
184
- | [no-graphql-injection](https://eslint.interlace.tools/docs/secure-coding/rules/no-graphql-injection) | CWE-943 | | 8.6 | [no-graphql-injection](./docs/rules/no-graphql-injection.md) | ๐Ÿ’ผ | | | | |
185
- | [no-xxe-injection](https://eslint.interlace.tools/docs/secure-coding/rules/no-xxe-injection) | CWE-611 | | 9.1 | [no-xxe-injection](./docs/rules/no-xxe-injection.md) | ๐Ÿ’ผ | | | | |
186
- | [no-xpath-injection](https://eslint.interlace.tools/docs/secure-coding/rules/no-xpath-injection) | CWE-643 | | 9.8 | [no-xpath-injection](./docs/rules/no-xpath-injection.md) | ๐Ÿ’ผ | | | | |
187
- | [no-ldap-injection](https://eslint.interlace.tools/docs/secure-coding/rules/no-ldap-injection) | CWE-90 | | 9.8 | [no-ldap-injection](./docs/rules/no-ldap-injection.md) | ๐Ÿ’ผ | | | | |
188
- | [no-directive-injection](https://eslint.interlace.tools/docs/secure-coding/rules/no-directive-injection) | CWE-94 | | 8.8 | [no-directive-injection](./docs/rules/no-directive-injection.md) | ๐Ÿ’ผ | | | | |
189
- | [no-format-string-injection](https://eslint.interlace.tools/docs/secure-coding/rules/no-format-string-injection) | CWE-134 | | 9.8 | [no-format-string-injection](./docs/rules/no-format-string-injection.md) | ๐Ÿ’ผ | | | | |
190
- | [no-http-urls](https://eslint.interlace.tools/docs/secure-coding/rules/no-http-urls) | CWE-319 | | 7.5 | [no-http-urls](./docs/rules/no-http-urls.md) | ๐Ÿ’ผ | | | | |
191
- | [no-hardcoded-credentials](https://eslint.interlace.tools/docs/secure-coding/rules/no-hardcoded-credentials) | CWE-798 | | 7.5 | [no-hardcoded-credentials](./docs/rules/no-hardcoded-credentials.md) | ๐Ÿ’ผ | | ๐Ÿ”ง | ๐Ÿ’ก | |
192
- | [no-credentials-in-storage-api](https://eslint.interlace.tools/docs/secure-coding/rules/no-credentials-in-storage-api) | CWE-522 | | 7.5 | [no-credentials-in-storage-api](./docs/rules/no-credentials-in-storage-api.md) | ๐Ÿ’ผ | | | | |
193
- | [no-credentials-in-query-params](https://eslint.interlace.tools/docs/secure-coding/rules/no-credentials-in-query-params) | CWE-598 | | 7.5 | [no-credentials-in-query-params](./docs/rules/no-credentials-in-query-params.md) | ๐Ÿ’ผ | | | | |
194
- | [no-allow-arbitrary-loads](https://eslint.interlace.tools/docs/secure-coding/rules/no-allow-arbitrary-loads) | CWE-295 | | 7.5 | [no-allow-arbitrary-loads](./docs/rules/no-allow-arbitrary-loads.md) | | | | | |
195
- | [no-disabled-certificate-validation](https://eslint.interlace.tools/docs/secure-coding/rules/no-disabled-certificate-validation) | CWE-295 | | 7.5 | [no-disabled-certificate-validation](./docs/rules/no-disabled-certificate-validation.md) | | | | | |
196
- | [require-https-only](https://eslint.interlace.tools/docs/secure-coding/rules/require-https-only) | CWE-319 | | 7.5 | [require-https-only](./docs/rules/require-https-only.md) | ๐Ÿ’ผ | | | | |
197
- | [require-network-timeout](https://eslint.interlace.tools/docs/secure-coding/rules/require-network-timeout) | CWE-400 | | 7.5 | [require-network-timeout](./docs/rules/require-network-timeout.md) | | | | | |
198
- | [detect-weak-password-validation](https://eslint.interlace.tools/docs/secure-coding/rules/detect-weak-password-validation) | CWE-521 | | 7.5 | [detect-weak-password-validation](./docs/rules/detect-weak-password-validation.md) | | | | | |
199
- | [no-client-side-auth-logic](https://eslint.interlace.tools/docs/secure-coding/rules/no-client-side-auth-logic) | CWE-602 | | 7.5 | [no-client-side-auth-logic](./docs/rules/no-client-side-auth-logic.md) | | | | | |
200
- | [no-hardcoded-session-tokens](https://eslint.interlace.tools/docs/secure-coding/rules/no-hardcoded-session-tokens) | CWE-798 | | 9.8 | [no-hardcoded-session-tokens](./docs/rules/no-hardcoded-session-tokens.md) | ๐Ÿ’ผ | | | | |
201
- | [no-unvalidated-deeplinks](https://eslint.interlace.tools/docs/secure-coding/rules/no-unvalidated-deeplinks) | CWE-939 | | 7.5 | [no-unvalidated-deeplinks](./docs/rules/no-unvalidated-deeplinks.md) | ๐Ÿ’ผ | | | | |
202
- | [require-url-validation](https://eslint.interlace.tools/docs/secure-coding/rules/require-url-validation) | CWE-601 | | 7.5 | [require-url-validation](./docs/rules/require-url-validation.md) | | | | | |
203
- | [require-mime-type-validation](https://eslint.interlace.tools/docs/secure-coding/rules/require-mime-type-validation) | CWE-434 | | 7.5 | [require-mime-type-validation](./docs/rules/require-mime-type-validation.md) | | | | | |
204
- | [no-arbitrary-file-access](https://eslint.interlace.tools/docs/secure-coding/rules/no-arbitrary-file-access) | CWE-22 | | 7.5 | [no-arbitrary-file-access](./docs/rules/no-arbitrary-file-access.md) | | | | | |
205
- | [no-pii-in-logs](https://eslint.interlace.tools/docs/secure-coding/rules/no-pii-in-logs) | CWE-532 | | 7.5 | [no-pii-in-logs](./docs/rules/no-pii-in-logs.md) | | โš ๏ธ | | | |
206
- | [no-tracking-without-consent](https://eslint.interlace.tools/docs/secure-coding/rules/no-tracking-without-consent) | CWE-359 | | 7.5 | [no-tracking-without-consent](./docs/rules/no-tracking-without-consent.md) | | | | | |
207
- | [no-sensitive-data-in-analytics](https://eslint.interlace.tools/docs/secure-coding/rules/no-sensitive-data-in-analytics) | CWE-359 | | 7.5 | [no-sensitive-data-in-analytics](./docs/rules/no-sensitive-data-in-analytics.md) | | | | | |
208
- | [require-data-minimization](https://eslint.interlace.tools/docs/secure-coding/rules/require-data-minimization) | CWE-213 | | 7.5 | [require-data-minimization](./docs/rules/require-data-minimization.md) | | | | | |
209
- | [no-debug-code-in-production](https://eslint.interlace.tools/docs/secure-coding/rules/no-debug-code-in-production) | CWE-489 | | 7.5 | [no-debug-code-in-production](./docs/rules/no-debug-code-in-production.md) | | | | | |
210
- | [require-code-minification](https://eslint.interlace.tools/docs/secure-coding/rules/require-code-minification) | CWE-656 | | 7.5 | [require-code-minification](./docs/rules/require-code-minification.md) | | | | | |
211
- | [no-verbose-error-messages](https://eslint.interlace.tools/docs/secure-coding/rules/no-verbose-error-messages) | CWE-209 | | 7.5 | [no-verbose-error-messages](./docs/rules/no-verbose-error-messages.md) | | โš ๏ธ | | | |
212
- | [require-secure-defaults](https://eslint.interlace.tools/docs/secure-coding/rules/require-secure-defaults) | CWE-276 | | 7.5 | [require-secure-defaults](./docs/rules/require-secure-defaults.md) | | | | | |
213
- | [no-sensitive-data-in-cache](https://eslint.interlace.tools/docs/secure-coding/rules/no-sensitive-data-in-cache) | CWE-524 | | 7.5 | [no-sensitive-data-in-cache](./docs/rules/no-sensitive-data-in-cache.md) | | | | | |
214
- | [no-data-in-temp-storage](https://eslint.interlace.tools/docs/secure-coding/rules/no-data-in-temp-storage) | CWE-312 | | 7.5 | [no-data-in-temp-storage](./docs/rules/no-data-in-temp-storage.md) | | | | | |
215
- | [require-secure-deletion](https://eslint.interlace.tools/docs/secure-coding/rules/require-secure-deletion) | CWE-459 | | 7.5 | [require-secure-deletion](./docs/rules/require-secure-deletion.md) | | | | | |
216
- | [require-storage-encryption](https://eslint.interlace.tools/docs/secure-coding/rules/require-storage-encryption) | CWE-311 | | 7.5 | [require-storage-encryption](./docs/rules/require-storage-encryption.md) | | | | | |
217
- | [no-unencrypted-local-storage](https://eslint.interlace.tools/docs/secure-coding/rules/no-unencrypted-local-storage) | CWE-312 | | 7.5 | [no-unencrypted-local-storage](./docs/rules/no-unencrypted-local-storage.md) | | | | | |
218
- | [require-credential-storage](https://eslint.interlace.tools/docs/secure-coding/rules/require-credential-storage) | CWE-522 | | 7.5 | [require-credential-storage](./docs/rules/require-credential-storage.md) | | | | | |
219
- | [no-exposed-debug-endpoints](https://eslint.interlace.tools/docs/secure-coding/rules/no-exposed-debug-endpoints) | CWE-489 | | 7.5 | [no-exposed-debug-endpoints](./docs/rules/no-exposed-debug-endpoints.md) | | | | | |
220
- | [detect-non-literal-fs-filename](https://eslint.interlace.tools/docs/secure-coding/rules/detect-non-literal-fs-filename) | CWE-22 | | 7.5 | [detect-non-literal-fs-filename](./docs/rules/detect-non-literal-fs-filename.md) | ๐Ÿ’ผ | | | | |
221
- | [no-zip-slip](https://eslint.interlace.tools/docs/secure-coding/rules/no-zip-slip) | CWE-22 | | 8.1 | [no-zip-slip](./docs/rules/no-zip-slip.md) | ๐Ÿ’ผ | | | | |
222
- | [no-toctou-vulnerability](https://eslint.interlace.tools/docs/secure-coding/rules/no-toctou-vulnerability) | CWE-367 | | 7.0 | [no-toctou-vulnerability](./docs/rules/no-toctou-vulnerability.md) | ๐Ÿ’ผ | | | ๐Ÿ’ก | |
223
- | [detect-non-literal-regexp](https://eslint.interlace.tools/docs/secure-coding/rules/detect-non-literal-regexp) | CWE-400 | | 7.5 | [detect-non-literal-regexp](./docs/rules/detect-non-literal-regexp.md) | | โš ๏ธ | | | |
224
- | [no-redos-vulnerable-regex](https://eslint.interlace.tools/docs/secure-coding/rules/no-redos-vulnerable-regex) | CWE-1333 | | 7.5 | [no-redos-vulnerable-regex](./docs/rules/no-redos-vulnerable-regex.md) | ๐Ÿ’ผ | | | ๐Ÿ’ก | |
225
- | [no-unsafe-regex-construction](https://eslint.interlace.tools/docs/secure-coding/rules/no-unsafe-regex-construction) | CWE-400 | | 7.5 | [no-unsafe-regex-construction](./docs/rules/no-unsafe-regex-construction.md) | | โš ๏ธ | | ๐Ÿ’ก | |
226
- | [detect-object-injection](https://eslint.interlace.tools/docs/secure-coding/rules/detect-object-injection) | CWE-915 | | 7.3 | [detect-object-injection](./docs/rules/detect-object-injection.md) | | โš ๏ธ | | | |
227
- | [no-unsafe-deserialization](https://eslint.interlace.tools/docs/secure-coding/rules/no-unsafe-deserialization) | CWE-502 | | 9.8 | [no-unsafe-deserialization](./docs/rules/no-unsafe-deserialization.md) | ๐Ÿ’ผ | | | | |
228
- | [no-weak-crypto](https://eslint.interlace.tools/docs/secure-coding/rules/no-weak-crypto) | CWE-327 | | 7.5 | [no-weak-crypto](./docs/rules/no-weak-crypto.md) | ๐Ÿ’ผ | | | | ๐Ÿšซ |
229
- | [no-insufficient-random](https://eslint.interlace.tools/docs/secure-coding/rules/no-insufficient-random) | CWE-330 | | 5.3 | [no-insufficient-random](./docs/rules/no-insufficient-random.md) | | โš ๏ธ | | | ๐Ÿšซ |
230
- | [no-timing-attack](https://eslint.interlace.tools/docs/secure-coding/rules/no-timing-attack) | CWE-208 | | 5.9 | [no-timing-attack](./docs/rules/no-timing-attack.md) | ๐Ÿ’ผ | | | | ๐Ÿšซ |
231
- | [no-insecure-comparison](https://eslint.interlace.tools/docs/secure-coding/rules/no-insecure-comparison) | CWE-697 | | 5.3 | [no-insecure-comparison](./docs/rules/no-insecure-comparison.md) | | โš ๏ธ | ๐Ÿ”ง | | ๐Ÿšซ |
232
- | [no-insecure-jwt](https://eslint.interlace.tools/docs/secure-coding/rules/no-insecure-jwt) | CWE-347 | | 7.5 | [no-insecure-jwt](./docs/rules/no-insecure-jwt.md) | ๐Ÿ’ผ | | | | ๐Ÿšซ |
233
- | [no-unvalidated-user-input](https://eslint.interlace.tools/docs/secure-coding/rules/no-unvalidated-user-input) | CWE-20 | | 8.6 | [no-unvalidated-user-input](./docs/rules/no-unvalidated-user-input.md) | | โš ๏ธ | | | |
234
- | [no-unsanitized-html](https://eslint.interlace.tools/docs/secure-coding/rules/no-unsanitized-html) | CWE-79 | | 6.1 | [no-unsanitized-html](./docs/rules/no-unsanitized-html.md) | ๐Ÿ’ผ | | | | |
235
- | [no-unescaped-url-parameter](https://eslint.interlace.tools/docs/secure-coding/rules/no-unescaped-url-parameter) | CWE-79 | | 6.1 | [no-unescaped-url-parameter](./docs/rules/no-unescaped-url-parameter.md) | | โš ๏ธ | | | |
236
- | [no-improper-sanitization](https://eslint.interlace.tools/docs/secure-coding/rules/no-improper-sanitization) | CWE-116 | | 7.5 | [no-improper-sanitization](./docs/rules/no-improper-sanitization.md) | ๐Ÿ’ผ | | | | |
237
- | [no-improper-type-validation](https://eslint.interlace.tools/docs/secure-coding/rules/no-improper-type-validation) | CWE-20 | | 5.3 | [no-improper-type-validation](./docs/rules/no-improper-type-validation.md) | | โš ๏ธ | | | |
238
- | [no-missing-authentication](https://eslint.interlace.tools/docs/secure-coding/rules/no-missing-authentication) | CWE-306 | | 9.8 | [no-missing-authentication](./docs/rules/no-missing-authentication.md) | | โš ๏ธ | | | |
239
- | [no-privilege-escalation](https://eslint.interlace.tools/docs/secure-coding/rules/no-privilege-escalation) | CWE-269 | | 8.8 | [no-privilege-escalation](./docs/rules/no-privilege-escalation.md) | | โš ๏ธ | | | |
240
- | [no-weak-password-recovery](https://eslint.interlace.tools/docs/secure-coding/rules/no-weak-password-recovery) | CWE-640 | | 9.8 | [no-weak-password-recovery](./docs/rules/no-weak-password-recovery.md) | ๐Ÿ’ผ | | | | |
241
- | [no-insecure-cookie-settings](https://eslint.interlace.tools/docs/secure-coding/rules/no-insecure-cookie-settings) | CWE-614 | | 5.3 | [no-insecure-cookie-settings](./docs/rules/no-insecure-cookie-settings.md) | | โš ๏ธ | | | ๐Ÿšซ |
242
- | [no-missing-csrf-protection](https://eslint.interlace.tools/docs/secure-coding/rules/no-missing-csrf-protection) | CWE-352 | | 8.8 | [no-missing-csrf-protection](./docs/rules/no-missing-csrf-protection.md) | | โš ๏ธ | | | ๐Ÿšซ |
243
- | [no-document-cookie](https://eslint.interlace.tools/docs/secure-coding/rules/no-document-cookie) | CWE-565 | | 4.3 | [no-document-cookie](./docs/rules/no-document-cookie.md) | | โš ๏ธ | | ๐Ÿ’ก | |
244
- | [no-missing-cors-check](https://eslint.interlace.tools/docs/secure-coding/rules/no-missing-cors-check) | CWE-942 | | 7.5 | [no-missing-cors-check](./docs/rules/no-missing-cors-check.md) | | โš ๏ธ | | | ๐Ÿšซ |
245
- | [no-missing-security-headers](https://eslint.interlace.tools/docs/secure-coding/rules/no-missing-security-headers) | CWE-693 | | 5.3 | [no-missing-security-headers](./docs/rules/no-missing-security-headers.md) | | โš ๏ธ | | ๐Ÿ’ก | ๐Ÿšซ |
246
- | [no-insecure-redirects](https://eslint.interlace.tools/docs/secure-coding/rules/no-insecure-redirects) | CWE-601 | | 6.1 | [no-insecure-redirects](./docs/rules/no-insecure-redirects.md) | | โš ๏ธ | | ๐Ÿ’ก | |
247
- | [no-unencrypted-transmission](https://eslint.interlace.tools/docs/secure-coding/rules/no-unencrypted-transmission) | CWE-319 | | 7.5 | [no-unencrypted-transmission](./docs/rules/no-unencrypted-transmission.md) | | โš ๏ธ | | | |
248
- | [no-clickjacking](https://eslint.interlace.tools/docs/secure-coding/rules/no-clickjacking) | CWE-1021 | | 6.1 | [no-clickjacking](./docs/rules/no-clickjacking.md) | ๐Ÿ’ผ | | | | ๐Ÿšซ |
249
- | [no-exposed-sensitive-data](https://eslint.interlace.tools/docs/secure-coding/rules/no-exposed-sensitive-data) | CWE-200 | | 7.5 | [no-exposed-sensitive-data](./docs/rules/no-exposed-sensitive-data.md) | ๐Ÿ’ผ | | | | |
250
- | [no-sensitive-data-exposure](https://eslint.interlace.tools/docs/secure-coding/rules/no-sensitive-data-exposure) | CWE-532 | | 5.5 | [no-sensitive-data-exposure](./docs/rules/no-sensitive-data-exposure.md) | | โš ๏ธ | | ๐Ÿ’ก | |
251
- | [no-buffer-overread](https://eslint.interlace.tools/docs/secure-coding/rules/no-buffer-overread) | CWE-126 | | 7.5 | [no-buffer-overread](./docs/rules/no-buffer-overread.md) | ๐Ÿ’ผ | | | | |
252
- | [no-unlimited-resource-allocation](https://eslint.interlace.tools/docs/secure-coding/rules/no-unlimited-resource-allocation) | CWE-770 | | 7.5 | [no-unlimited-resource-allocation](./docs/rules/no-unlimited-resource-allocation.md) | ๐Ÿ’ผ | | | | |
253
- | [no-unchecked-loop-condition](https://eslint.interlace.tools/docs/secure-coding/rules/no-unchecked-loop-condition) | CWE-835 | | 7.5 | [no-unchecked-loop-condition](./docs/rules/no-unchecked-loop-condition.md) | ๐Ÿ’ผ | | | | |
254
- | [no-electron-security-issues](https://eslint.interlace.tools/docs/secure-coding/rules/no-electron-security-issues) | CWE-693 | | 8.8 | [no-electron-security-issues](./docs/rules/no-electron-security-issues.md) | ๐Ÿ’ผ | | | | |
255
- | [no-insufficient-postmessage-validation](https://eslint.interlace.tools/docs/secure-coding/rules/no-insufficient-postmessage-validation) | CWE-346 | | 8.8 | [no-insufficient-postmessage-validation](./docs/rules/no-insufficient-postmessage-validation.md) | ๐Ÿ’ผ | | | | |
256
- | [Deprecated](https://eslint.interlace.tools/docs/secure-coding/rules/Deprecated) | | | | Deprecated Rules | | | | | |
62
+ | Rule | CWE | OWASP | CVSS | Description | ๐Ÿ’ผ | โš ๏ธ | ๐Ÿ”ง | ๐Ÿ’ก | ๐Ÿšซ |
63
+ | :------------------------------------------------------------------------------------------------------------------------------------------- | :------: | :---: | :--: | :---------------------------------------------------------------------- | :-: | :-: | :-: | :-: | :-: |
64
+ | [detect-non-literal-regexp](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/detect-non-literal-regexp) | CWE-400 | | 7.5 | ESLint security rule documentation for detect-non-literal-regexp | | โš ๏ธ | | | |
65
+ | [detect-object-injection](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/detect-object-injection) | CWE-915 | | 7.3 | ESLint security rule documentation for detect-object-injection | | โš ๏ธ | | | |
66
+ | [detect-weak-password-validation](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/detect-weak-password-validation) | CWE-521 | | 7.5 | ESLint security rule documentation for detect-weak-password-validation | | | | | |
67
+ | [no-directive-injection](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-directive-injection) | CWE-94 | | 8.8 | ESLint security rule documentation for no-directive-injection | ๐Ÿ’ผ | | | | |
68
+ | [no-electron-security-issues](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-electron-security-issues) | CWE-693 | | 8.8 | ESLint security rule documentation for no-electron-security-issues | ๐Ÿ’ผ | | | | |
69
+ | [no-format-string-injection](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-format-string-injection) | CWE-134 | | 9.8 | ESLint security rule documentation for no-format-string-injection | ๐Ÿ’ผ | | | | |
70
+ | [no-graphql-injection](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-graphql-injection) | CWE-943 | | 8.6 | ESLint security rule documentation for no-graphql-injection | ๐Ÿ’ผ | | | | |
71
+ | [no-hardcoded-credentials](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-hardcoded-credentials) | CWE-798 | | 7.5 | ESLint security rule documentation for no-hardcoded-credentials | ๐Ÿ’ผ | | ๐Ÿ”ง | ๐Ÿ’ก | |
72
+ | [no-hardcoded-session-tokens](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-hardcoded-session-tokens) | CWE-798 | | 9.8 | ESLint security rule documentation for no-hardcoded-session-tokens | ๐Ÿ’ผ | | | | |
73
+ | [no-improper-sanitization](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-improper-sanitization) | CWE-116 | | 7.5 | ESLint security rule documentation for no-improper-sanitization | ๐Ÿ’ผ | | | | |
74
+ | [no-improper-type-validation](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-improper-type-validation) | CWE-20 | | 5.3 | ESLint security rule documentation for no-improper-type-validation | | โš ๏ธ | | | |
75
+ | [no-insecure-comparison](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-insecure-comparison) | CWE-697 | | 5.3 | ESLint security rule documentation for no-insecure-comparison | | โš ๏ธ | ๐Ÿ”ง | | ๐Ÿšซ |
76
+ | [no-ldap-injection](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-ldap-injection) | CWE-90 | | 9.8 | ESLint security rule documentation for no-ldap-injection | ๐Ÿ’ผ | | | | |
77
+ | [no-missing-authentication](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-missing-authentication) | CWE-306 | | 9.8 | ESLint security rule documentation for no-missing-authentication | | โš ๏ธ | | | |
78
+ | [no-pii-in-logs](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-pii-in-logs) | CWE-532 | | 7.5 | Enforce no pii in logs | | โš ๏ธ | | | |
79
+ | [no-privilege-escalation](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-privilege-escalation) | CWE-269 | | 8.8 | ESLint security rule documentation for no-privilege-escalation | | โš ๏ธ | | | |
80
+ | [no-redos-vulnerable-regex](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-redos-vulnerable-regex) | CWE-1333 | | 7.5 | ESLint security rule documentation for no-redos-vulnerable-regex | ๐Ÿ’ผ | | | ๐Ÿ’ก | |
81
+ | [no-sensitive-data-exposure](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-sensitive-data-exposure) | CWE-532 | | 5.5 | ESLint security rule documentation for no-sensitive-data-exposure | | โš ๏ธ | | ๐Ÿ’ก | |
82
+ | [no-unchecked-loop-condition](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-unchecked-loop-condition) | CWE-835 | | 7.5 | ESLint security rule documentation for no-unchecked-loop-condition | ๐Ÿ’ผ | | | | |
83
+ | [no-unlimited-resource-allocation](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-unlimited-resource-allocation) | CWE-770 | | 7.5 | ESLint security rule documentation for no-unlimited-resource-allocation | ๐Ÿ’ผ | | | | |
84
+ | [no-unsafe-deserialization](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-unsafe-deserialization) | CWE-502 | | 9.8 | ESLint security rule documentation for no-unsafe-deserialization | ๐Ÿ’ผ | | | | |
85
+ | [no-unsafe-regex-construction](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-unsafe-regex-construction) | CWE-400 | | 7.5 | ESLint security rule documentation for no-unsafe-regex-construction | | โš ๏ธ | | ๐Ÿ’ก | |
86
+ | [no-weak-password-recovery](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-weak-password-recovery) | CWE-640 | | 9.8 | ESLint security rule documentation for no-weak-password-recovery | ๐Ÿ’ผ | | | | |
87
+ | [no-xpath-injection](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-xpath-injection) | CWE-643 | | 9.8 | ESLint security rule documentation for no-xpath-injection | ๐Ÿ’ผ | | | | |
88
+ | [no-xxe-injection](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/no-xxe-injection) | CWE-611 | | 9.1 | ESLint security rule documentation for no-xxe-injection | ๐Ÿ’ผ | | | | |
89
+ | [require-backend-authorization](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/require-backend-authorization) | | | | ESLint security rule documentation for require-backend-authorization | | | | | |
90
+ | [require-secure-defaults](https://eslint.interlace.tools/docs/security/plugin-secure-coding/rules/require-secure-defaults) | CWE-276 | | 7.5 | ESLint security rule documentation for require-secure-defaults | | | | | |
257
91
 
258
92
  ## ๐Ÿ”— Related ESLint Plugins
259
93
 
@@ -269,7 +103,7 @@ Part of the **Interlace ESLint Ecosystem** โ€” AI-native security plugins with L
269
103
  | [`eslint-plugin-express-security`](https://www.npmjs.com/package/eslint-plugin-express-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-express-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-express-security) | Express.js security hardening rules. |
270
104
  | [`eslint-plugin-lambda-security`](https://www.npmjs.com/package/eslint-plugin-lambda-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-lambda-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-lambda-security) | AWS Lambda security best practices. |
271
105
  | [`eslint-plugin-nestjs-security`](https://www.npmjs.com/package/eslint-plugin-nestjs-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-nestjs-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-nestjs-security) | NestJS security rules & patterns. |
272
- | [`eslint-plugin-mongodb-security`](https://www.npmjs.com/package/eslint-plugin-mongodb-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-mongodb-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-mongodb-security) | MongoDB security best practices. |
106
+ | [`eslint-plugin-mongodb-security`](https://www.npmjs.com/package/eslint-plugin-mongodb-security) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-mongodb-security.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-mongodb-security) | MongoDB security best practices. |
273
107
  | [`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 hardening. |
274
108
  | [`eslint-plugin-import-next`](https://www.npmjs.com/package/eslint-plugin-import-next) | [![downloads](https://img.shields.io/npm/dt/eslint-plugin-import-next.svg?style=flat-square)](https://www.npmjs.com/package/eslint-plugin-import-next) | Next-gen import sorting & architecture. |
275
109
 
@@ -278,5 +112,5 @@ Part of the **Interlace ESLint Ecosystem** โ€” AI-native security plugins with L
278
112
  MIT ยฉ [Ofri Peretz](https://github.com/ofri-peretz)
279
113
 
280
114
  <p align="center">
281
- <a href="https://eslint.interlace.tools/docs/secure-coding"><img src="https://eslint.interlace.tools/images/og-secure-coding.png" alt="ESLint Interlace Plugin" width="300" /></a>
115
+ <a href="https://eslint.interlace.tools/docs/security/plugin-secure-coding"><img src="https://eslint.interlace.tools/images/og-secure-coding.png" alt="ESLint Interlace Plugin" width="100%" /></a>
282
116
  </p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-secure-coding",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "Security-focused ESLint plugin with 89 AI-parseable rules for detecting and preventing vulnerabilities. OWASP Top 10 2021 + Mobile Top 10 2024 coverage, CWE references, and AI-assisted fix guidance.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -17,10 +17,10 @@
17
17
  },
18
18
  "author": "Ofri Peretz <ofriperetzdev@gmail.com>",
19
19
  "license": "MIT",
20
- "homepage": "https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-secure-coding/README.md",
20
+ "homepage": "https://github.com/ofri-peretz/eslint/tree/main/packages/eslint-plugin-secure-coding#readme",
21
21
  "repository": {
22
22
  "type": "git",
23
- "url": "git+https://github.com/ofri-peretz/eslint.git",
23
+ "url": "https://github.com/ofri-peretz/eslint",
24
24
  "directory": "packages/eslint-plugin-secure-coding"
25
25
  },
26
26
  "bugs": {
@@ -378,6 +378,15 @@ exports.detectObjectInjection = (0, eslint_devkit_3.createRule)({
378
378
  if (propertyNode.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof propertyNode.value === 'number') {
379
379
  return false;
380
380
  }
381
+ // SAFE: Common numeric index variable names (i, j, k, index, idx, n)
382
+ // These are typically loop counters for array access
383
+ if (propertyNode.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
384
+ const name = propertyNode.name;
385
+ const numericIndexNames = new Set(['i', 'j', 'k', 'index', 'idx', 'n', 'len']);
386
+ if (numericIndexNames.has(name)) {
387
+ return false;
388
+ }
389
+ }
381
390
  // Check if it's a literal string first
382
391
  if (isLiteralString(propertyNode)) {
383
392
  const propName = String(propertyNode.value);
@@ -402,6 +411,56 @@ exports.detectObjectInjection = (0, eslint_devkit_3.createRule)({
402
411
  // DANGEROUS: Any untyped/dynamic property access (e.g., obj[userInput])
403
412
  return true;
404
413
  };
414
+ /**
415
+ * Check if the object is a prototype-less object (Object.create(null))
416
+ * or is derived from an array spread/copy pattern
417
+ */
418
+ const isPrototypelessObject = (objectNode) => {
419
+ if (objectNode.type !== eslint_devkit_1.AST_NODE_TYPES.Identifier) {
420
+ return false;
421
+ }
422
+ const varName = objectNode.name;
423
+ // Walk up to find the variable declaration
424
+ let current = objectNode;
425
+ while (current) {
426
+ if (current.type === eslint_devkit_1.AST_NODE_TYPES.BlockStatement ||
427
+ current.type === eslint_devkit_1.AST_NODE_TYPES.Program) {
428
+ const statements = current.type === eslint_devkit_1.AST_NODE_TYPES.BlockStatement
429
+ ? current.body
430
+ : current.body;
431
+ for (const stmt of statements) {
432
+ if (stmt.type === eslint_devkit_1.AST_NODE_TYPES.VariableDeclaration) {
433
+ for (const decl of stmt.declarations) {
434
+ if (decl.id.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
435
+ decl.id.name === varName &&
436
+ decl.init) {
437
+ // Check for Object.create(null)
438
+ if (decl.init.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
439
+ decl.init.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
440
+ decl.init.callee.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
441
+ decl.init.callee.object.name === 'Object' &&
442
+ decl.init.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
443
+ decl.init.callee.property.name === 'create' &&
444
+ decl.init.arguments.length > 0 &&
445
+ decl.init.arguments[0].type === eslint_devkit_1.AST_NODE_TYPES.Literal &&
446
+ decl.init.arguments[0].value === null) {
447
+ return true;
448
+ }
449
+ // Check for array spread: [...array]
450
+ if (decl.init.type === eslint_devkit_1.AST_NODE_TYPES.ArrayExpression &&
451
+ decl.init.elements.length > 0 &&
452
+ decl.init.elements[0]?.type === eslint_devkit_1.AST_NODE_TYPES.SpreadElement) {
453
+ return true;
454
+ }
455
+ }
456
+ }
457
+ }
458
+ }
459
+ }
460
+ current = current.parent;
461
+ }
462
+ return false;
463
+ };
405
464
  /**
406
465
  * Extract property access information
407
466
  */
@@ -445,6 +504,10 @@ exports.detectObjectInjection = (0, eslint_devkit_3.createRule)({
445
504
  if (!node.left.computed) {
446
505
  return false;
447
506
  }
507
+ // SAFE: Object.create(null) objects have no prototype to pollute
508
+ if (isPrototypelessObject(node.left.object)) {
509
+ return false;
510
+ }
448
511
  const { propertyNode } = extractPropertyAccess(node);
449
512
  // Skip if the key has been validated (e.g., includes() or hasOwnProperty check)
450
513
  if (hasPrecedingValidation(propertyNode, node)) {
@@ -32,6 +32,13 @@ export interface Options extends SecurityRuleOptions {
32
32
  trustedGraphqlLibraries?: string[];
33
33
  /** Functions that validate GraphQL input */
34
34
  validationFunctions?: string[];
35
+ /**
36
+ * Callers where template literals should never be treated as GraphQL.
37
+ * Format: 'object.method' for member calls (e.g. 'console.log'),
38
+ * or 'ClassName' for constructors (e.g. 'URL', 'Error').
39
+ * These are merged with built-in safe callers.
40
+ */
41
+ safeTemplateLiteralCallers?: string[];
35
42
  }
36
43
  type RuleOptions = [Options?];
37
44
  export declare const noGraphqlInjection: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
@@ -136,6 +136,12 @@ exports.noGraphqlInjection = (0, eslint_devkit_1.createRule)({
136
136
  items: { type: 'string' },
137
137
  default: ['validate', 'sanitize', 'isValid', 'assertValid'],
138
138
  },
139
+ safeTemplateLiteralCallers: {
140
+ type: 'array',
141
+ items: { type: 'string' },
142
+ default: [],
143
+ description: 'Additional callers where template literals are never GraphQL. Format: object.method or ClassName.',
144
+ },
139
145
  trustedSanitizers: {
140
146
  type: 'array',
141
147
  items: { type: 'string' },
@@ -164,6 +170,7 @@ exports.noGraphqlInjection = (0, eslint_devkit_1.createRule)({
164
170
  maxQueryDepth: 10,
165
171
  trustedGraphqlLibraries: ['graphql', 'apollo-server', 'graphql-tools', 'graphql-tag'],
166
172
  validationFunctions: ['validate', 'sanitize', 'isValid', 'assertValid'],
173
+ safeTemplateLiteralCallers: [],
167
174
  trustedSanitizers: [],
168
175
  trustedAnnotations: [],
169
176
  strictMode: false,
@@ -171,7 +178,7 @@ exports.noGraphqlInjection = (0, eslint_devkit_1.createRule)({
171
178
  ],
172
179
  create(context) {
173
180
  const options = context.options[0] || {};
174
- const { allowIntrospection = false, maxQueryDepth = 10, trustedGraphqlLibraries = ['graphql', 'apollo-server', 'graphql-tools', 'graphql-tag'], validationFunctions = ['validate', 'sanitize', 'isValid', 'assertValid'], trustedSanitizers = [], trustedAnnotations = [], strictMode = false, } = options;
181
+ const { allowIntrospection = false, maxQueryDepth = 10, trustedGraphqlLibraries = ['graphql', 'apollo-server', 'graphql-tools', 'graphql-tag'], validationFunctions = ['validate', 'sanitize', 'isValid', 'assertValid'], safeTemplateLiteralCallers = [], trustedSanitizers = [], trustedAnnotations = [], strictMode = false, } = options;
175
182
  const sourceCode = context.sourceCode || context.sourceCode;
176
183
  const filename = context.filename || context.getFilename();
177
184
  // Create safety checker for false positive detection
@@ -197,46 +204,280 @@ exports.noGraphqlInjection = (0, eslint_devkit_1.createRule)({
197
204
  }
198
205
  return false;
199
206
  };
207
+ // โ”€โ”€โ”€ AST-based safe caller detection โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
208
+ // Hard-coded safe callers that should NEVER contain GraphQL queries.
209
+ // Users can extend via safeTemplateLiteralCallers option.
210
+ const BUILTIN_SAFE_MEMBER_CALLERS = new Set([
211
+ 'console.log', 'console.warn', 'console.error', 'console.info', 'console.debug',
212
+ 'console.trace', 'logger.log', 'logger.info', 'logger.warn', 'logger.error',
213
+ 'logger.debug',
214
+ ]);
215
+ const BUILTIN_SAFE_CONSTRUCTORS = new Set(['URL', 'Error', 'TypeError', 'RangeError']);
216
+ // Merge user-provided callers
217
+ const safeMemberCallers = new Set(BUILTIN_SAFE_MEMBER_CALLERS);
218
+ const safeConstructors = new Set(BUILTIN_SAFE_CONSTRUCTORS);
219
+ for (const caller of safeTemplateLiteralCallers) {
220
+ if (caller.includes('.')) {
221
+ safeMemberCallers.add(caller);
222
+ }
223
+ else {
224
+ safeConstructors.add(caller);
225
+ }
226
+ }
200
227
  /**
201
- * Check if string contains GraphQL query patterns
228
+ * AST-based check: is this node inside a call that can't be GraphQL?
229
+ * Walks up from the TemplateLiteral to its nearest CallExpression/NewExpression parent.
202
230
  */
203
- const containsGraphqlQuery = (text) => {
204
- const lowerText = text.toLowerCase();
205
- return /\b(query|mutation|subscription|fragment)\b/.test(lowerText) ||
206
- /{\s*\w+/.test(lowerText) || // GraphQL selection sets
207
- /\btype\b|\binterface\b|\benum\b|\bscalar\b/.test(lowerText); // Schema definitions
231
+ const isInSafeCallerContext = (node) => {
232
+ let current = node.parent;
233
+ while (current) {
234
+ // Direct arg to a call: console.log(`...`)
235
+ if (current.type === 'CallExpression') {
236
+ const callee = current.callee;
237
+ // object.method() pattern
238
+ if (callee.type === 'MemberExpression' &&
239
+ callee.object.type === 'Identifier' &&
240
+ callee.property.type === 'Identifier') {
241
+ const key = `${callee.object.name}.${callee.property.name}`;
242
+ if (safeMemberCallers.has(key))
243
+ return true;
244
+ }
245
+ // Direct function call
246
+ if (callee.type === 'Identifier' && safeMemberCallers.has(callee.name)) {
247
+ return true;
248
+ }
249
+ break; // Stop at first enclosing call
250
+ }
251
+ // new URL(`...`), new Error(`...`)
252
+ if (current.type === eslint_devkit_1.AST_NODE_TYPES.NewExpression) {
253
+ if (current.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
254
+ safeConstructors.has(current.callee.name)) {
255
+ return true;
256
+ }
257
+ break;
258
+ }
259
+ // Don't walk past function boundaries
260
+ if (current.type === eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression ||
261
+ current.type === eslint_devkit_1.AST_NODE_TYPES.FunctionExpression ||
262
+ current.type === eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration) {
263
+ break;
264
+ }
265
+ current = current.parent;
266
+ }
267
+ return false;
268
+ };
269
+ // โ”€โ”€โ”€ AST-based GraphQL detection helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
270
+ const isWordChar = (ch) => {
271
+ const code = ch.charCodeAt(0);
272
+ return (code >= 65 && code <= 90) || // A-Z
273
+ (code >= 97 && code <= 122) || // a-z
274
+ (code >= 48 && code <= 57) || // 0-9
275
+ code === 95; // _
208
276
  };
277
+ const isWhitespace = (ch) => ch === ' ' || ch === '\n' || ch === '\t' || ch === '\r';
278
+ /** Operation keywords that must be followed by optional name + { or ( */
279
+ const GRAPHQL_OP_KEYWORDS = ['query', 'mutation', 'subscription'];
280
+ /** Schema keywords that must be followed by a type name */
281
+ const GRAPHQL_SCHEMA_KEYWORDS = ['type', 'interface', 'enum', 'scalar', 'input'];
209
282
  /**
210
- * Check if string contains dangerous introspection
283
+ * Find keyword at word boundary in text. Returns index or -1.
284
+ * Uses simple character checks instead of regex \b.
211
285
  */
212
- const containsIntrospection = (text) => {
213
- return /__schema|__type/i.test(text);
286
+ const findKeywordAtBoundary = (text, keyword, startFrom = 0) => {
287
+ let pos = startFrom;
288
+ while (pos < text.length) {
289
+ const idx = text.indexOf(keyword, pos);
290
+ if (idx === -1)
291
+ return -1;
292
+ const beforeOk = idx === 0 || !isWordChar(text[idx - 1]);
293
+ const afterIdx = idx + keyword.length;
294
+ const afterOk = afterIdx >= text.length || !isWordChar(text[afterIdx]);
295
+ if (beforeOk && afterOk)
296
+ return idx;
297
+ pos = idx + 1;
298
+ }
299
+ return -1;
214
300
  };
215
301
  /**
216
- * Calculate query depth (rough estimate)
302
+ * AST-based: Check if a TemplateLiteral contains GraphQL syntax.
303
+ * Examines quasis (static template parts) directly โ€” no regex, no sourceCode.getText().
217
304
  */
218
- const calculateQueryDepth = (queryText) => {
305
+ const isGraphqlTemplate = (node) => {
306
+ // Build combined static text from quasis for keyword scanning
307
+ const staticText = node.quasis
308
+ .map(q => (q.value.cooked ?? q.value.raw).toLowerCase())
309
+ .join('');
310
+ // 1. Check for GraphQL operation keywords (query, mutation, subscription)
311
+ // Must be followed by optional name then { or (
312
+ for (const keyword of GRAPHQL_OP_KEYWORDS) {
313
+ let pos = 0;
314
+ while (pos < staticText.length) {
315
+ const idx = findKeywordAtBoundary(staticText, keyword, pos);
316
+ if (idx === -1)
317
+ break;
318
+ pos = idx + 1;
319
+ // Scan past keyword, skip whitespace, skip optional name, skip whitespace, expect { or (
320
+ let scan = idx + keyword.length;
321
+ while (scan < staticText.length && isWhitespace(staticText[scan]))
322
+ scan++;
323
+ while (scan < staticText.length && isWordChar(staticText[scan]))
324
+ scan++;
325
+ while (scan < staticText.length && isWhitespace(staticText[scan]))
326
+ scan++;
327
+ if (scan < staticText.length && (staticText[scan] === '{' || staticText[scan] === '(')) {
328
+ return true;
329
+ }
330
+ }
331
+ }
332
+ // Check for fragment keyword: fragment Name on Type
333
+ {
334
+ let pos = 0;
335
+ while (pos < staticText.length) {
336
+ const idx = findKeywordAtBoundary(staticText, 'fragment', pos);
337
+ if (idx === -1)
338
+ break;
339
+ pos = idx + 1;
340
+ let scan = idx + 8; // 'fragment'.length
341
+ while (scan < staticText.length && isWhitespace(staticText[scan]))
342
+ scan++;
343
+ const nameStart = scan;
344
+ while (scan < staticText.length && isWordChar(staticText[scan]))
345
+ scan++;
346
+ if (scan === nameStart)
347
+ continue; // no name
348
+ while (scan < staticText.length && isWhitespace(staticText[scan]))
349
+ scan++;
350
+ if (staticText.slice(scan, scan + 2) === 'on' && (scan + 2 >= staticText.length || !isWordChar(staticText[scan + 2]))) {
351
+ return true;
352
+ }
353
+ }
354
+ }
355
+ // 2. Check for schema definition keywords (type User, interface Foo, etc.)
356
+ for (const keyword of GRAPHQL_SCHEMA_KEYWORDS) {
357
+ let pos = 0;
358
+ while (pos < staticText.length) {
359
+ const idx = findKeywordAtBoundary(staticText, keyword, pos);
360
+ if (idx === -1)
361
+ break;
362
+ pos = idx + 1;
363
+ let scan = idx + keyword.length;
364
+ // Must have whitespace then a word (type name)
365
+ if (scan >= staticText.length || !isWhitespace(staticText[scan]))
366
+ continue;
367
+ while (scan < staticText.length && isWhitespace(staticText[scan]))
368
+ scan++;
369
+ if (scan < staticText.length && isWordChar(staticText[scan])) {
370
+ return true;
371
+ }
372
+ }
373
+ }
374
+ // 3. Check for selection sets (nested braces): { users { name } }
375
+ let braceDepth = 0;
376
+ for (let i = 0; i < staticText.length; i++) {
377
+ if (staticText[i] === '{') {
378
+ braceDepth++;
379
+ if (braceDepth >= 2)
380
+ return true;
381
+ }
382
+ else if (staticText[i] === '}') {
383
+ braceDepth = Math.max(0, braceDepth - 1);
384
+ }
385
+ }
386
+ return false;
387
+ };
388
+ /**
389
+ * Check if a TemplateLiteral contains introspection patterns.
390
+ * AST-based: scans quasis directly.
391
+ */
392
+ const templateHasIntrospection = (node) => {
393
+ return node.quasis.some(q => {
394
+ const text = (q.value.cooked ?? q.value.raw).toLowerCase();
395
+ return text.includes('__schema') || text.includes('__type');
396
+ });
397
+ };
398
+ /**
399
+ * Calculate query depth from template quasis (brace depth scan).
400
+ */
401
+ const templateQueryDepth = (node) => {
219
402
  let depth = 0;
220
403
  let braceCount = 0;
221
- for (const char of queryText) {
222
- if (char === '{') {
223
- braceCount++;
224
- depth = Math.max(depth, braceCount);
225
- }
226
- else if (char === '}') {
227
- braceCount--;
404
+ for (const q of node.quasis) {
405
+ const text = q.value.cooked ?? q.value.raw;
406
+ for (const char of text) {
407
+ if (char === '{') {
408
+ braceCount++;
409
+ depth = Math.max(depth, braceCount);
410
+ }
411
+ else if (char === '}') {
412
+ braceCount--;
413
+ }
228
414
  }
229
415
  }
230
416
  return depth;
231
417
  };
232
418
  /**
233
- * Check if string contains unsafe interpolation
419
+ * Text-based GraphQL detection for string Literals and BinaryExpressions.
420
+ * Uses simple string methods โ€” only regex is for fragment pattern.
234
421
  */
235
- const containsUnsafeInterpolation = (text) => {
236
- // Check for template literal interpolation
237
- return /\$\{[^}]+\}/.test(text) ||
238
- // Check for string concatenation patterns
239
- /\w+\s*\+\s*[^+]+\s*\+\s*\w+/.test(text);
422
+ const containsGraphqlText = (text) => {
423
+ const lower = text.toLowerCase();
424
+ // Check operation keywords
425
+ for (const keyword of GRAPHQL_OP_KEYWORDS) {
426
+ const idx = findKeywordAtBoundary(lower, keyword);
427
+ if (idx === -1)
428
+ continue;
429
+ let scan = idx + keyword.length;
430
+ while (scan < lower.length && isWhitespace(lower[scan]))
431
+ scan++;
432
+ while (scan < lower.length && isWordChar(lower[scan]))
433
+ scan++;
434
+ while (scan < lower.length && isWhitespace(lower[scan]))
435
+ scan++;
436
+ if (scan < lower.length && (lower[scan] === '{' || lower[scan] === '('))
437
+ return true;
438
+ }
439
+ // Check fragment
440
+ const fragIdx = findKeywordAtBoundary(lower, 'fragment');
441
+ if (fragIdx !== -1) {
442
+ let scan = fragIdx + 8;
443
+ while (scan < lower.length && isWhitespace(lower[scan]))
444
+ scan++;
445
+ const nameStart = scan;
446
+ while (scan < lower.length && isWordChar(lower[scan]))
447
+ scan++;
448
+ if (scan > nameStart) {
449
+ while (scan < lower.length && isWhitespace(lower[scan]))
450
+ scan++;
451
+ if (lower.slice(scan, scan + 2) === 'on' && (scan + 2 >= lower.length || !isWordChar(lower[scan + 2])))
452
+ return true;
453
+ }
454
+ }
455
+ // Check schema keywords
456
+ for (const keyword of GRAPHQL_SCHEMA_KEYWORDS) {
457
+ const idx = findKeywordAtBoundary(lower, keyword);
458
+ if (idx === -1)
459
+ continue;
460
+ let scan = idx + keyword.length;
461
+ if (scan >= lower.length || !isWhitespace(lower[scan]))
462
+ continue;
463
+ while (scan < lower.length && isWhitespace(lower[scan]))
464
+ scan++;
465
+ if (scan < lower.length && isWordChar(lower[scan]))
466
+ return true;
467
+ }
468
+ // Check nested braces
469
+ let braceDepth = 0;
470
+ for (let i = 0; i < text.length; i++) {
471
+ if (text[i] === '{') {
472
+ braceDepth++;
473
+ if (braceDepth >= 2)
474
+ return true;
475
+ }
476
+ else if (text[i] === '}') {
477
+ braceDepth = Math.max(0, braceDepth - 1);
478
+ }
479
+ }
480
+ return false;
240
481
  };
241
482
  /**
242
483
  * Check if input is validated before use
@@ -258,12 +499,16 @@ exports.noGraphqlInjection = (0, eslint_devkit_1.createRule)({
258
499
  return {
259
500
  // Check template literals for GraphQL queries
260
501
  TemplateLiteral(node) {
261
- const queryText = sourceCode.getText(node);
262
- if (!containsGraphqlQuery(queryText)) {
502
+ // AST-based context check: skip templates inside safe callers
503
+ if (isInSafeCallerContext(node)) {
263
504
  return;
264
505
  }
265
- // Check for introspection queries
266
- if (!allowIntrospection && containsIntrospection(queryText)) {
506
+ // AST-based GraphQL detection: scan quasis, no regex
507
+ if (!isGraphqlTemplate(node)) {
508
+ return;
509
+ }
510
+ // Check for introspection queries (AST-based)
511
+ if (!allowIntrospection && templateHasIntrospection(node)) {
267
512
  // FALSE POSITIVE REDUCTION: Skip if annotated as safe
268
513
  if (safetyChecker.isSafe(node, context)) {
269
514
  return;
@@ -278,8 +523,8 @@ exports.noGraphqlInjection = (0, eslint_devkit_1.createRule)({
278
523
  });
279
524
  return;
280
525
  }
281
- // Check for unsafe interpolation
282
- if (containsUnsafeInterpolation(queryText)) {
526
+ // Check for unsafe interpolation (AST-based: just check expressions array)
527
+ if (node.expressions.length > 0) {
283
528
  // FALSE POSITIVE REDUCTION: Skip if all expressions are validated
284
529
  const allExpressionsSafe = node.expressions.every((expr) => isInputValidated(expr) || safetyChecker.isSafe(expr, context));
285
530
  if (!allExpressionsSafe) {
@@ -299,8 +544,8 @@ exports.noGraphqlInjection = (0, eslint_devkit_1.createRule)({
299
544
  });
300
545
  }
301
546
  }
302
- // Check query depth for DoS protection
303
- const depth = calculateQueryDepth(queryText);
547
+ // Check query depth for DoS protection (AST-based)
548
+ const depth = templateQueryDepth(node);
304
549
  if (depth > maxQueryDepth) {
305
550
  context.report({
306
551
  node,
@@ -324,11 +569,12 @@ exports.noGraphqlInjection = (0, eslint_devkit_1.createRule)({
324
569
  return;
325
570
  }
326
571
  const queryText = node.value;
327
- if (!containsGraphqlQuery(queryText)) {
572
+ if (!containsGraphqlText(queryText)) {
328
573
  return;
329
574
  }
330
575
  // Check for introspection queries
331
- if (!allowIntrospection && containsIntrospection(queryText)) {
576
+ const lowerQuery = queryText.toLowerCase();
577
+ if (!allowIntrospection && (lowerQuery.includes('__schema') || lowerQuery.includes('__type'))) {
332
578
  /* c8 ignore start -- safetyChecker requires JSDoc annotations not testable via RuleTester */
333
579
  if (safetyChecker.isSafe(node, context)) {
334
580
  return;
@@ -343,8 +589,18 @@ exports.noGraphqlInjection = (0, eslint_devkit_1.createRule)({
343
589
  },
344
590
  });
345
591
  }
346
- // Check query depth
347
- const depth = calculateQueryDepth(queryText);
592
+ // Check query depth via brace scanner
593
+ let depth = 0;
594
+ let bc = 0;
595
+ for (const ch of queryText) {
596
+ if (ch === '{') {
597
+ bc++;
598
+ depth = Math.max(depth, bc);
599
+ }
600
+ else if (ch === '}') {
601
+ bc--;
602
+ }
603
+ }
348
604
  if (depth > maxQueryDepth) {
349
605
  context.report({
350
606
  node,
@@ -362,7 +618,7 @@ exports.noGraphqlInjection = (0, eslint_devkit_1.createRule)({
362
618
  return;
363
619
  }
364
620
  const fullText = sourceCode.getText(node);
365
- if (!containsGraphqlQuery(fullText)) {
621
+ if (!containsGraphqlText(fullText)) {
366
622
  return;
367
623
  }
368
624
  // String concatenation in GraphQL queries is dangerous
@@ -135,6 +135,15 @@ exports.noInsecureComparison = (0, eslint_devkit_2.createRule)({
135
135
  // Timing-safe comparison for secrets even with strict equality
136
136
  if ((node.operator === '===' || node.operator === '!==') &&
137
137
  (isPotentialSecret(node.left) || isPotentialSecret(node.right))) {
138
+ // SKIP: Length comparisons are safe - they're actually required before timingSafeEqual
139
+ const isLengthComparison = (expr) => {
140
+ return expr.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
141
+ expr.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
142
+ expr.property.name === 'length';
143
+ };
144
+ if (isLengthComparison(node.left) || isLengthComparison(node.right)) {
145
+ return; // Length checks are safe and recommended
146
+ }
138
147
  const leftText = sourceCode.getText(node.left);
139
148
  const rightText = sourceCode.getText(node.right);
140
149
  // ... rest of logic uses example ...
@@ -321,7 +321,6 @@ exports.noMissingAuthentication = (0, eslint_devkit_2.createRule)({
321
321
  suggest: [
322
322
  {
323
323
  messageId: 'addAuthentication',
324
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
325
324
  fix: (_fixer) => null,
326
325
  },
327
326
  ],
@@ -9,11 +9,23 @@ exports.noSensitiveDataExposure = void 0;
9
9
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
10
  const eslint_devkit_2 = require("@interlace/eslint-devkit");
11
11
  /**
12
- * Check if string contains sensitive data patterns
12
+ * Check if string contains sensitive data patterns.
13
+ * Handles camelCase (secretKey), snake_case (secret_key), and plain text.
13
14
  */
14
15
  function containsSensitiveData(text, patterns) {
15
- const lowerText = text.toLowerCase();
16
- return patterns.some(pattern => lowerText.includes(pattern.toLowerCase()));
16
+ // Normalize camelCase โ†’ space separated for matching (secretKey โ†’ secret key)
17
+ const normalized = text
18
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
19
+ .toLowerCase();
20
+ for (const pattern of patterns) {
21
+ const escaped = pattern.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
22
+ // Allow spaces or underscores as word separators (e.g. 'credit card' matches 'credit_card')
23
+ const flexPattern = escaped.replace(/[_ ]/g, '[_ ]');
24
+ if (new RegExp(`\\b${flexPattern}\\b`, 'i').test(normalized)) {
25
+ return pattern;
26
+ }
27
+ }
28
+ return null;
17
29
  }
18
30
  exports.noSensitiveDataExposure = (0, eslint_devkit_2.createRule)({
19
31
  name: 'no-sensitive-data-exposure',
@@ -65,7 +77,7 @@ exports.noSensitiveDataExposure = (0, eslint_devkit_2.createRule)({
65
77
  sensitivePatterns: {
66
78
  type: 'array',
67
79
  items: { type: 'string' },
68
- default: ['password', 'secret', 'token', 'key', 'ssn', 'credit', 'card', 'api_key', 'apikey'],
80
+ default: ['password', 'passwd', 'secret', 'token', 'access_token', 'auth_token', 'ssn', 'credit_card', 'creditcard', 'api_key', 'apikey', 'secret_key', 'private_key', 'encryption_key'],
69
81
  description: 'Sensitive data patterns',
70
82
  },
71
83
  checkConsoleLog: {
@@ -90,14 +102,14 @@ exports.noSensitiveDataExposure = (0, eslint_devkit_2.createRule)({
90
102
  },
91
103
  defaultOptions: [
92
104
  {
93
- sensitivePatterns: ['password', 'secret', 'token', 'key', 'ssn', 'credit', 'card', 'api_key', 'apikey'],
105
+ sensitivePatterns: ['password', 'passwd', 'secret', 'token', 'access_token', 'auth_token', 'ssn', 'credit_card', 'creditcard', 'api_key', 'apikey', 'secret_key', 'private_key', 'encryption_key'],
94
106
  checkConsoleLog: true,
95
107
  checkErrorMessages: true,
96
108
  checkApiResponses: true,
97
109
  },
98
110
  ],
99
111
  create(context, [options = {}]) {
100
- const { sensitivePatterns = ['password', 'secret', 'token', 'key', 'ssn', 'credit', 'card', 'api_key', 'apikey'], checkConsoleLog = true, checkErrorMessages = true, } = options || {};
112
+ const { sensitivePatterns = ['password', 'passwd', 'secret', 'token', 'access_token', 'auth_token', 'ssn', 'credit_card', 'creditcard', 'api_key', 'apikey', 'secret_key', 'private_key', 'encryption_key'], checkConsoleLog = true, checkErrorMessages = true, } = options || {};
101
113
  /**
102
114
  * Check CallExpression for logging calls with sensitive data
103
115
  */
@@ -134,13 +146,14 @@ exports.noSensitiveDataExposure = (0, eslint_devkit_2.createRule)({
134
146
  for (const arg of node.arguments) {
135
147
  if (arg.type === 'Literal' && typeof arg.value === 'string') {
136
148
  const text = arg.value;
137
- if (containsSensitiveData(text, sensitivePatterns)) {
149
+ const matchedPattern = containsSensitiveData(text, sensitivePatterns);
150
+ if (matchedPattern) {
138
151
  context.report({
139
152
  node: arg,
140
153
  messageId: 'sensitiveDataExposure',
141
154
  data: {
142
155
  context: 'logs',
143
- dataType: 'password',
156
+ dataType: matchedPattern,
144
157
  },
145
158
  suggest: [
146
159
  { messageId: 'redactData', fix: () => null },
@@ -152,14 +165,14 @@ exports.noSensitiveDataExposure = (0, eslint_devkit_2.createRule)({
152
165
  }
153
166
  }
154
167
  else if (arg.type === 'Identifier' && arg.name) {
155
- const name = arg.name.toLowerCase();
156
- if (containsSensitiveData(name, sensitivePatterns)) {
168
+ const matchedPattern2 = containsSensitiveData(arg.name, sensitivePatterns);
169
+ if (matchedPattern2) {
157
170
  context.report({
158
171
  node: arg,
159
172
  messageId: 'sensitiveDataExposure',
160
173
  data: {
161
174
  context: 'logs',
162
- dataType: 'password',
175
+ dataType: matchedPattern2,
163
176
  },
164
177
  suggest: [
165
178
  { messageId: 'redactData', fix: () => null },
@@ -185,13 +198,14 @@ exports.noSensitiveDataExposure = (0, eslint_devkit_2.createRule)({
185
198
  for (const arg of node.arguments) {
186
199
  if (arg.type === 'Literal' && typeof arg.value === 'string') {
187
200
  const text = arg.value;
188
- if (containsSensitiveData(text, sensitivePatterns)) {
201
+ const matchedErrPattern = containsSensitiveData(text, sensitivePatterns);
202
+ if (matchedErrPattern) {
189
203
  context.report({
190
204
  node: arg,
191
205
  messageId: 'sensitiveDataExposure',
192
206
  data: {
193
207
  context: 'error messages',
194
- dataType: 'password',
208
+ dataType: matchedErrPattern,
195
209
  },
196
210
  suggest: [
197
211
  { messageId: 'redactData', fix: () => null },
@@ -206,13 +220,14 @@ exports.noSensitiveDataExposure = (0, eslint_devkit_2.createRule)({
206
220
  // Check left side if it's a literal
207
221
  if (arg.left && arg.left.type === 'Literal' && typeof arg.left.value === 'string') {
208
222
  const leftText = arg.left.value;
209
- if (containsSensitiveData(leftText, sensitivePatterns)) {
223
+ const leftMatchedPattern = containsSensitiveData(leftText, sensitivePatterns);
224
+ if (leftMatchedPattern) {
210
225
  context.report({
211
226
  node: arg.left,
212
227
  messageId: 'sensitiveDataExposure',
213
228
  data: {
214
229
  context: 'error messages',
215
- dataType: 'password',
230
+ dataType: leftMatchedPattern,
216
231
  },
217
232
  suggest: [
218
233
  { messageId: 'redactData', fix: () => null },
@@ -225,14 +240,14 @@ exports.noSensitiveDataExposure = (0, eslint_devkit_2.createRule)({
225
240
  }
226
241
  // Check right side if it's an identifier
227
242
  if (arg.right && arg.right.type === 'Identifier' && arg.right.name) {
228
- const rightName = arg.right.name.toLowerCase();
229
- if (containsSensitiveData(rightName, sensitivePatterns)) {
243
+ const rightMatchedPattern = containsSensitiveData(arg.right.name, sensitivePatterns);
244
+ if (rightMatchedPattern) {
230
245
  context.report({
231
246
  node: arg.right,
232
247
  messageId: 'sensitiveDataExposure',
233
248
  data: {
234
249
  context: 'error messages',
235
- dataType: 'password',
250
+ dataType: rightMatchedPattern,
236
251
  },
237
252
  suggest: [
238
253
  { messageId: 'redactData', fix: () => null },
@@ -355,8 +355,32 @@ exports.noXpathInjection = (0, eslint_devkit_1.createRule)({
355
355
  // Check template literals for XPath expressions
356
356
  TemplateLiteral(node) {
357
357
  const fullText = sourceCode.getText(node);
358
- // Check if this looks like an XPath expression
359
- if (!fullText.includes('/') && !fullText.includes('[') && !fullText.includes('@')) {
358
+ // Skip common non-XPath patterns
359
+ // URLs and API endpoints
360
+ if (/https?:\/\//.test(fullText) || /^[`'"]\s*\/api\//.test(fullText)) {
361
+ return;
362
+ }
363
+ // File paths (start with / or contain common path patterns)
364
+ if (/^[`'"]\s*\/home\//.test(fullText) || /^[`'"]\s*\/usr\//.test(fullText) || /^[`'"]\s*\/tmp\//.test(fullText)) {
365
+ return;
366
+ }
367
+ // CSS selectors
368
+ if (/\[data-[\w-]+/.test(fullText) || /\[class=/.test(fullText) || /\[id=/.test(fullText)) {
369
+ return;
370
+ }
371
+ // Search/query strings
372
+ if (/\?.*=/.test(fullText) && !/\[@/.test(fullText)) {
373
+ return;
374
+ }
375
+ // Check if this looks like an ACTUAL XPath expression
376
+ // Must have XPath-specific syntax, not just forward slashes
377
+ const hasXpathSyntax = /\/\/\w+/.test(fullText) || // //element
378
+ /\[@\w+/.test(fullText) || // [@attr
379
+ /\[contains\(/.test(fullText) || // [contains(
380
+ /\[text\(\)/.test(fullText) || // [text()
381
+ /\/child::/.test(fullText) || // /child::
382
+ /\/descendant::/.test(fullText); // /descendant::
383
+ if (!hasXpathSyntax) {
360
384
  return;
361
385
  }
362
386
  // Check for interpolation in XPath-like expressions