@sun-asterisk/sunlint 1.0.5
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 +202 -0
- package/LICENSE +21 -0
- package/README.md +490 -0
- package/cli-legacy.js +355 -0
- package/cli.js +35 -0
- package/config/default.json +22 -0
- package/config/presets/beginner.json +36 -0
- package/config/presets/ci.json +46 -0
- package/config/presets/recommended.json +24 -0
- package/config/presets/strict.json +32 -0
- package/config/rules-registry.json +681 -0
- package/config/sunlint-schema.json +166 -0
- package/config/typescript/custom-rules-new.js +0 -0
- package/config/typescript/custom-rules.js +9 -0
- package/config/typescript/eslint.config.js +110 -0
- package/config/typescript/package-lock.json +1585 -0
- package/config/typescript/package.json +13 -0
- package/config/typescript/security-rules/index.js +90 -0
- package/config/typescript/security-rules/s005-no-origin-auth.js +95 -0
- package/config/typescript/security-rules/s006-activation-recovery-secret-not-plaintext.js +69 -0
- package/config/typescript/security-rules/s008-crypto-agility.js +62 -0
- package/config/typescript/security-rules/s009-no-insecure-crypto.js +103 -0
- package/config/typescript/security-rules/s010-no-insecure-random-in-sensitive-context.js +123 -0
- package/config/typescript/security-rules/s011-no-insecure-uuid.js +66 -0
- package/config/typescript/security-rules/s012-hardcode-secret.js +71 -0
- package/config/typescript/security-rules/s014-insecure-tls-version.js +50 -0
- package/config/typescript/security-rules/s015-insecure-tls-certificate.js +43 -0
- package/config/typescript/security-rules/s016-sensitive-query-parameter.js +59 -0
- package/config/typescript/security-rules/s017-no-sql-injection.js +193 -0
- package/config/typescript/security-rules/s018-positive-input-validation.js +56 -0
- package/config/typescript/security-rules/s019-no-raw-user-input-in-email.js +113 -0
- package/config/typescript/security-rules/s020-no-eval-dynamic-execution.js +89 -0
- package/config/typescript/security-rules/s022-output-encoding.js +78 -0
- package/config/typescript/security-rules/s023-no-json-injection.js +300 -0
- package/config/typescript/security-rules/s025-server-side-input-validation.js +217 -0
- package/config/typescript/security-rules/s026-json-schema-validation.js +68 -0
- package/config/typescript/security-rules/s027-no-hardcoded-secrets.js +80 -0
- package/config/typescript/security-rules/s029-require-csrf-protection.js +79 -0
- package/config/typescript/security-rules/s030-no-directory-browsing.js +78 -0
- package/config/typescript/security-rules/s033-require-samesite-cookie.js +80 -0
- package/config/typescript/security-rules/s034-require-host-cookie-prefix.js +77 -0
- package/config/typescript/security-rules/s035-cookie-specific-path.js +74 -0
- package/config/typescript/security-rules/s036-no-unsafe-file-include.js +68 -0
- package/config/typescript/security-rules/s037-require-anti-cache-headers.js +70 -0
- package/config/typescript/security-rules/s038-no-version-disclosure.js +74 -0
- package/config/typescript/security-rules/s039-no-session-token-in-url.js +63 -0
- package/config/typescript/security-rules/s041-require-session-invalidate-on-logout.js +211 -0
- package/config/typescript/security-rules/s042-require-periodic-reauthentication.js +294 -0
- package/config/typescript/security-rules/s043-terminate-sessions-on-password-change.js +254 -0
- package/config/typescript/security-rules/s044-require-full-session-for-sensitive-operations.js +292 -0
- package/config/typescript/security-rules/s045-anti-automation-controls.js +46 -0
- package/config/typescript/security-rules/s046-secure-notification-on-auth-change.js +44 -0
- package/config/typescript/security-rules/s048-password-credential-recovery.js +54 -0
- package/config/typescript/security-rules/s050-session-token-weak-hash.js +94 -0
- package/config/typescript/security-rules/s052-secure-random-authentication-code.js +66 -0
- package/config/typescript/security-rules/s054-verification-default-account.js +109 -0
- package/config/typescript/security-rules/s057-utc-logging.js +54 -0
- package/config/typescript/security-rules/s058-no-ssrf.js +73 -0
- package/config/typescript/test-s005-working.ts +22 -0
- package/config/typescript/tsconfig.json +29 -0
- package/core/ai-analyzer.js +169 -0
- package/core/analysis-orchestrator.js +705 -0
- package/core/cli-action-handler.js +230 -0
- package/core/cli-program.js +106 -0
- package/core/config-manager.js +396 -0
- package/core/config-merger.js +136 -0
- package/core/config-override-processor.js +74 -0
- package/core/config-preset-resolver.js +65 -0
- package/core/config-source-loader.js +152 -0
- package/core/config-validator.js +126 -0
- package/core/dependency-manager.js +105 -0
- package/core/eslint-engine-service.js +312 -0
- package/core/eslint-instance-manager.js +104 -0
- package/core/eslint-integration-service.js +363 -0
- package/core/git-utils.js +170 -0
- package/core/multi-rule-runner.js +239 -0
- package/core/output-service.js +250 -0
- package/core/report-generator.js +320 -0
- package/core/rule-mapping-service.js +309 -0
- package/core/rule-selection-service.js +121 -0
- package/core/sunlint-engine-service.js +23 -0
- package/core/typescript-analyzer.js +262 -0
- package/core/typescript-engine.js +313 -0
- package/docs/AI.md +163 -0
- package/docs/ARCHITECTURE.md +78 -0
- package/docs/CI-CD-GUIDE.md +315 -0
- package/docs/COMMAND-EXAMPLES.md +256 -0
- package/docs/DEBUG.md +86 -0
- package/docs/DISTRIBUTION.md +153 -0
- package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
- package/docs/ESLINT_INTEGRATION.md +238 -0
- package/docs/FOLDER_STRUCTURE.md +59 -0
- package/docs/HEURISTIC_VS_AI.md +113 -0
- package/docs/README.md +32 -0
- package/docs/RELEASE_GUIDE.md +230 -0
- package/docs/RULE-RESPONSIBILITY-MATRIX.md +204 -0
- package/eslint-integration/.eslintrc.js +98 -0
- package/eslint-integration/cli.js +35 -0
- package/eslint-integration/eslint-plugin-custom/c002-no-duplicate-code.js +204 -0
- package/eslint-integration/eslint-plugin-custom/c003-no-vague-abbreviations.js +246 -0
- package/eslint-integration/eslint-plugin-custom/c006-function-name-verb-noun.js +207 -0
- package/eslint-integration/eslint-plugin-custom/c010-limit-block-nesting.js +90 -0
- package/eslint-integration/eslint-plugin-custom/c013-no-dead-code.js +43 -0
- package/eslint-integration/eslint-plugin-custom/c014-abstract-dependency-preferred.js +38 -0
- package/eslint-integration/eslint-plugin-custom/c017-limit-constructor-logic.js +39 -0
- package/eslint-integration/eslint-plugin-custom/c018-no-generic-throw.js +335 -0
- package/eslint-integration/eslint-plugin-custom/c023-no-duplicate-variable-name-in-scope.js +142 -0
- package/eslint-integration/eslint-plugin-custom/c027-limit-function-nesting.js +50 -0
- package/eslint-integration/eslint-plugin-custom/c029-catch-block-logging.js +80 -0
- package/eslint-integration/eslint-plugin-custom/c030-use-custom-error-classes.js +294 -0
- package/eslint-integration/eslint-plugin-custom/c034-no-implicit-return.js +34 -0
- package/eslint-integration/eslint-plugin-custom/c035-no-empty-catch.js +32 -0
- package/eslint-integration/eslint-plugin-custom/c041-no-config-inline.js +64 -0
- package/eslint-integration/eslint-plugin-custom/c042-boolean-name-prefix.js +406 -0
- package/eslint-integration/eslint-plugin-custom/c043-no-console-or-print.js +300 -0
- package/eslint-integration/eslint-plugin-custom/c047-no-duplicate-retry-logic.js +239 -0
- package/eslint-integration/eslint-plugin-custom/c048-no-var-declaration.js +31 -0
- package/eslint-integration/eslint-plugin-custom/c076-one-assert-per-test.js +184 -0
- package/eslint-integration/eslint-plugin-custom/index.js +155 -0
- package/eslint-integration/eslint-plugin-custom/package.json +13 -0
- package/eslint-integration/eslint-plugin-custom/package.json.bak +9 -0
- package/eslint-integration/eslint-plugin-custom/s003-no-unvalidated-redirect.js +86 -0
- package/eslint-integration/eslint-plugin-custom/s005-no-origin-auth.js +95 -0
- package/eslint-integration/eslint-plugin-custom/s006-activation-recovery-secret-not-plaintext.js +69 -0
- package/eslint-integration/eslint-plugin-custom/s008-crypto-agility.js +62 -0
- package/eslint-integration/eslint-plugin-custom/s009-no-insecure-crypto.js +103 -0
- package/eslint-integration/eslint-plugin-custom/s010-no-insecure-random-in-sensitive-context.js +123 -0
- package/eslint-integration/eslint-plugin-custom/s011-no-insecure-uuid.js +66 -0
- package/eslint-integration/eslint-plugin-custom/s012-hardcode-secret.js +71 -0
- package/eslint-integration/eslint-plugin-custom/s014-insecure-tls-version.js +50 -0
- package/eslint-integration/eslint-plugin-custom/s015-insecure-tls-certificate.js +43 -0
- package/eslint-integration/eslint-plugin-custom/s016-sensitive-query-parameter.js +59 -0
- package/eslint-integration/eslint-plugin-custom/s017-no-sql-injection.js +193 -0
- package/eslint-integration/eslint-plugin-custom/s018-positive-input-validation.js +56 -0
- package/eslint-integration/eslint-plugin-custom/s019-no-raw-user-input-in-email.js +113 -0
- package/eslint-integration/eslint-plugin-custom/s020-no-eval-dynamic-execution.js +89 -0
- package/eslint-integration/eslint-plugin-custom/s022-output-encoding.js +78 -0
- package/eslint-integration/eslint-plugin-custom/s023-no-json-injection.js +300 -0
- package/eslint-integration/eslint-plugin-custom/s025-server-side-input-validation.js +217 -0
- package/eslint-integration/eslint-plugin-custom/s026-json-schema-validation.js +68 -0
- package/eslint-integration/eslint-plugin-custom/s027-no-hardcoded-secrets.js +80 -0
- package/eslint-integration/eslint-plugin-custom/s029-require-csrf-protection.js +79 -0
- package/eslint-integration/eslint-plugin-custom/s030-no-directory-browsing.js +78 -0
- package/eslint-integration/eslint-plugin-custom/s033-require-samesite-cookie.js +80 -0
- package/eslint-integration/eslint-plugin-custom/s034-require-host-cookie-prefix.js +77 -0
- package/eslint-integration/eslint-plugin-custom/s035-cookie-specific-path.js +74 -0
- package/eslint-integration/eslint-plugin-custom/s036-no-unsafe-file-include.js +68 -0
- package/eslint-integration/eslint-plugin-custom/s037-require-anti-cache-headers.js +70 -0
- package/eslint-integration/eslint-plugin-custom/s038-no-version-disclosure.js +74 -0
- package/eslint-integration/eslint-plugin-custom/s039-no-session-token-in-url.js +63 -0
- package/eslint-integration/eslint-plugin-custom/s041-require-session-invalidate-on-logout.js +211 -0
- package/eslint-integration/eslint-plugin-custom/s042-require-periodic-reauthentication.js +294 -0
- package/eslint-integration/eslint-plugin-custom/s043-terminate-sessions-on-password-change.js +254 -0
- package/eslint-integration/eslint-plugin-custom/s044-require-full-session-for-sensitive-operations.js +292 -0
- package/eslint-integration/eslint-plugin-custom/s045-anti-automation-controls.js +46 -0
- package/eslint-integration/eslint-plugin-custom/s046-secure-notification-on-auth-change.js +44 -0
- package/eslint-integration/eslint-plugin-custom/s047-secure-random-passwords.js +108 -0
- package/eslint-integration/eslint-plugin-custom/s048-password-credential-recovery.js +54 -0
- package/eslint-integration/eslint-plugin-custom/s050-session-token-weak-hash.js +94 -0
- package/eslint-integration/eslint-plugin-custom/s052-secure-random-authentication-code.js +66 -0
- package/eslint-integration/eslint-plugin-custom/s054-verification-default-account.js +109 -0
- package/eslint-integration/eslint-plugin-custom/s055-verification-rest-check-the-incoming-content-type.js +143 -0
- package/eslint-integration/eslint-plugin-custom/s057-utc-logging.js +54 -0
- package/eslint-integration/eslint-plugin-custom/s058-no-ssrf.js +73 -0
- package/eslint-integration/eslint-plugin-custom/t002-interface-prefix-i.js +42 -0
- package/eslint-integration/eslint-plugin-custom/t003-ts-ignore-reason.js +48 -0
- package/eslint-integration/eslint-plugin-custom/t004-interface-public-only.js +160 -0
- package/eslint-integration/eslint-plugin-custom/t007-no-fn-in-constructor.js +52 -0
- package/eslint-integration/eslint-plugin-custom/t011-no-real-time-dependency.js +175 -0
- package/eslint-integration/eslint-plugin-custom/t019-no-empty-type.js +95 -0
- package/eslint-integration/eslint-plugin-custom/t025-no-nested-union-tuple.js +48 -0
- package/eslint-integration/eslint-plugin-custom/t026-limit-nested-generics.js +377 -0
- package/eslint-integration/eslint.config.js +125 -0
- package/eslint-integration/eslint.config.simple.js +24 -0
- package/eslint-integration/node_modules/eslint-plugin-custom/package.json +0 -0
- package/eslint-integration/package.json +23 -0
- package/eslint-integration/sample.ts +53 -0
- package/eslint-integration/test-s003.js +5 -0
- package/eslint-integration/tsconfig.json +27 -0
- package/examples/.github/workflows/code-quality.yml +111 -0
- package/examples/.sunlint.json +42 -0
- package/examples/README.md +47 -0
- package/examples/package.json +33 -0
- package/package.json +100 -0
- package/rules/C006_function_naming/analyzer.js +338 -0
- package/rules/C006_function_naming/config.json +86 -0
- package/rules/C019_log_level_usage/analyzer.js +359 -0
- package/rules/C019_log_level_usage/config.json +121 -0
- package/rules/C029_catch_block_logging/analyzer.js +339 -0
- package/rules/C029_catch_block_logging/config.json +59 -0
- package/rules/C031_validation_separation/README.md +72 -0
- package/rules/C031_validation_separation/analyzer.js +186 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule: S043 – Terminate all sessions on password change
|
|
3
|
+
* Rule ID: custom/s043
|
|
4
|
+
* Purpose: Ensure password change methods terminate all other active sessions
|
|
5
|
+
* OWASP 3.3.3: Verify that the application gives the option to terminate all other active sessions
|
|
6
|
+
* after a successful password change (including change via password reset/recovery)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
"use strict";
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
meta: {
|
|
13
|
+
type: "problem",
|
|
14
|
+
docs: {
|
|
15
|
+
description: "Ensure password change methods terminate all other active sessions and require re-authentication",
|
|
16
|
+
recommended: true,
|
|
17
|
+
},
|
|
18
|
+
schema: [],
|
|
19
|
+
messages: {
|
|
20
|
+
missingSessionTermination: "Password change method '{{method}}' must terminate all other active sessions. Use sessionManager.terminateAllSessions(), tokenService.revokeAllTokens(), or equivalent session cleanup.",
|
|
21
|
+
missingReAuthRequirement: "Password change method '{{method}}' should require re-authentication after successful password change.",
|
|
22
|
+
incompleteImplementation: "Password change method '{{method}}' should both terminate existing sessions and require user re-authentication for security compliance.",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
create(context) {
|
|
27
|
+
// Keywords that indicate password change functionality
|
|
28
|
+
const passwordChangeKeywords = [
|
|
29
|
+
"password", "passwd", "pwd", "changepassword", "change-password",
|
|
30
|
+
"updatepassword", "update-password", "resetpassword", "reset-password",
|
|
31
|
+
"newpassword", "new-password", "setpassword", "set-password"
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
// Session termination methods
|
|
35
|
+
const sessionTerminationMethods = [
|
|
36
|
+
"terminateAllSessions", "revokeAllTokens", "invalidateAllSessions",
|
|
37
|
+
"destroyAllSessions", "clearAllSessions", "terminateOtherSessions",
|
|
38
|
+
"revokeOtherTokens", "logoutAllDevices", "signOutAllDevices",
|
|
39
|
+
"terminateAll", "revokeAll", "invalidateAll", "destroyAll"
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// Re-authentication requirement methods
|
|
43
|
+
const reAuthMethods = [
|
|
44
|
+
"requireReAuth", "forceReAuth", "requireLogin", "forceLogin",
|
|
45
|
+
"redirectToLogin", "requireAuthentication", "invalidateCurrentSession",
|
|
46
|
+
"logout", "signOut", "clearCurrentSession"
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
function isPasswordChangeMethod(name) {
|
|
50
|
+
if (!name) return false;
|
|
51
|
+
const lowerName = name.toLowerCase();
|
|
52
|
+
return passwordChangeKeywords.some(keyword => lowerName.includes(keyword));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function checkPasswordChangeMethodBody(node, methodName) {
|
|
56
|
+
let hasSessionTermination = false;
|
|
57
|
+
let hasReAuthRequirement = false;
|
|
58
|
+
|
|
59
|
+
function checkNode(n, visited = new Set()) {
|
|
60
|
+
if (!n || visited.has(n)) return;
|
|
61
|
+
visited.add(n);
|
|
62
|
+
|
|
63
|
+
// Check for session termination calls
|
|
64
|
+
if (n.type === "CallExpression") {
|
|
65
|
+
const callee = n.callee;
|
|
66
|
+
|
|
67
|
+
// Direct method calls: terminateAllSessions(), revokeAllTokens()
|
|
68
|
+
if (callee.type === "Identifier") {
|
|
69
|
+
if (sessionTerminationMethods.includes(callee.name)) {
|
|
70
|
+
hasSessionTermination = true;
|
|
71
|
+
}
|
|
72
|
+
if (reAuthMethods.includes(callee.name)) {
|
|
73
|
+
hasReAuthRequirement = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Member expressions: sessionManager.terminateAllSessions(), tokenService.revokeAllTokens()
|
|
78
|
+
else if (callee.type === "MemberExpression") {
|
|
79
|
+
const property = callee.property.name;
|
|
80
|
+
|
|
81
|
+
if (sessionTerminationMethods.includes(property)) {
|
|
82
|
+
hasSessionTermination = true;
|
|
83
|
+
}
|
|
84
|
+
if (reAuthMethods.includes(property)) {
|
|
85
|
+
hasReAuthRequirement = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check for patterns like: sessionManager.terminateAll(), authService.revokeAll()
|
|
89
|
+
if (property && (
|
|
90
|
+
property.includes("terminate") || property.includes("revoke") ||
|
|
91
|
+
property.includes("invalidate") || property.includes("destroy") ||
|
|
92
|
+
property.includes("clear")
|
|
93
|
+
)) {
|
|
94
|
+
const methodCall = property.toLowerCase();
|
|
95
|
+
if (methodCall.includes("all") || methodCall.includes("other")) {
|
|
96
|
+
hasSessionTermination = true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check function arguments and nested calls
|
|
102
|
+
if (n.arguments) {
|
|
103
|
+
n.arguments.forEach(arg => checkNode(arg, visited));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check await expressions
|
|
108
|
+
if (n.type === "AwaitExpression") {
|
|
109
|
+
checkNode(n.argument, visited);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Recursively check nested structures
|
|
113
|
+
for (const key in n) {
|
|
114
|
+
if (n[key] && typeof n[key] === "object") {
|
|
115
|
+
if (Array.isArray(n[key])) {
|
|
116
|
+
n[key].forEach(child => checkNode(child, visited));
|
|
117
|
+
} else if (n[key].type) {
|
|
118
|
+
checkNode(n[key], visited);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Analyze method body
|
|
125
|
+
if (node.body) {
|
|
126
|
+
if (node.body.type === "BlockStatement") {
|
|
127
|
+
node.body.body.forEach(stmt => checkNode(stmt));
|
|
128
|
+
} else {
|
|
129
|
+
checkNode(node.body);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { hasSessionTermination, hasReAuthRequirement };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
// Check method definitions
|
|
138
|
+
MethodDefinition(node) {
|
|
139
|
+
const methodName = node.key.name;
|
|
140
|
+
if (isPasswordChangeMethod(methodName)) {
|
|
141
|
+
const analysis = checkPasswordChangeMethodBody(node.value, methodName);
|
|
142
|
+
|
|
143
|
+
if (!analysis.hasSessionTermination && !analysis.hasReAuthRequirement) {
|
|
144
|
+
context.report({
|
|
145
|
+
node: node.key,
|
|
146
|
+
messageId: "incompleteImplementation",
|
|
147
|
+
data: { method: methodName }
|
|
148
|
+
});
|
|
149
|
+
} else if (!analysis.hasSessionTermination) {
|
|
150
|
+
context.report({
|
|
151
|
+
node: node.key,
|
|
152
|
+
messageId: "missingSessionTermination",
|
|
153
|
+
data: { method: methodName }
|
|
154
|
+
});
|
|
155
|
+
} else if (!analysis.hasReAuthRequirement) {
|
|
156
|
+
context.report({
|
|
157
|
+
node: node.key,
|
|
158
|
+
messageId: "missingReAuthRequirement",
|
|
159
|
+
data: { method: methodName }
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
// Check function declarations
|
|
166
|
+
FunctionDeclaration(node) {
|
|
167
|
+
const functionName = node.id ? node.id.name : null;
|
|
168
|
+
if (isPasswordChangeMethod(functionName)) {
|
|
169
|
+
const analysis = checkPasswordChangeMethodBody(node, functionName);
|
|
170
|
+
|
|
171
|
+
if (!analysis.hasSessionTermination && !analysis.hasReAuthRequirement) {
|
|
172
|
+
context.report({
|
|
173
|
+
node: node.id,
|
|
174
|
+
messageId: "incompleteImplementation",
|
|
175
|
+
data: { method: functionName }
|
|
176
|
+
});
|
|
177
|
+
} else if (!analysis.hasSessionTermination) {
|
|
178
|
+
context.report({
|
|
179
|
+
node: node.id,
|
|
180
|
+
messageId: "missingSessionTermination",
|
|
181
|
+
data: { method: functionName }
|
|
182
|
+
});
|
|
183
|
+
} else if (!analysis.hasReAuthRequirement) {
|
|
184
|
+
context.report({
|
|
185
|
+
node: node.id,
|
|
186
|
+
messageId: "missingReAuthRequirement",
|
|
187
|
+
data: { method: functionName }
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// Check function expressions and arrow functions assigned to variables
|
|
194
|
+
VariableDeclarator(node) {
|
|
195
|
+
if (node.init && (node.init.type === "FunctionExpression" || node.init.type === "ArrowFunctionExpression")) {
|
|
196
|
+
const varName = node.id.name;
|
|
197
|
+
if (isPasswordChangeMethod(varName)) {
|
|
198
|
+
const analysis = checkPasswordChangeMethodBody(node.init, varName);
|
|
199
|
+
|
|
200
|
+
if (!analysis.hasSessionTermination && !analysis.hasReAuthRequirement) {
|
|
201
|
+
context.report({
|
|
202
|
+
node: node.id,
|
|
203
|
+
messageId: "incompleteImplementation",
|
|
204
|
+
data: { method: varName }
|
|
205
|
+
});
|
|
206
|
+
} else if (!analysis.hasSessionTermination) {
|
|
207
|
+
context.report({
|
|
208
|
+
node: node.id,
|
|
209
|
+
messageId: "missingSessionTermination",
|
|
210
|
+
data: { method: varName }
|
|
211
|
+
});
|
|
212
|
+
} else if (!analysis.hasReAuthRequirement) {
|
|
213
|
+
context.report({
|
|
214
|
+
node: node.id,
|
|
215
|
+
messageId: "missingReAuthRequirement",
|
|
216
|
+
data: { method: varName }
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
// Check property assignments (for object methods)
|
|
224
|
+
Property(node) {
|
|
225
|
+
if (node.value && (node.value.type === "FunctionExpression" || node.value.type === "ArrowFunctionExpression")) {
|
|
226
|
+
const propName = node.key.name;
|
|
227
|
+
if (isPasswordChangeMethod(propName)) {
|
|
228
|
+
const analysis = checkPasswordChangeMethodBody(node.value, propName);
|
|
229
|
+
|
|
230
|
+
if (!analysis.hasSessionTermination && !analysis.hasReAuthRequirement) {
|
|
231
|
+
context.report({
|
|
232
|
+
node: node.key,
|
|
233
|
+
messageId: "incompleteImplementation",
|
|
234
|
+
data: { method: propName }
|
|
235
|
+
});
|
|
236
|
+
} else if (!analysis.hasSessionTermination) {
|
|
237
|
+
context.report({
|
|
238
|
+
node: node.key,
|
|
239
|
+
messageId: "missingSessionTermination",
|
|
240
|
+
data: { method: propName }
|
|
241
|
+
});
|
|
242
|
+
} else if (!analysis.hasReAuthRequirement) {
|
|
243
|
+
context.report({
|
|
244
|
+
node: node.key,
|
|
245
|
+
messageId: "missingReAuthRequirement",
|
|
246
|
+
data: { method: propName }
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
},
|
|
254
|
+
};
|
package/config/typescript/security-rules/s044-require-full-session-for-sensitive-operations.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule: S044 – Require full session for sensitive operations
|
|
3
|
+
* Rule ID: custom/s044
|
|
4
|
+
* Purpose: Verify the application ensures a full, valid login session or requires re-authentication
|
|
5
|
+
* or secondary verification before allowing any sensitive transactions or account modifications
|
|
6
|
+
* OWASP 3.7.1: Verify the application ensures a full, valid login session or requires re-authentication
|
|
7
|
+
* or secondary verification before allowing any sensitive transactions or account modifications
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
meta: {
|
|
14
|
+
type: "problem",
|
|
15
|
+
docs: {
|
|
16
|
+
description: "Require full session validation or re-authentication before sensitive operations",
|
|
17
|
+
recommended: true,
|
|
18
|
+
},
|
|
19
|
+
schema: [],
|
|
20
|
+
messages: {
|
|
21
|
+
missingSensitiveOperationProtection: "Sensitive operation '{{method}}' requires full session validation or re-authentication. Use sessionManager.validateFullSession(), requireReAuth(), or 2FA verification.",
|
|
22
|
+
incompleteSessionValidation: "Method '{{method}}' should validate complete session state before allowing sensitive operations.",
|
|
23
|
+
missingSecondaryVerification: "Sensitive operation '{{method}}' should require secondary verification (2FA, password confirmation) for enhanced security.",
|
|
24
|
+
halfOpenSessionAccess: "Method '{{method}}' may allow access with incomplete session. Ensure full session validation before sensitive operations.",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
create(context) {
|
|
29
|
+
// Keywords that indicate sensitive operations
|
|
30
|
+
const sensitiveOperationKeywords = [
|
|
31
|
+
// Account modifications
|
|
32
|
+
"password", "passwd", "pwd", "changepassword", "updatepassword", "resetpassword",
|
|
33
|
+
"email", "changeemail", "updateemail", "setemail",
|
|
34
|
+
"profile", "updateprofile", "changeprofile", "editprofile",
|
|
35
|
+
"account", "updateaccount", "changeaccount", "deleteaccount",
|
|
36
|
+
"user", "updateuser", "changeuser", "deleteuser",
|
|
37
|
+
|
|
38
|
+
// Financial/Transaction operations
|
|
39
|
+
"payment", "pay", "transfer", "transaction", "withdraw", "deposit",
|
|
40
|
+
"purchase", "buy", "order", "checkout", "billing",
|
|
41
|
+
"balance", "fund", "money", "amount", "financial",
|
|
42
|
+
|
|
43
|
+
// Security operations
|
|
44
|
+
"permission", "role", "access", "privilege", "authorization",
|
|
45
|
+
"security", "admin", "superuser", "root",
|
|
46
|
+
"settings", "config", "configuration", "sensitive",
|
|
47
|
+
|
|
48
|
+
// Data operations
|
|
49
|
+
"delete", "remove", "destroy", "drop", "truncate",
|
|
50
|
+
"export", "import", "backup", "restore", "migration"
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
// Session validation methods
|
|
54
|
+
const sessionValidationMethods = [
|
|
55
|
+
"validateFullSession", "checkFullSession", "ensureFullSession",
|
|
56
|
+
"validateSession", "checkSession", "ensureSession",
|
|
57
|
+
"isFullyAuthenticated", "hasFullSession", "isSessionValid",
|
|
58
|
+
"validateAuthState", "checkAuthState", "ensureAuthState",
|
|
59
|
+
"verifySession", "confirmSession", "validateUser"
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
// Re-authentication methods
|
|
63
|
+
const reAuthMethods = [
|
|
64
|
+
"requireReAuth", "forceReAuth", "requireAuthentication",
|
|
65
|
+
"requestReAuth", "validateReAuth", "checkReAuth",
|
|
66
|
+
"confirmPassword", "verifyPassword", "validatePassword",
|
|
67
|
+
"requireLogin", "forceLogin", "redirectToLogin"
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
// Secondary verification methods (2FA, etc.)
|
|
71
|
+
const secondaryVerificationMethods = [
|
|
72
|
+
"require2FA", "requireTwoFactor", "verifyTwoFactor",
|
|
73
|
+
"requireOTP", "verifyOTP", "validateOTP",
|
|
74
|
+
"requireMFA", "verifyMFA", "validateMFA",
|
|
75
|
+
"requireSMS", "verifySMS", "validateSMS",
|
|
76
|
+
"requireEmail", "verifyEmail", "validateEmail",
|
|
77
|
+
"requireSecondaryAuth", "verifySecondaryAuth"
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
// Patterns indicating half-open or incomplete sessions
|
|
81
|
+
const halfOpenSessionPatterns = [
|
|
82
|
+
"partial", "incomplete", "temp", "temporary", "pending",
|
|
83
|
+
"halfopen", "half-open", "inprogress", "in-progress"
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
function isSensitiveOperation(name) {
|
|
87
|
+
if (!name) return false;
|
|
88
|
+
const lowerName = name.toLowerCase();
|
|
89
|
+
return sensitiveOperationKeywords.some(keyword =>
|
|
90
|
+
lowerName.includes(keyword) ||
|
|
91
|
+
name.toLowerCase().includes(keyword)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function checkSensitiveOperationMethodBody(node, methodName) {
|
|
96
|
+
let hasSessionValidation = false;
|
|
97
|
+
let hasReAuthentication = false;
|
|
98
|
+
let hasSecondaryVerification = false;
|
|
99
|
+
let hasHalfOpenSessionCheck = false;
|
|
100
|
+
|
|
101
|
+
function analyzeNode(n, visited = new Set()) {
|
|
102
|
+
if (!n || visited.has(n)) return;
|
|
103
|
+
visited.add(n);
|
|
104
|
+
|
|
105
|
+
// Check for method calls
|
|
106
|
+
if (n.type === "CallExpression") {
|
|
107
|
+
const callee = n.callee;
|
|
108
|
+
|
|
109
|
+
// Direct method calls
|
|
110
|
+
if (callee.type === "Identifier") {
|
|
111
|
+
const methodName = callee.name;
|
|
112
|
+
|
|
113
|
+
if (sessionValidationMethods.includes(methodName)) {
|
|
114
|
+
hasSessionValidation = true;
|
|
115
|
+
}
|
|
116
|
+
if (reAuthMethods.includes(methodName)) {
|
|
117
|
+
hasReAuthentication = true;
|
|
118
|
+
}
|
|
119
|
+
if (secondaryVerificationMethods.includes(methodName)) {
|
|
120
|
+
hasSecondaryVerification = true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Member expressions: sessionManager.validateFullSession()
|
|
125
|
+
else if (callee.type === "MemberExpression") {
|
|
126
|
+
const property = callee.property.name;
|
|
127
|
+
|
|
128
|
+
if (sessionValidationMethods.includes(property)) {
|
|
129
|
+
hasSessionValidation = true;
|
|
130
|
+
}
|
|
131
|
+
if (reAuthMethods.includes(property)) {
|
|
132
|
+
hasReAuthentication = true;
|
|
133
|
+
}
|
|
134
|
+
if (secondaryVerificationMethods.includes(property)) {
|
|
135
|
+
hasSecondaryVerification = true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check for session validation patterns
|
|
139
|
+
if (property && (
|
|
140
|
+
property.includes("validate") || property.includes("check") ||
|
|
141
|
+
property.includes("verify") || property.includes("ensure")
|
|
142
|
+
)) {
|
|
143
|
+
const methodCall = property.toLowerCase();
|
|
144
|
+
if (methodCall.includes("session") || methodCall.includes("auth")) {
|
|
145
|
+
hasSessionValidation = true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Check for conditional statements that might validate session
|
|
152
|
+
if (n.type === "IfStatement") {
|
|
153
|
+
const test = n.test;
|
|
154
|
+
if (test && test.type === "CallExpression") {
|
|
155
|
+
const callee = test.callee;
|
|
156
|
+
|
|
157
|
+
// Check for session validation in if conditions
|
|
158
|
+
if (callee.type === "MemberExpression") {
|
|
159
|
+
const property = callee.property.name;
|
|
160
|
+
if (property && (
|
|
161
|
+
property.includes("isAuthenticated") ||
|
|
162
|
+
property.includes("hasSession") ||
|
|
163
|
+
property.includes("isValid")
|
|
164
|
+
)) {
|
|
165
|
+
hasSessionValidation = true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Check for guard clauses
|
|
172
|
+
if (n.type === "ThrowStatement" || n.type === "ReturnStatement") {
|
|
173
|
+
// Look for early returns/throws that might be session guards
|
|
174
|
+
if (n.argument && n.argument.type === "CallExpression") {
|
|
175
|
+
const callee = n.argument.callee;
|
|
176
|
+
if (callee.type === "Identifier" && callee.name === "Error") {
|
|
177
|
+
hasSessionValidation = true; // Assume error throwing is session validation
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Recursively check child nodes
|
|
183
|
+
for (const key in n) {
|
|
184
|
+
if (key === "parent" || key === "range" || key === "loc") continue;
|
|
185
|
+
const child = n[key];
|
|
186
|
+
|
|
187
|
+
if (Array.isArray(child)) {
|
|
188
|
+
child.forEach(item => {
|
|
189
|
+
if (item && typeof item === "object") {
|
|
190
|
+
analyzeNode(item, visited);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
} else if (child && typeof child === "object") {
|
|
194
|
+
analyzeNode(child, visited);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Analyze the method body
|
|
200
|
+
if (node.body) {
|
|
201
|
+
analyzeNode(node.body);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
hasSessionValidation,
|
|
206
|
+
hasReAuthentication,
|
|
207
|
+
hasSecondaryVerification,
|
|
208
|
+
hasHalfOpenSessionCheck
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function checkMethodDeclaration(node) {
|
|
213
|
+
if (!node.key || !node.key.name) return;
|
|
214
|
+
|
|
215
|
+
const methodName = node.key.name;
|
|
216
|
+
|
|
217
|
+
if (isSensitiveOperation(methodName)) {
|
|
218
|
+
const analysis = checkSensitiveOperationMethodBody(node, methodName);
|
|
219
|
+
|
|
220
|
+
// Report if no session validation found
|
|
221
|
+
if (!analysis.hasSessionValidation && !analysis.hasReAuthentication) {
|
|
222
|
+
context.report({
|
|
223
|
+
node: node.key,
|
|
224
|
+
messageId: "missingSensitiveOperationProtection",
|
|
225
|
+
data: { method: methodName }
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
// Report if only partial validation found
|
|
229
|
+
else if (analysis.hasSessionValidation && !analysis.hasReAuthentication && !analysis.hasSecondaryVerification) {
|
|
230
|
+
context.report({
|
|
231
|
+
node: node.key,
|
|
232
|
+
messageId: "incompleteSessionValidation",
|
|
233
|
+
data: { method: methodName }
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function checkFunctionDeclaration(node) {
|
|
240
|
+
if (!node.id || !node.id.name) return;
|
|
241
|
+
|
|
242
|
+
const functionName = node.id.name;
|
|
243
|
+
|
|
244
|
+
if (isSensitiveOperation(functionName)) {
|
|
245
|
+
const analysis = checkSensitiveOperationMethodBody(node, functionName);
|
|
246
|
+
|
|
247
|
+
if (!analysis.hasSessionValidation && !analysis.hasReAuthentication) {
|
|
248
|
+
context.report({
|
|
249
|
+
node: node.id,
|
|
250
|
+
messageId: "missingSensitiveOperationProtection",
|
|
251
|
+
data: { method: functionName }
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
else if (analysis.hasSessionValidation && !analysis.hasReAuthentication && !analysis.hasSecondaryVerification) {
|
|
255
|
+
context.report({
|
|
256
|
+
node: node.id,
|
|
257
|
+
messageId: "incompleteSessionValidation",
|
|
258
|
+
data: { method: functionName }
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function checkArrowFunction(node) {
|
|
265
|
+
// Check if arrow function is assigned to a variable with sensitive name
|
|
266
|
+
const parent = node.parent;
|
|
267
|
+
if (parent && parent.type === "VariableDeclarator" && parent.id && parent.id.name) {
|
|
268
|
+
const functionName = parent.id.name;
|
|
269
|
+
|
|
270
|
+
if (isSensitiveOperation(functionName)) {
|
|
271
|
+
const analysis = checkSensitiveOperationMethodBody(node, functionName);
|
|
272
|
+
|
|
273
|
+
if (!analysis.hasSessionValidation && !analysis.hasReAuthentication) {
|
|
274
|
+
context.report({
|
|
275
|
+
node: parent.id,
|
|
276
|
+
messageId: "missingSensitiveOperationProtection",
|
|
277
|
+
data: { method: functionName }
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
MethodDefinition: checkMethodDeclaration,
|
|
286
|
+
Property: checkMethodDeclaration, // For object method properties
|
|
287
|
+
FunctionDeclaration: checkFunctionDeclaration,
|
|
288
|
+
ArrowFunctionExpression: checkArrowFunction,
|
|
289
|
+
FunctionExpression: checkArrowFunction
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* S045 – Anti Automation Controls
|
|
5
|
+
* OWASP ASVS 2.2.1
|
|
6
|
+
* Ensure that anti-automation controls are in place to mitigate brute force and automated attacks.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
meta: {
|
|
11
|
+
type: "problem",
|
|
12
|
+
docs: {
|
|
13
|
+
description:
|
|
14
|
+
"Ensure anti-automation controls are in place (rate limiting, CAPTCHA, account lockout, etc.).",
|
|
15
|
+
recommended: true,
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
messages: {
|
|
19
|
+
noAntiAutomation:
|
|
20
|
+
"Missing or ineffective anti-automation controls. Consider rate limiting, CAPTCHA, or account lockout.",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
create(context) {
|
|
25
|
+
return {
|
|
26
|
+
CallExpression(node) {
|
|
27
|
+
// Check for missing rate limiter middleware in Express apps (demo only)
|
|
28
|
+
if (
|
|
29
|
+
node.callee.type === "Identifier" &&
|
|
30
|
+
node.callee.name === "app" &&
|
|
31
|
+
node.parent &&
|
|
32
|
+
node.parent.type === "ExpressionStatement" &&
|
|
33
|
+
node.arguments.length &&
|
|
34
|
+
node.arguments[0].type === "Literal" &&
|
|
35
|
+
node.arguments[0].value === "/login"
|
|
36
|
+
) {
|
|
37
|
+
// This is a simplified check; real implementation should be more robust
|
|
38
|
+
context.report({
|
|
39
|
+
node,
|
|
40
|
+
messageId: "noAntiAutomation",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* S046 – Secure Notification On Auth Change
|
|
5
|
+
* OWASP ASVS 2.2.3
|
|
6
|
+
* Ensure that users are securely notified after authentication changes.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
meta: {
|
|
11
|
+
type: "problem",
|
|
12
|
+
docs: {
|
|
13
|
+
description:
|
|
14
|
+
"Ensure secure notification is sent on authentication changes (password reset, email/phone change, new device login, etc.).",
|
|
15
|
+
recommended: true,
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
messages: {
|
|
19
|
+
missingNotification:
|
|
20
|
+
"Missing secure notification after authentication change. Notify users via secure channel when credentials or auth details change.",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
create(context) {
|
|
25
|
+
return {
|
|
26
|
+
CallExpression(node) {
|
|
27
|
+
// Example: after password change, should call sendNotification/sendMail/sendPushNotification
|
|
28
|
+
if (
|
|
29
|
+
node.callee.type === "Identifier" &&
|
|
30
|
+
(node.callee.name === "resetPassword" ||
|
|
31
|
+
node.callee.name === "changeEmail") &&
|
|
32
|
+
node.parent &&
|
|
33
|
+
node.parent.type === "ExpressionStatement"
|
|
34
|
+
) {
|
|
35
|
+
// This is a simplified check; real implementation should be more robust
|
|
36
|
+
context.report({
|
|
37
|
+
node,
|
|
38
|
+
messageId: "missingNotification",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* S048 – Password Credential Recovery
|
|
5
|
+
* OWASP ASVS 2.4.3
|
|
6
|
+
* Ensure password credential recovery does not reveal the current password in any way.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
meta: {
|
|
11
|
+
type: "problem",
|
|
12
|
+
docs: {
|
|
13
|
+
description:
|
|
14
|
+
"Ensure password recovery does not reveal the current password. Users must set a new password via a secure, one-time token.",
|
|
15
|
+
recommended: true,
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
messages: {
|
|
19
|
+
revealPassword:
|
|
20
|
+
"Never reveal or send the current password during recovery. Always require user to set a new password.",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
create(context) {
|
|
25
|
+
return {
|
|
26
|
+
CallExpression(node) {
|
|
27
|
+
// Example: sending current password in email (should not happen)
|
|
28
|
+
if (
|
|
29
|
+
node.callee.type === "Identifier" &&
|
|
30
|
+
node.callee.name === "sendMail" &&
|
|
31
|
+
node.arguments.length &&
|
|
32
|
+
node.arguments[0].type === "ObjectExpression"
|
|
33
|
+
) {
|
|
34
|
+
const bodyProp = node.arguments[0].properties.find(
|
|
35
|
+
(prop) =>
|
|
36
|
+
prop.key &&
|
|
37
|
+
(prop.key.name === "text" || prop.key.name === "html") &&
|
|
38
|
+
prop.value.type === "Literal" &&
|
|
39
|
+
(prop.value.value
|
|
40
|
+
.toLowerCase()
|
|
41
|
+
.includes("your current password") ||
|
|
42
|
+
prop.value.value.toLowerCase().includes("mật khẩu hiện tại"))
|
|
43
|
+
);
|
|
44
|
+
if (bodyProp) {
|
|
45
|
+
context.report({
|
|
46
|
+
node: bodyProp.value,
|
|
47
|
+
messageId: "revealPassword",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
};
|