eslint-plugin-node-security 4.0.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/CHANGELOG.md +83 -0
- package/README.md +50 -0
- package/package.json +79 -0
- package/src/index.d.ts +10 -0
- package/src/index.js +118 -0
- package/src/index.js.map +1 -0
- package/src/rules/detect-child-process/index.d.ts +30 -0
- package/src/rules/detect-child-process/index.js +535 -0
- package/src/rules/detect-child-process/index.js.map +1 -0
- package/src/rules/detect-eval-with-expression/index.d.ts +28 -0
- package/src/rules/detect-eval-with-expression/index.js +398 -0
- package/src/rules/detect-eval-with-expression/index.js.map +1 -0
- package/src/rules/detect-non-literal-fs-filename/index.d.ts +26 -0
- package/src/rules/detect-non-literal-fs-filename/index.js +460 -0
- package/src/rules/detect-non-literal-fs-filename/index.js.map +1 -0
- package/src/rules/detect-suspicious-dependencies/index.d.ts +12 -0
- package/src/rules/detect-suspicious-dependencies/index.js +77 -0
- package/src/rules/detect-suspicious-dependencies/index.js.map +1 -0
- package/src/rules/lock-file/index.d.ts +13 -0
- package/src/rules/lock-file/index.js +94 -0
- package/src/rules/lock-file/index.js.map +1 -0
- package/src/rules/no-arbitrary-file-access/index.d.ts +12 -0
- package/src/rules/no-arbitrary-file-access/index.js +201 -0
- package/src/rules/no-arbitrary-file-access/index.js.map +1 -0
- package/src/rules/no-buffer-overread/index.d.ts +39 -0
- package/src/rules/no-buffer-overread/index.js +612 -0
- package/src/rules/no-buffer-overread/index.js.map +1 -0
- package/src/rules/no-cryptojs/index.d.ts +24 -0
- package/src/rules/no-cryptojs/index.js +104 -0
- package/src/rules/no-cryptojs/index.js.map +1 -0
- package/src/rules/no-cryptojs-weak-random/index.d.ts +24 -0
- package/src/rules/no-cryptojs-weak-random/index.js +112 -0
- package/src/rules/no-cryptojs-weak-random/index.js.map +1 -0
- package/src/rules/no-data-in-temp-storage/index.d.ts +14 -0
- package/src/rules/no-data-in-temp-storage/index.js +99 -0
- package/src/rules/no-data-in-temp-storage/index.js.map +1 -0
- package/src/rules/no-deprecated-cipher-method/index.d.ts +23 -0
- package/src/rules/no-deprecated-cipher-method/index.js +118 -0
- package/src/rules/no-deprecated-cipher-method/index.js.map +1 -0
- package/src/rules/no-dynamic-dependency-loading/index.d.ts +12 -0
- package/src/rules/no-dynamic-dependency-loading/index.js +55 -0
- package/src/rules/no-dynamic-dependency-loading/index.js.map +1 -0
- package/src/rules/no-dynamic-require/index.d.ts +21 -0
- package/src/rules/no-dynamic-require/index.js +122 -0
- package/src/rules/no-dynamic-require/index.js.map +1 -0
- package/src/rules/no-ecb-mode/index.d.ts +23 -0
- package/src/rules/no-ecb-mode/index.js +113 -0
- package/src/rules/no-ecb-mode/index.js.map +1 -0
- package/src/rules/no-insecure-key-derivation/index.d.ts +24 -0
- package/src/rules/no-insecure-key-derivation/index.js +116 -0
- package/src/rules/no-insecure-key-derivation/index.js.map +1 -0
- package/src/rules/no-insecure-rsa-padding/index.d.ts +24 -0
- package/src/rules/no-insecure-rsa-padding/index.js +110 -0
- package/src/rules/no-insecure-rsa-padding/index.js.map +1 -0
- package/src/rules/no-pii-in-logs/index.d.ts +12 -0
- package/src/rules/no-pii-in-logs/index.js +74 -0
- package/src/rules/no-pii-in-logs/index.js.map +1 -0
- package/src/rules/no-self-signed-certs/index.d.ts +23 -0
- package/src/rules/no-self-signed-certs/index.js +116 -0
- package/src/rules/no-self-signed-certs/index.js.map +1 -0
- package/src/rules/no-sha1-hash/index.d.ts +24 -0
- package/src/rules/no-sha1-hash/index.js +128 -0
- package/src/rules/no-sha1-hash/index.js.map +1 -0
- package/src/rules/no-static-iv/index.d.ts +23 -0
- package/src/rules/no-static-iv/index.js +147 -0
- package/src/rules/no-static-iv/index.js.map +1 -0
- package/src/rules/no-timing-unsafe-compare/index.d.ts +23 -0
- package/src/rules/no-timing-unsafe-compare/index.js +114 -0
- package/src/rules/no-timing-unsafe-compare/index.js.map +1 -0
- package/src/rules/no-toctou-vulnerability/index.d.ts +26 -0
- package/src/rules/no-toctou-vulnerability/index.js +214 -0
- package/src/rules/no-toctou-vulnerability/index.js.map +1 -0
- package/src/rules/no-unsafe-dynamic-require/index.d.ts +19 -0
- package/src/rules/no-unsafe-dynamic-require/index.js +112 -0
- package/src/rules/no-unsafe-dynamic-require/index.js.map +1 -0
- package/src/rules/no-weak-cipher-algorithm/index.d.ts +25 -0
- package/src/rules/no-weak-cipher-algorithm/index.js +190 -0
- package/src/rules/no-weak-cipher-algorithm/index.js.map +1 -0
- package/src/rules/no-weak-hash-algorithm/index.d.ts +25 -0
- package/src/rules/no-weak-hash-algorithm/index.js +218 -0
- package/src/rules/no-weak-hash-algorithm/index.js.map +1 -0
- package/src/rules/no-zip-slip/index.d.ts +35 -0
- package/src/rules/no-zip-slip/index.js +451 -0
- package/src/rules/no-zip-slip/index.js.map +1 -0
- package/src/rules/prefer-native-crypto/index.d.ts +23 -0
- package/src/rules/prefer-native-crypto/index.js +124 -0
- package/src/rules/prefer-native-crypto/index.js.map +1 -0
- package/src/rules/require-dependency-integrity/index.d.ts +12 -0
- package/src/rules/require-dependency-integrity/index.js +70 -0
- package/src/rules/require-dependency-integrity/index.js.map +1 -0
- package/src/rules/require-secure-credential-storage/index.d.ts +12 -0
- package/src/rules/require-secure-credential-storage/index.js +54 -0
- package/src/rules/require-secure-credential-storage/index.js.map +1 -0
- package/src/rules/require-secure-deletion/index.d.ts +12 -0
- package/src/rules/require-secure-deletion/index.js +46 -0
- package/src/rules/require-secure-deletion/index.js.map +1 -0
- package/src/rules/require-storage-encryption/index.d.ts +12 -0
- package/src/rules/require-storage-encryption/index.js +54 -0
- package/src/rules/require-storage-encryption/index.js.map +1 -0
- package/src/types/index.d.ts +24 -0
- package/src/types/index.js +8 -0
- package/src/types/index.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-node-security/src/rules/no-pii-in-logs/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH;;;;GAIG;AAEH,4DAAsG;AAUzF,QAAA,WAAW,GAAG,IAAA,0BAAU,EAA0B;IAC7D,IAAI,EAAE,gBAAgB;IACtB,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EAAE,wDAAwD;SACtE;QACD,QAAQ,EAAE;YACR,iBAAiB,EAAE,IAAA,gCAAgB,EAAC;gBAClC,IAAI,EAAE,4BAAY,CAAC,QAAQ;gBAC3B,SAAS,EAAE,oBAAoB;gBAC/B,GAAG,EAAE,SAAS;gBACd,WAAW,EAAE,2FAA2F;gBACxG,QAAQ,EAAE,MAAM;gBAChB,GAAG,EAAE,mCAAmC;gBACxC,iBAAiB,EAAE,iDAAiD;aACrE,CAAC;SACH;QACD,MAAM,EAAE,EAAE;KACX;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,CAAC,OAAO;QACZ,SAAS,MAAM,CAAC,IAAmB;YACjC,OAAO,CAAC,MAAM,CAAC;gBACb,IAAI;gBACJ,SAAS,EAAE,mBAAmB;aAC/B,CAAC,CAAC;QACL,CAAC;QAED,OAAO;YACL,cAAc,CAAC,IAA6B;gBAC1C,qCAAqC;gBACrC,IACE,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,8BAAc,CAAC,gBAAgB;oBACpD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,8BAAc,CAAC,UAAU;oBACrD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS;oBACrC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,8BAAc,CAAC,UAAU;oBACvD,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EACpE,CAAC;oBACD,kDAAkD;oBAClD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;wBACjC,IACE,GAAG,CAAC,IAAI,KAAK,8BAAc,CAAC,gBAAgB;4BAC5C,GAAG,CAAC,QAAQ,CAAC,IAAI,KAAK,8BAAc,CAAC,UAAU,EAC/C,CAAC;4BACD,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;4BACjD,MAAM,QAAQ,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;4BAErE,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gCAC7C,MAAM,CAAC,IAAI,CAAC,CAAC;4BACf,CAAC;wBACH,CAAC;wBAED,uCAAuC;wBACvC,IAAI,GAAG,CAAC,IAAI,KAAK,8BAAc,CAAC,OAAO,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;4BACzE,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;4BACrC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gCACnF,MAAM,CAAC,IAAI,CAAC,CAAC;4BACf,CAAC;wBACH,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
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 Rule: no-self-signed-certs
|
|
8
|
+
* Detects rejectUnauthorized: false in TLS options
|
|
9
|
+
* CWE-295: Improper Certificate Validation
|
|
10
|
+
*
|
|
11
|
+
* @see https://cwe.mitre.org/data/definitions/295.html
|
|
12
|
+
*/
|
|
13
|
+
import type { TSESLint } from '@interlace/eslint-devkit';
|
|
14
|
+
type MessageIds = 'insecureTls' | 'enableValidation';
|
|
15
|
+
export interface Options {
|
|
16
|
+
/** Allow in test/development files. Default: false */
|
|
17
|
+
allowInTests?: boolean;
|
|
18
|
+
}
|
|
19
|
+
type RuleOptions = [Options?];
|
|
20
|
+
export declare const noSelfSignedCerts: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
|
|
21
|
+
name: string;
|
|
22
|
+
};
|
|
23
|
+
export type { Options as NoSelfSignedCertsOptions };
|
|
@@ -0,0 +1,116 @@
|
|
|
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.noSelfSignedCerts = void 0;
|
|
9
|
+
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
|
+
exports.noSelfSignedCerts = (0, eslint_devkit_1.createRule)({
|
|
11
|
+
name: 'no-self-signed-certs',
|
|
12
|
+
meta: {
|
|
13
|
+
type: 'problem',
|
|
14
|
+
docs: {
|
|
15
|
+
description: 'Disallow rejectUnauthorized: false in TLS options',
|
|
16
|
+
},
|
|
17
|
+
hasSuggestions: true,
|
|
18
|
+
messages: {
|
|
19
|
+
insecureTls: (0, eslint_devkit_1.formatLLMMessage)({
|
|
20
|
+
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
21
|
+
issueName: 'TLS certificate validation disabled',
|
|
22
|
+
cwe: 'CWE-295',
|
|
23
|
+
description: 'rejectUnauthorized: false disables TLS certificate validation, enabling man-in-the-middle attacks. Any certificate will be accepted, including self-signed and expired ones.',
|
|
24
|
+
severity: 'CRITICAL',
|
|
25
|
+
fix: 'Remove rejectUnauthorized: false or set it to true',
|
|
26
|
+
documentationLink: 'https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html',
|
|
27
|
+
}),
|
|
28
|
+
enableValidation: (0, eslint_devkit_1.formatLLMMessage)({
|
|
29
|
+
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
30
|
+
issueName: 'Enable certificate validation',
|
|
31
|
+
description: 'Enable proper TLS certificate validation',
|
|
32
|
+
severity: 'LOW',
|
|
33
|
+
fix: 'rejectUnauthorized: true (or remove the property)',
|
|
34
|
+
documentationLink: 'https://nodejs.org/api/tls.html#tlsconnectoptions-callback',
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
schema: [
|
|
38
|
+
{
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
allowInTests: {
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
default: false,
|
|
44
|
+
description: 'Allow in test files',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
additionalProperties: false,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
defaultOptions: [
|
|
52
|
+
{
|
|
53
|
+
allowInTests: false,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
create(context, [options = {}]) {
|
|
57
|
+
const { allowInTests = false } = options;
|
|
58
|
+
const filename = context.filename;
|
|
59
|
+
const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
|
|
60
|
+
function checkProperty(node) {
|
|
61
|
+
if (isTestFile)
|
|
62
|
+
return;
|
|
63
|
+
// Check for rejectUnauthorized: false
|
|
64
|
+
if (node.key.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
65
|
+
node.key.name === 'rejectUnauthorized' &&
|
|
66
|
+
node.value.type === eslint_devkit_1.AST_NODE_TYPES.Literal &&
|
|
67
|
+
node.value.value === false) {
|
|
68
|
+
context.report({
|
|
69
|
+
node,
|
|
70
|
+
messageId: 'insecureTls',
|
|
71
|
+
suggest: [
|
|
72
|
+
{
|
|
73
|
+
messageId: 'enableValidation',
|
|
74
|
+
fix: (fixer) => {
|
|
75
|
+
return fixer.replaceText(node.value, 'true');
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function checkAssignmentExpression(node) {
|
|
83
|
+
if (isTestFile)
|
|
84
|
+
return;
|
|
85
|
+
// Check for process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
|
86
|
+
if (node.left.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
|
|
87
|
+
node.left.object.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
|
|
88
|
+
node.left.object.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
89
|
+
node.left.object.object.name === 'process' &&
|
|
90
|
+
node.left.object.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
91
|
+
node.left.object.property.name === 'env' &&
|
|
92
|
+
node.left.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
93
|
+
node.left.property.name === 'NODE_TLS_REJECT_UNAUTHORIZED') {
|
|
94
|
+
if (node.right.type === eslint_devkit_1.AST_NODE_TYPES.Literal &&
|
|
95
|
+
(node.right.value === '0' || node.right.value === 0)) {
|
|
96
|
+
/* c8 ignore next 10 -- process.env.NODE_TLS_REJECT_UNAUTHORIZED pattern requires complex AST */
|
|
97
|
+
context.report({
|
|
98
|
+
node,
|
|
99
|
+
messageId: 'insecureTls',
|
|
100
|
+
suggest: [
|
|
101
|
+
{
|
|
102
|
+
messageId: 'enableValidation',
|
|
103
|
+
fix: () => null, // Should remove the assignment
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
Property: checkProperty,
|
|
112
|
+
AssignmentExpression: checkAssignmentExpression,
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-node-security/src/rules/no-self-signed-certs/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
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 Rule: no-sha1-hash
|
|
8
|
+
* Detects sha1() usage from crypto-hash package
|
|
9
|
+
* CWE-327: SHA-1 is cryptographically broken
|
|
10
|
+
*
|
|
11
|
+
* The crypto-hash package itself warns: "SHA-1 is insecure and should not be used"
|
|
12
|
+
* @see https://www.npmjs.com/package/crypto-hash
|
|
13
|
+
*/
|
|
14
|
+
import type { TSESLint } from '@interlace/eslint-devkit';
|
|
15
|
+
type MessageIds = 'sha1Deprecated' | 'useSha256' | 'useSha512';
|
|
16
|
+
export interface Options {
|
|
17
|
+
/** Allow SHA1 in test files. Default: false */
|
|
18
|
+
allowInTests?: boolean;
|
|
19
|
+
}
|
|
20
|
+
type RuleOptions = [Options?];
|
|
21
|
+
export declare const noSha1Hash: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
|
|
22
|
+
name: string;
|
|
23
|
+
};
|
|
24
|
+
export type { Options as NoSha1HashOptions };
|
|
@@ -0,0 +1,128 @@
|
|
|
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.noSha1Hash = void 0;
|
|
9
|
+
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
|
+
exports.noSha1Hash = (0, eslint_devkit_1.createRule)({
|
|
11
|
+
name: 'no-sha1-hash',
|
|
12
|
+
meta: {
|
|
13
|
+
type: 'problem',
|
|
14
|
+
docs: {
|
|
15
|
+
description: 'Disallow sha1() from crypto-hash package (use sha256 or sha512)',
|
|
16
|
+
},
|
|
17
|
+
hasSuggestions: true,
|
|
18
|
+
messages: {
|
|
19
|
+
sha1Deprecated: (0, eslint_devkit_1.formatLLMMessage)({
|
|
20
|
+
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
21
|
+
issueName: 'SHA-1 hash detected',
|
|
22
|
+
cwe: 'CWE-327',
|
|
23
|
+
description: 'SHA-1 is cryptographically broken and should not be used. The crypto-hash package itself warns against using sha1().',
|
|
24
|
+
severity: 'HIGH',
|
|
25
|
+
fix: 'Use sha256() or sha512() instead',
|
|
26
|
+
documentationLink: 'https://shattered.io/',
|
|
27
|
+
}),
|
|
28
|
+
useSha256: (0, eslint_devkit_1.formatLLMMessage)({
|
|
29
|
+
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
30
|
+
issueName: 'Use sha256',
|
|
31
|
+
description: 'SHA-256 provides adequate security for most use cases',
|
|
32
|
+
severity: 'LOW',
|
|
33
|
+
fix: "import { sha256 } from 'crypto-hash'; await sha256(data)",
|
|
34
|
+
documentationLink: 'https://www.npmjs.com/package/crypto-hash',
|
|
35
|
+
}),
|
|
36
|
+
useSha512: (0, eslint_devkit_1.formatLLMMessage)({
|
|
37
|
+
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
38
|
+
issueName: 'Use sha512',
|
|
39
|
+
description: 'SHA-512 provides stronger security with longer hash',
|
|
40
|
+
severity: 'LOW',
|
|
41
|
+
fix: "import { sha512 } from 'crypto-hash'; await sha512(data)",
|
|
42
|
+
documentationLink: 'https://www.npmjs.com/package/crypto-hash',
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
schema: [
|
|
46
|
+
{
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: {
|
|
49
|
+
allowInTests: {
|
|
50
|
+
type: 'boolean',
|
|
51
|
+
default: false,
|
|
52
|
+
description: 'Allow SHA1 in test files',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
defaultOptions: [
|
|
60
|
+
{
|
|
61
|
+
allowInTests: false,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
create(context, [options = {}]) {
|
|
65
|
+
const { allowInTests = false } = options;
|
|
66
|
+
const filename = context.filename;
|
|
67
|
+
const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
|
|
68
|
+
// Track if sha1 was imported from crypto-hash
|
|
69
|
+
let sha1ImportedFromCryptoHash = false;
|
|
70
|
+
return {
|
|
71
|
+
ImportDeclaration(node) {
|
|
72
|
+
if (isTestFile)
|
|
73
|
+
return;
|
|
74
|
+
// Check for: import { sha1 } from 'crypto-hash'
|
|
75
|
+
if (node.source.value === 'crypto-hash') {
|
|
76
|
+
for (const specifier of node.specifiers) {
|
|
77
|
+
if (specifier.type === eslint_devkit_1.AST_NODE_TYPES.ImportSpecifier &&
|
|
78
|
+
specifier.imported.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
79
|
+
specifier.imported.name === 'sha1') {
|
|
80
|
+
sha1ImportedFromCryptoHash = true;
|
|
81
|
+
context.report({
|
|
82
|
+
node: specifier,
|
|
83
|
+
messageId: 'sha1Deprecated',
|
|
84
|
+
suggest: [
|
|
85
|
+
{
|
|
86
|
+
messageId: 'useSha256',
|
|
87
|
+
fix: (fixer) => {
|
|
88
|
+
return fixer.replaceText(specifier.imported, 'sha256');
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
messageId: 'useSha512',
|
|
93
|
+
fix: (fixer) => {
|
|
94
|
+
return fixer.replaceText(specifier.imported, 'sha512');
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
CallExpression(node) {
|
|
104
|
+
if (isTestFile)
|
|
105
|
+
return;
|
|
106
|
+
// Check for sha1() call if imported from crypto-hash
|
|
107
|
+
if (sha1ImportedFromCryptoHash &&
|
|
108
|
+
node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
109
|
+
node.callee.name === 'sha1') {
|
|
110
|
+
/* c8 ignore next 12 -- sha1() call pattern requires specific import tracking */
|
|
111
|
+
context.report({
|
|
112
|
+
node,
|
|
113
|
+
messageId: 'sha1Deprecated',
|
|
114
|
+
suggest: [
|
|
115
|
+
{
|
|
116
|
+
messageId: 'useSha256',
|
|
117
|
+
fix: (fixer) => {
|
|
118
|
+
return fixer.replaceText(node.callee, 'sha256');
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-node-security/src/rules/no-sha1-hash/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
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 Rule: no-static-iv
|
|
8
|
+
* Detects use of hardcoded or reused initialization vectors (IVs)
|
|
9
|
+
* CWE-329: Not Using an Unpredictable IV with CBC Mode
|
|
10
|
+
*
|
|
11
|
+
* @see https://cwe.mitre.org/data/definitions/329.html
|
|
12
|
+
*/
|
|
13
|
+
import type { TSESLint } from '@interlace/eslint-devkit';
|
|
14
|
+
type MessageIds = 'staticIv' | 'useRandomBytes';
|
|
15
|
+
export interface Options {
|
|
16
|
+
/** Allow static IVs in test files. Default: false */
|
|
17
|
+
allowInTests?: boolean;
|
|
18
|
+
}
|
|
19
|
+
type RuleOptions = [Options?];
|
|
20
|
+
export declare const noStaticIv: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
|
|
21
|
+
name: string;
|
|
22
|
+
};
|
|
23
|
+
export type { Options as NoStaticIvOptions };
|
|
@@ -0,0 +1,147 @@
|
|
|
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.noStaticIv = void 0;
|
|
9
|
+
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
|
+
/**
|
|
11
|
+
* Patterns that indicate a hardcoded IV
|
|
12
|
+
*/
|
|
13
|
+
const STATIC_IV_PATTERNS = [
|
|
14
|
+
/^[0-9a-f]+$/i, // Hex string
|
|
15
|
+
/^[A-Za-z0-9+/]+=*$/, // Base64
|
|
16
|
+
];
|
|
17
|
+
exports.noStaticIv = (0, eslint_devkit_1.createRule)({
|
|
18
|
+
name: 'no-static-iv',
|
|
19
|
+
meta: {
|
|
20
|
+
type: 'problem',
|
|
21
|
+
docs: {
|
|
22
|
+
description: 'Disallow static or hardcoded initialization vectors (IVs)',
|
|
23
|
+
},
|
|
24
|
+
hasSuggestions: false,
|
|
25
|
+
messages: {
|
|
26
|
+
staticIv: (0, eslint_devkit_1.formatLLMMessage)({
|
|
27
|
+
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
28
|
+
issueName: 'Static IV detected',
|
|
29
|
+
cwe: 'CWE-329',
|
|
30
|
+
description: 'Hardcoded IV detected. Using static IVs makes encryption deterministic, allowing attackers to detect repeated plaintexts.',
|
|
31
|
+
severity: 'HIGH',
|
|
32
|
+
fix: 'Generate IV dynamically using crypto.randomBytes(16)',
|
|
33
|
+
documentationLink: 'https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#initialization-vectors',
|
|
34
|
+
}),
|
|
35
|
+
useRandomBytes: (0, eslint_devkit_1.formatLLMMessage)({
|
|
36
|
+
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
37
|
+
issueName: 'Use randomBytes',
|
|
38
|
+
description: 'Generate IV dynamically for each encryption operation',
|
|
39
|
+
severity: 'LOW',
|
|
40
|
+
fix: 'const iv = crypto.randomBytes(16);',
|
|
41
|
+
documentationLink: 'https://nodejs.org/api/crypto.html#cryptorandombytessize-callback',
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
schema: [
|
|
45
|
+
{
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
allowInTests: {
|
|
49
|
+
type: 'boolean',
|
|
50
|
+
default: false,
|
|
51
|
+
description: 'Allow static IVs in test files',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
additionalProperties: false,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
defaultOptions: [
|
|
59
|
+
{
|
|
60
|
+
allowInTests: false,
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
create(context, [options = {}]) {
|
|
64
|
+
const { allowInTests = false } = options;
|
|
65
|
+
const filename = context.filename;
|
|
66
|
+
const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
|
|
67
|
+
// Track variables assigned from randomBytes
|
|
68
|
+
const randomIvVariables = new Set();
|
|
69
|
+
function checkVariableDeclarator(node) {
|
|
70
|
+
// Track: const iv = crypto.randomBytes(16)
|
|
71
|
+
if (node.id.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
72
|
+
node.init?.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
|
|
73
|
+
const init = node.init;
|
|
74
|
+
if (init.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
|
|
75
|
+
init.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
76
|
+
init.callee.property.name === 'randomBytes') {
|
|
77
|
+
randomIvVariables.add(node.id.name);
|
|
78
|
+
}
|
|
79
|
+
/* c8 ignore next 4 -- standalone randomBytes call requires specific import pattern */
|
|
80
|
+
if (init.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
81
|
+
init.callee.name === 'randomBytes') {
|
|
82
|
+
randomIvVariables.add(node.id.name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function checkCallExpression(node) {
|
|
87
|
+
if (isTestFile)
|
|
88
|
+
return;
|
|
89
|
+
// Check for createCipheriv calls
|
|
90
|
+
const isCipherivCall = (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
|
|
91
|
+
node.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
92
|
+
(node.callee.property.name === 'createCipheriv' || node.callee.property.name === 'createDecipheriv')) ||
|
|
93
|
+
(node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
94
|
+
(node.callee.name === 'createCipheriv' || node.callee.name === 'createDecipheriv'));
|
|
95
|
+
if (isCipherivCall && node.arguments.length >= 3) {
|
|
96
|
+
const ivArg = node.arguments[2];
|
|
97
|
+
checkIvArgument(ivArg);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function checkIvArgument(ivArg) {
|
|
101
|
+
// Check for string literal IV
|
|
102
|
+
if (ivArg.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof ivArg.value === 'string') {
|
|
103
|
+
const value = ivArg.value;
|
|
104
|
+
if (STATIC_IV_PATTERNS.some(p => p.test(value)) || value.length >= 8) {
|
|
105
|
+
reportStaticIv(ivArg);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Check for Buffer.from('static')
|
|
109
|
+
if (ivArg.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
|
|
110
|
+
ivArg.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
|
|
111
|
+
ivArg.callee.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
112
|
+
ivArg.callee.object.name === 'Buffer' &&
|
|
113
|
+
ivArg.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
114
|
+
(ivArg.callee.property.name === 'from' || ivArg.callee.property.name === 'alloc')) {
|
|
115
|
+
const firstArg = ivArg.arguments[0];
|
|
116
|
+
if (firstArg?.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof firstArg.value === 'string') {
|
|
117
|
+
reportStaticIv(ivArg);
|
|
118
|
+
}
|
|
119
|
+
/* c8 ignore next 7 -- ArrayExpression with all numeric literals is rare pattern */
|
|
120
|
+
// Check for new Uint8Array([...])
|
|
121
|
+
if (firstArg?.type === eslint_devkit_1.AST_NODE_TYPES.ArrayExpression) {
|
|
122
|
+
const allLiterals = firstArg.elements.every((el) => el?.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof el.value === 'number');
|
|
123
|
+
if (allLiterals) {
|
|
124
|
+
reportStaticIv(ivArg);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Check for variable reference that's NOT from randomBytes
|
|
129
|
+
if (ivArg.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
|
|
130
|
+
// If we've tracked this variable as coming from randomBytes, it's OK
|
|
131
|
+
// Otherwise, we can't be sure - only flag if we have evidence it's static
|
|
132
|
+
// For now, we don't report variables as we can't always determine their source
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function reportStaticIv(node) {
|
|
136
|
+
context.report({
|
|
137
|
+
node,
|
|
138
|
+
messageId: 'staticIv',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
VariableDeclarator: checkVariableDeclarator,
|
|
143
|
+
CallExpression: checkCallExpression,
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-node-security/src/rules/no-static-iv/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
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 Rule: no-timing-unsafe-compare
|
|
8
|
+
* Detects === comparison of secrets, suggest crypto.timingSafeEqual()
|
|
9
|
+
* CWE-208: Observable Timing Discrepancy
|
|
10
|
+
*
|
|
11
|
+
* @see https://cwe.mitre.org/data/definitions/208.html
|
|
12
|
+
*/
|
|
13
|
+
import type { TSESLint } from '@interlace/eslint-devkit';
|
|
14
|
+
type MessageIds = 'timingUnsafeCompare' | 'useTimingSafeEqual';
|
|
15
|
+
export interface Options {
|
|
16
|
+
/** Variable name patterns that indicate secrets. Default: ['token', 'secret', 'key', 'password', 'hash', 'signature', 'mac', 'hmac', 'digest', 'apiKey', 'api_key'] */
|
|
17
|
+
secretPatterns?: string[];
|
|
18
|
+
}
|
|
19
|
+
type RuleOptions = [Options?];
|
|
20
|
+
export declare const noTimingUnsafeCompare: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
|
|
21
|
+
name: string;
|
|
22
|
+
};
|
|
23
|
+
export type { Options as NoTimingUnsafeCompareOptions };
|
|
@@ -0,0 +1,114 @@
|
|
|
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.noTimingUnsafeCompare = void 0;
|
|
9
|
+
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
|
+
const DEFAULT_SECRET_PATTERNS = [
|
|
11
|
+
// Common secret names (camelCase, snake_case, kebab-case)
|
|
12
|
+
'token', 'secret', 'key', 'password', 'hash', 'signature',
|
|
13
|
+
'mac', 'hmac', 'digest', 'apiKey', 'api_key', 'api-key',
|
|
14
|
+
'auth', 'credential', 'bearer', 'jwt', 'csrf', 'nonce',
|
|
15
|
+
// PII and sensitive data patterns
|
|
16
|
+
'ssn', 'social_security', 'social-security',
|
|
17
|
+
'pii', 'private_key', 'private-key', 'privateKey',
|
|
18
|
+
'access_token', 'access-token', 'accessToken',
|
|
19
|
+
'refresh_token', 'refresh-token', 'refreshToken',
|
|
20
|
+
'session_id', 'session-id', 'sessionId',
|
|
21
|
+
'auth_token', 'auth-token', 'authToken',
|
|
22
|
+
'encryption_key', 'encryption-key', 'encryptionKey',
|
|
23
|
+
];
|
|
24
|
+
exports.noTimingUnsafeCompare = (0, eslint_devkit_1.createRule)({
|
|
25
|
+
name: 'no-timing-unsafe-compare',
|
|
26
|
+
meta: {
|
|
27
|
+
type: 'problem',
|
|
28
|
+
docs: {
|
|
29
|
+
description: 'Disallow timing-unsafe comparison of secrets',
|
|
30
|
+
},
|
|
31
|
+
hasSuggestions: true,
|
|
32
|
+
messages: {
|
|
33
|
+
timingUnsafeCompare: (0, eslint_devkit_1.formatLLMMessage)({
|
|
34
|
+
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
35
|
+
issueName: 'Timing-unsafe comparison',
|
|
36
|
+
cwe: 'CWE-208',
|
|
37
|
+
description: 'Using === to compare secrets enables timing attacks. The comparison short-circuits on first mismatch, leaking information about the secret.',
|
|
38
|
+
severity: 'HIGH',
|
|
39
|
+
fix: 'Use crypto.timingSafeEqual() for constant-time comparison',
|
|
40
|
+
documentationLink: 'https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b',
|
|
41
|
+
}),
|
|
42
|
+
useTimingSafeEqual: (0, eslint_devkit_1.formatLLMMessage)({
|
|
43
|
+
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
44
|
+
issueName: 'Use timingSafeEqual',
|
|
45
|
+
description: 'Use constant-time comparison to prevent timing attacks',
|
|
46
|
+
severity: 'LOW',
|
|
47
|
+
fix: 'crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b))',
|
|
48
|
+
documentationLink: 'https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b',
|
|
49
|
+
}),
|
|
50
|
+
},
|
|
51
|
+
schema: [
|
|
52
|
+
{
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties: {
|
|
55
|
+
secretPatterns: {
|
|
56
|
+
type: 'array',
|
|
57
|
+
items: { type: 'string' },
|
|
58
|
+
default: DEFAULT_SECRET_PATTERNS,
|
|
59
|
+
description: 'Variable name patterns that indicate secrets',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
additionalProperties: false,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
defaultOptions: [
|
|
67
|
+
{
|
|
68
|
+
secretPatterns: DEFAULT_SECRET_PATTERNS,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
create(context, [options = {}]) {
|
|
72
|
+
const { secretPatterns = DEFAULT_SECRET_PATTERNS } = options;
|
|
73
|
+
const patterns = secretPatterns.map(p => new RegExp(p, 'i'));
|
|
74
|
+
function isSecretIdentifier(node) {
|
|
75
|
+
if (node.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
|
|
76
|
+
return patterns.some(p => p.test(node.name));
|
|
77
|
+
}
|
|
78
|
+
if (node.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression) {
|
|
79
|
+
const prop = node.property;
|
|
80
|
+
if (prop.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
|
|
81
|
+
return patterns.some(p => p.test(prop.name));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
function checkBinaryExpression(node) {
|
|
87
|
+
// Check for === or == comparisons
|
|
88
|
+
if (node.operator !== '===' && node.operator !== '==' &&
|
|
89
|
+
node.operator !== '!==' && node.operator !== '!=') {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Check if either side looks like a secret
|
|
93
|
+
const leftIsSecret = isSecretIdentifier(node.left);
|
|
94
|
+
const rightIsSecret = isSecretIdentifier(node.right);
|
|
95
|
+
if (leftIsSecret || rightIsSecret) {
|
|
96
|
+
/* c8 ignore next 11 -- suggestions with fix: () => null cannot be tested with RuleTester */
|
|
97
|
+
context.report({
|
|
98
|
+
node,
|
|
99
|
+
messageId: 'timingUnsafeCompare',
|
|
100
|
+
suggest: [
|
|
101
|
+
{
|
|
102
|
+
messageId: 'useTimingSafeEqual',
|
|
103
|
+
fix: () => null, // Complex refactoring
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
BinaryExpression: checkBinaryExpression,
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-node-security/src/rules/no-timing-unsafe-compare/index.ts"],"names":[],"mappings":"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"}
|