@sun-asterisk/sunlint 1.3.28 → 1.3.30
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/origin-rules/common-en.md +14 -0
- package/package.json +1 -1
- package/rules/common/C006_function_naming/analyzer.js +5 -2
- package/rules/security/S041_session_token_invalidation/analyzer.js +1 -1
- package/rules/security/S041_session_token_invalidation/symbol-based-analyzer.js +21 -2
- package/rules/security/S042_require_re_authentication_for_long_lived/symbol-based-analyzer.js +97 -4
|
@@ -81,6 +81,20 @@
|
|
|
81
81
|
- Use verbs that describe specific actions
|
|
82
82
|
- Boolean functions should start with is/has/can/should
|
|
83
83
|
- Avoid generic names that lack context
|
|
84
|
+
- Accepted verbs (reference list):
|
|
85
|
+
- Getters/Queries: `get`, `fetch`, `retrieve`, `find`, `search`, `query`, `load`
|
|
86
|
+
- Setters/Modifiers: `set`, `update`, `modify`, `change`, `edit`, `alter`, `transform`
|
|
87
|
+
- Creation: `create`, `build`, `make`, `generate`, `construct`, `produce`
|
|
88
|
+
- Deletion: `delete`, `remove`, `destroy`, `clean`, `clear`, `reset`
|
|
89
|
+
- Validation: `validate`, `verify`, `check`, `confirm`, `ensure`, `test`, `compare`
|
|
90
|
+
- Computation: `calculate`, `compute`, `parse`, `format`, `convert`
|
|
91
|
+
- Communication: `send`, `receive`, `transmit`, `broadcast`, `emit`, `publish`
|
|
92
|
+
- Collections: `map`, `filter`, `sort`, `group`, `merge`, `split`, `add`, `append`, `insert`
|
|
93
|
+
- State checks: `is`, `has`, `can`, `should`, `will`, `does`
|
|
94
|
+
- UI actions: `show`, `hide`, `display`, `render`, `draw`, `toggle`, `enable`, `disable`
|
|
95
|
+
- Lifecycle: `connect`, `disconnect`, `open`, `close`, `start`, `stop`, `run`, `refresh`
|
|
96
|
+
- Event Handling: `on`, `trigger`, `fire`, `dispatch`, `invoke`, `call`
|
|
97
|
+
- Monitoring: `count`, `measure`, `monitor`, `watch`, `track`, `observe`
|
|
84
98
|
- **Applies to**: All languages
|
|
85
99
|
- **Tools**: PR review, AI Suggestion (Copilot Review)
|
|
86
100
|
- **Principles**: CODE_QUALITY
|
package/package.json
CHANGED
|
@@ -277,7 +277,7 @@ class SmartC006Analyzer {
|
|
|
277
277
|
'delete', 'remove', 'destroy', 'clean', 'clear', 'reset',
|
|
278
278
|
'load', 'save', 'fetch', 'retrieve', 'find', 'search', 'query',
|
|
279
279
|
'validate', 'verify', 'check', 'confirm', 'ensure', 'test',
|
|
280
|
-
'calculate', 'compute', 'parse', 'format', 'convert',
|
|
280
|
+
'calculate', 'compute', 'compare', 'parse', 'format', 'convert',
|
|
281
281
|
'send', 'receive', 'transmit', 'broadcast', 'emit', 'publish',
|
|
282
282
|
'map', 'filter', 'sort', 'group', 'merge', 'split',
|
|
283
283
|
'connect', 'disconnect', 'open', 'close', 'start', 'stop', 'run',
|
|
@@ -428,10 +428,13 @@ class SmartC006Analyzer {
|
|
|
428
428
|
|
|
429
429
|
let reason = `Function '${functionName}' should follow verb-noun naming pattern`;
|
|
430
430
|
let suggestion = this.generateSmartSuggestion(functionName, semanticIntent, architecturalContext);
|
|
431
|
-
|
|
431
|
+
|
|
432
432
|
if (architecturalContext.layer !== 'UNKNOWN') {
|
|
433
433
|
reason += ` (${architecturalContext.layer} layer)`;
|
|
434
434
|
}
|
|
435
|
+
|
|
436
|
+
// Add helpful note about accepted verbs
|
|
437
|
+
reason += `. Accepted verbs: get, set, create, update, delete, validate, check, compare, etc. See: https://coding-standards.sun-asterisk.vn/rules/rule/C006/`;
|
|
435
438
|
|
|
436
439
|
return {
|
|
437
440
|
isViolation: true,
|
|
@@ -29,7 +29,7 @@ class S041Analyzer {
|
|
|
29
29
|
// Configuration
|
|
30
30
|
this.config = {
|
|
31
31
|
useSymbolBased: true, // Primary approach
|
|
32
|
-
fallbackToRegex:
|
|
32
|
+
fallbackToRegex: false, // Secondary approach
|
|
33
33
|
regexBasedOnly: false, // Can be set to true for pure mode
|
|
34
34
|
};
|
|
35
35
|
|
|
@@ -11,6 +11,17 @@ class S041SymbolBasedAnalyzer {
|
|
|
11
11
|
this.ruleId = "S041";
|
|
12
12
|
this.category = "security";
|
|
13
13
|
|
|
14
|
+
this.skipPatterns = [
|
|
15
|
+
/\/node_modules\//,
|
|
16
|
+
/\/tests?\//,
|
|
17
|
+
/\/dist\//,
|
|
18
|
+
/\/build\//,
|
|
19
|
+
/\.spec\.ts$/,
|
|
20
|
+
/\.spec\.tsx$/,
|
|
21
|
+
/\.test\.ts$/,
|
|
22
|
+
/\.test\.tsx$/,
|
|
23
|
+
];
|
|
24
|
+
|
|
14
25
|
// Session management methods that should invalidate tokens
|
|
15
26
|
this.sessionMethods = [
|
|
16
27
|
"logout",
|
|
@@ -106,7 +117,7 @@ class S041SymbolBasedAnalyzer {
|
|
|
106
117
|
}
|
|
107
118
|
}
|
|
108
119
|
|
|
109
|
-
async analyze(filePath) {
|
|
120
|
+
async analyze(sourceFile, filePath) {
|
|
110
121
|
if (this.verbose) {
|
|
111
122
|
console.log(
|
|
112
123
|
`🔍 [${this.ruleId}] Symbol: Starting analysis for ${filePath}`
|
|
@@ -123,7 +134,6 @@ class S041SymbolBasedAnalyzer {
|
|
|
123
134
|
}
|
|
124
135
|
|
|
125
136
|
try {
|
|
126
|
-
const sourceFile = this.semanticEngine.getSourceFile(filePath);
|
|
127
137
|
if (!sourceFile) {
|
|
128
138
|
if (this.verbose) {
|
|
129
139
|
console.log(
|
|
@@ -179,6 +189,11 @@ class S041SymbolBasedAnalyzer {
|
|
|
179
189
|
console.log(`🔍 [${this.ruleId}] Symbol: Starting symbol-based analysis`);
|
|
180
190
|
}
|
|
181
191
|
|
|
192
|
+
if (this.shouldIgnoreFile(filePath)) {
|
|
193
|
+
if (verbose) console.log(`[${this.ruleId}] Ignoring ${filePath}`);
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
|
|
182
197
|
const callExpressions = sourceFile.getDescendantsOfKind
|
|
183
198
|
? sourceFile.getDescendantsOfKind(
|
|
184
199
|
require("typescript").SyntaxKind.CallExpression
|
|
@@ -669,6 +684,10 @@ class S041SymbolBasedAnalyzer {
|
|
|
669
684
|
return null;
|
|
670
685
|
}
|
|
671
686
|
}
|
|
687
|
+
|
|
688
|
+
shouldIgnoreFile(filePath) {
|
|
689
|
+
return this.skipPatterns.some((pattern) => pattern.test(filePath));
|
|
690
|
+
}
|
|
672
691
|
}
|
|
673
692
|
|
|
674
693
|
module.exports = S041SymbolBasedAnalyzer;
|
package/rules/security/S042_require_re_authentication_for_long_lived/symbol-based-analyzer.js
CHANGED
|
@@ -25,7 +25,9 @@ class S042SymbolBasedAnalyzer {
|
|
|
25
25
|
/\/dist\//,
|
|
26
26
|
/\/build\//,
|
|
27
27
|
/\.spec\.ts$/,
|
|
28
|
-
/\.
|
|
28
|
+
/\.spec\.tsx$/,
|
|
29
|
+
/\.test\.ts$/,
|
|
30
|
+
/\.test\.tsx$/,
|
|
29
31
|
];
|
|
30
32
|
|
|
31
33
|
// Sensitive action patterns
|
|
@@ -679,13 +681,16 @@ class S042SymbolBasedAnalyzer {
|
|
|
679
681
|
const hasIdleTimeout = this.hasIdleTimeoutLogic(sourceFile);
|
|
680
682
|
|
|
681
683
|
if (hasSessionConfig && !hasIdleTimeout) {
|
|
684
|
+
// Try to find the actual session configuration location
|
|
685
|
+
const sessionConfigLocation = this.findSessionConfigLocation(sourceFile);
|
|
686
|
+
|
|
682
687
|
violations.push({
|
|
683
688
|
ruleId: this.ruleId,
|
|
684
689
|
ruleName: this.ruleName,
|
|
685
690
|
severity: 'medium',
|
|
686
691
|
message: `Session management detected but no idle timeout implementation found.`,
|
|
687
|
-
line:
|
|
688
|
-
column:
|
|
692
|
+
line: sessionConfigLocation.line,
|
|
693
|
+
column: sessionConfigLocation.column,
|
|
689
694
|
filePath: sourceFile.getFilePath(),
|
|
690
695
|
type: 'MISSING_IDLE_TIMEOUT_LOGIC',
|
|
691
696
|
details: `Implement idle timeout to automatically expire sessions after ${this.formatDuration(this.MAX_IDLE_TIME)} of inactivity.`
|
|
@@ -695,6 +700,91 @@ class S042SymbolBasedAnalyzer {
|
|
|
695
700
|
return violations;
|
|
696
701
|
}
|
|
697
702
|
|
|
703
|
+
findSessionConfigLocation(sourceFile) {
|
|
704
|
+
const defaultLocation = { line: 1, column: 1 };
|
|
705
|
+
|
|
706
|
+
try {
|
|
707
|
+
// Look for session-related call expressions
|
|
708
|
+
const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
709
|
+
|
|
710
|
+
for (const callExpr of callExpressions) {
|
|
711
|
+
const expression = callExpr.getExpression();
|
|
712
|
+
const expressionText = expression.getText();
|
|
713
|
+
|
|
714
|
+
// Check for session middleware calls
|
|
715
|
+
if (expressionText.match(/session|express-session|cookie-session/i)) {
|
|
716
|
+
const args = callExpr.getArguments();
|
|
717
|
+
|
|
718
|
+
// If there's a config object argument, use its location
|
|
719
|
+
if (args.length > 0 && args[0].getKind() === SyntaxKind.ObjectLiteralExpression) {
|
|
720
|
+
const configObj = args[0];
|
|
721
|
+
return {
|
|
722
|
+
line: configObj.getStartLineNumber(),
|
|
723
|
+
column: configObj.getStart() - configObj.getStartLinePos() + 1
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Otherwise use the call expression location
|
|
728
|
+
return {
|
|
729
|
+
line: callExpr.getStartLineNumber(),
|
|
730
|
+
column: callExpr.getStart() - callExpr.getStartLinePos() + 1
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Look for variable declarations with session config
|
|
736
|
+
const variableDeclarations = sourceFile.getDescendantsOfKind(SyntaxKind.VariableDeclaration);
|
|
737
|
+
|
|
738
|
+
for (const varDecl of variableDeclarations) {
|
|
739
|
+
const name = varDecl.getName();
|
|
740
|
+
|
|
741
|
+
if (name.match(/sessionConfig|sessionOptions|sessionMiddleware/i)) {
|
|
742
|
+
const initializer = varDecl.getInitializer();
|
|
743
|
+
|
|
744
|
+
if (initializer) {
|
|
745
|
+
return {
|
|
746
|
+
line: initializer.getStartLineNumber(),
|
|
747
|
+
column: initializer.getStart() - initializer.getStartLinePos() + 1
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Look for object literals with session config keys
|
|
754
|
+
const objectLiterals = sourceFile.getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression);
|
|
755
|
+
|
|
756
|
+
for (const objLiteral of objectLiterals) {
|
|
757
|
+
const properties = objLiteral.getProperties();
|
|
758
|
+
let sessionKeyCount = 0;
|
|
759
|
+
|
|
760
|
+
for (const prop of properties) {
|
|
761
|
+
if (prop.getKind() === SyntaxKind.PropertyAssignment) {
|
|
762
|
+
const name = prop.getName();
|
|
763
|
+
if (this.sessionConfigKeys.some(key => name === key)) {
|
|
764
|
+
sessionKeyCount++;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// If we found multiple session config keys, this is likely the config
|
|
770
|
+
if (sessionKeyCount >= 2) {
|
|
771
|
+
return {
|
|
772
|
+
line: objLiteral.getStartLineNumber(),
|
|
773
|
+
column: objLiteral.getStart() - objLiteral.getStartLinePos() + 1
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
} catch (error) {
|
|
779
|
+
// Fall back to default location on error
|
|
780
|
+
if (verbose) {
|
|
781
|
+
console.warn(`[${this.ruleId}] Failed to find session config location:`, error.message);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return defaultLocation;
|
|
786
|
+
}
|
|
787
|
+
|
|
698
788
|
// Helper methods
|
|
699
789
|
|
|
700
790
|
isJWTSignCall(expressionText) {
|
|
@@ -1046,7 +1136,10 @@ class S042SymbolBasedAnalyzer {
|
|
|
1046
1136
|
|
|
1047
1137
|
hasSessionConfiguration(sourceFile) {
|
|
1048
1138
|
const text = sourceFile.getText();
|
|
1049
|
-
|
|
1139
|
+
|
|
1140
|
+
// More specific patterns for server-side session middleware
|
|
1141
|
+
// Avoid matching client-side hooks like useSession()
|
|
1142
|
+
return /express-session|cookie-session|session\.Session|getSessionConfig|sessionConfig|sessionOptions|sessionMiddleware|session\(\{|require\(['"](express-session|cookie-session)['"]\)/i.test(text);
|
|
1050
1143
|
}
|
|
1051
1144
|
|
|
1052
1145
|
hasIdleTimeoutLogic(sourceFile) {
|