camouf 0.1.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/LICENSE +21 -0
- package/README.md +346 -0
- package/dist/cli/commands/analyze.d.ts +8 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli/commands/analyze.js +81 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +104 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/report.d.ts +8 -0
- package/dist/cli/commands/report.d.ts.map +1 -0
- package/dist/cli/commands/report.js +63 -0
- package/dist/cli/commands/report.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +9 -0
- package/dist/cli/commands/validate.d.ts.map +1 -0
- package/dist/cli/commands/validate.js +87 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/commands/watch.d.ts +9 -0
- package/dist/cli/commands/watch.d.ts.map +1 -0
- package/dist/cli/commands/watch.js +93 -0
- package/dist/cli/commands/watch.js.map +1 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +48 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/version.d.ts +16 -0
- package/dist/cli/version.d.ts.map +1 -0
- package/dist/cli/version.js +16 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/core/analyzer/architecture-visualizer.d.ts +25 -0
- package/dist/core/analyzer/architecture-visualizer.d.ts.map +1 -0
- package/dist/core/analyzer/architecture-visualizer.js +333 -0
- package/dist/core/analyzer/architecture-visualizer.js.map +1 -0
- package/dist/core/analyzer/dependency-analyzer.d.ts +49 -0
- package/dist/core/analyzer/dependency-analyzer.d.ts.map +1 -0
- package/dist/core/analyzer/dependency-analyzer.js +242 -0
- package/dist/core/analyzer/dependency-analyzer.js.map +1 -0
- package/dist/core/config/config-schema.d.ts +280 -0
- package/dist/core/config/config-schema.d.ts.map +1 -0
- package/dist/core/config/config-schema.js +199 -0
- package/dist/core/config/config-schema.js.map +1 -0
- package/dist/core/config/configuration-manager.d.ts +82 -0
- package/dist/core/config/configuration-manager.d.ts.map +1 -0
- package/dist/core/config/configuration-manager.js +306 -0
- package/dist/core/config/configuration-manager.js.map +1 -0
- package/dist/core/logger.d.ts +27 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +86 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/parsers/go-parser.d.ts +16 -0
- package/dist/core/parsers/go-parser.d.ts.map +1 -0
- package/dist/core/parsers/go-parser.js +117 -0
- package/dist/core/parsers/go-parser.js.map +1 -0
- package/dist/core/parsers/java-parser.d.ts +22 -0
- package/dist/core/parsers/java-parser.d.ts.map +1 -0
- package/dist/core/parsers/java-parser.js +114 -0
- package/dist/core/parsers/java-parser.js.map +1 -0
- package/dist/core/parsers/javascript-parser.d.ts +14 -0
- package/dist/core/parsers/javascript-parser.d.ts.map +1 -0
- package/dist/core/parsers/javascript-parser.js +19 -0
- package/dist/core/parsers/javascript-parser.js.map +1 -0
- package/dist/core/parsers/parser-registry.d.ts +45 -0
- package/dist/core/parsers/parser-registry.d.ts.map +1 -0
- package/dist/core/parsers/parser-registry.js +121 -0
- package/dist/core/parsers/parser-registry.js.map +1 -0
- package/dist/core/parsers/parser.interface.d.ts +49 -0
- package/dist/core/parsers/parser.interface.d.ts.map +1 -0
- package/dist/core/parsers/parser.interface.js +7 -0
- package/dist/core/parsers/parser.interface.js.map +1 -0
- package/dist/core/parsers/python-parser.d.ts +22 -0
- package/dist/core/parsers/python-parser.d.ts.map +1 -0
- package/dist/core/parsers/python-parser.js +136 -0
- package/dist/core/parsers/python-parser.js.map +1 -0
- package/dist/core/parsers/rust-parser.d.ts +16 -0
- package/dist/core/parsers/rust-parser.d.ts.map +1 -0
- package/dist/core/parsers/rust-parser.js +176 -0
- package/dist/core/parsers/rust-parser.js.map +1 -0
- package/dist/core/parsers/typescript-parser.d.ts +29 -0
- package/dist/core/parsers/typescript-parser.d.ts.map +1 -0
- package/dist/core/parsers/typescript-parser.js +251 -0
- package/dist/core/parsers/typescript-parser.js.map +1 -0
- package/dist/core/reporter/report-generator.d.ts +31 -0
- package/dist/core/reporter/report-generator.d.ts.map +1 -0
- package/dist/core/reporter/report-generator.js +417 -0
- package/dist/core/reporter/report-generator.js.map +1 -0
- package/dist/core/reporter/violation-reporter.d.ts +62 -0
- package/dist/core/reporter/violation-reporter.d.ts.map +1 -0
- package/dist/core/reporter/violation-reporter.js +265 -0
- package/dist/core/reporter/violation-reporter.js.map +1 -0
- package/dist/core/rules/builtin/api-versioning.rule.d.ts +28 -0
- package/dist/core/rules/builtin/api-versioning.rule.d.ts.map +1 -0
- package/dist/core/rules/builtin/api-versioning.rule.js +103 -0
- package/dist/core/rules/builtin/api-versioning.rule.js.map +1 -0
- package/dist/core/rules/builtin/circular-dependencies.rule.d.ts +26 -0
- package/dist/core/rules/builtin/circular-dependencies.rule.d.ts.map +1 -0
- package/dist/core/rules/builtin/circular-dependencies.rule.js +99 -0
- package/dist/core/rules/builtin/circular-dependencies.rule.js.map +1 -0
- package/dist/core/rules/builtin/data-flow-integrity.rule.d.ts +27 -0
- package/dist/core/rules/builtin/data-flow-integrity.rule.d.ts.map +1 -0
- package/dist/core/rules/builtin/data-flow-integrity.rule.js +111 -0
- package/dist/core/rules/builtin/data-flow-integrity.rule.js.map +1 -0
- package/dist/core/rules/builtin/ddd-boundaries.rule.d.ts +31 -0
- package/dist/core/rules/builtin/ddd-boundaries.rule.d.ts.map +1 -0
- package/dist/core/rules/builtin/ddd-boundaries.rule.js +141 -0
- package/dist/core/rules/builtin/ddd-boundaries.rule.js.map +1 -0
- package/dist/core/rules/builtin/distributed-transactions.rule.d.ts +27 -0
- package/dist/core/rules/builtin/distributed-transactions.rule.d.ts.map +1 -0
- package/dist/core/rules/builtin/distributed-transactions.rule.js +134 -0
- package/dist/core/rules/builtin/distributed-transactions.rule.js.map +1 -0
- package/dist/core/rules/builtin/index.d.ts +16 -0
- package/dist/core/rules/builtin/index.d.ts.map +1 -0
- package/dist/core/rules/builtin/index.js +16 -0
- package/dist/core/rules/builtin/index.js.map +1 -0
- package/dist/core/rules/builtin/layer-dependencies.rule.d.ts +30 -0
- package/dist/core/rules/builtin/layer-dependencies.rule.d.ts.map +1 -0
- package/dist/core/rules/builtin/layer-dependencies.rule.js +102 -0
- package/dist/core/rules/builtin/layer-dependencies.rule.js.map +1 -0
- package/dist/core/rules/builtin/performance-antipatterns.rule.d.ts +29 -0
- package/dist/core/rules/builtin/performance-antipatterns.rule.d.ts.map +1 -0
- package/dist/core/rules/builtin/performance-antipatterns.rule.js +148 -0
- package/dist/core/rules/builtin/performance-antipatterns.rule.js.map +1 -0
- package/dist/core/rules/builtin/resilience-patterns.rule.d.ts +29 -0
- package/dist/core/rules/builtin/resilience-patterns.rule.d.ts.map +1 -0
- package/dist/core/rules/builtin/resilience-patterns.rule.js +123 -0
- package/dist/core/rules/builtin/resilience-patterns.rule.js.map +1 -0
- package/dist/core/rules/builtin/security-context.rule.d.ts +32 -0
- package/dist/core/rules/builtin/security-context.rule.d.ts.map +1 -0
- package/dist/core/rules/builtin/security-context.rule.js +145 -0
- package/dist/core/rules/builtin/security-context.rule.js.map +1 -0
- package/dist/core/rules/builtin/type-safety.rule.d.ts +32 -0
- package/dist/core/rules/builtin/type-safety.rule.d.ts.map +1 -0
- package/dist/core/rules/builtin/type-safety.rule.js +175 -0
- package/dist/core/rules/builtin/type-safety.rule.js.map +1 -0
- package/dist/core/rules/rule-engine.d.ts +72 -0
- package/dist/core/rules/rule-engine.d.ts.map +1 -0
- package/dist/core/rules/rule-engine.js +225 -0
- package/dist/core/rules/rule-engine.js.map +1 -0
- package/dist/core/rules/rule.interface.d.ts +169 -0
- package/dist/core/rules/rule.interface.d.ts.map +1 -0
- package/dist/core/rules/rule.interface.js +38 -0
- package/dist/core/rules/rule.interface.js.map +1 -0
- package/dist/core/scanner/project-detector.d.ts +51 -0
- package/dist/core/scanner/project-detector.d.ts.map +1 -0
- package/dist/core/scanner/project-detector.js +310 -0
- package/dist/core/scanner/project-detector.js.map +1 -0
- package/dist/core/scanner/project-scanner.d.ts +101 -0
- package/dist/core/scanner/project-scanner.d.ts.map +1 -0
- package/dist/core/scanner/project-scanner.js +321 -0
- package/dist/core/scanner/project-scanner.js.map +1 -0
- package/dist/core/watcher/file-watcher.d.ts +48 -0
- package/dist/core/watcher/file-watcher.d.ts.map +1 -0
- package/dist/core/watcher/file-watcher.js +124 -0
- package/dist/core/watcher/file-watcher.js.map +1 -0
- package/dist/types/config.types.d.ts +163 -0
- package/dist/types/config.types.d.ts.map +1 -0
- package/dist/types/config.types.js +36 -0
- package/dist/types/config.types.js.map +1 -0
- package/dist/types/core.types.d.ts +247 -0
- package/dist/types/core.types.d.ts.map +1 -0
- package/dist/types/core.types.js +7 -0
- package/dist/types/core.types.js.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer Dependencies Rule
|
|
3
|
+
*
|
|
4
|
+
* Validates architectural layer dependencies (e.g., presentation -> business -> data).
|
|
5
|
+
*/
|
|
6
|
+
export class LayerDependenciesRule {
|
|
7
|
+
id = 'layer-dependencies';
|
|
8
|
+
name = 'Layer Dependencies';
|
|
9
|
+
description = 'Validates architectural layer dependencies';
|
|
10
|
+
severity = 'error';
|
|
11
|
+
tags = ['architecture', 'layers', 'dependencies'];
|
|
12
|
+
config = {
|
|
13
|
+
enabled: true,
|
|
14
|
+
severity: 'error',
|
|
15
|
+
strictMode: true,
|
|
16
|
+
layers: [
|
|
17
|
+
{
|
|
18
|
+
name: 'presentation',
|
|
19
|
+
patterns: ['controller', 'handler', 'view', 'component', 'page'],
|
|
20
|
+
allowedDependencies: ['application', 'domain'],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'application',
|
|
24
|
+
patterns: ['service', 'usecase', 'application'],
|
|
25
|
+
allowedDependencies: ['domain', 'infrastructure'],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'domain',
|
|
29
|
+
patterns: ['entity', 'domain', 'model', 'aggregate', 'value-object'],
|
|
30
|
+
allowedDependencies: [],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'infrastructure',
|
|
34
|
+
patterns: ['repository', 'adapter', 'infrastructure', 'persistence'],
|
|
35
|
+
allowedDependencies: ['domain'],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
configure(options) {
|
|
40
|
+
this.config = { ...this.config, ...options };
|
|
41
|
+
}
|
|
42
|
+
async check(context) {
|
|
43
|
+
const violations = [];
|
|
44
|
+
for (const nodeId of context.graph.nodes()) {
|
|
45
|
+
const node = context.getNodeData(nodeId);
|
|
46
|
+
if (!node)
|
|
47
|
+
continue;
|
|
48
|
+
const filePath = node.data.relativePath;
|
|
49
|
+
const sourceLayer = this.detectLayer(filePath);
|
|
50
|
+
if (!sourceLayer)
|
|
51
|
+
continue;
|
|
52
|
+
const successors = context.graph.successors(nodeId);
|
|
53
|
+
if (!successors)
|
|
54
|
+
continue;
|
|
55
|
+
for (const successor of successors) {
|
|
56
|
+
const targetNode = context.getNodeData(successor);
|
|
57
|
+
if (!targetNode)
|
|
58
|
+
continue;
|
|
59
|
+
const targetPath = targetNode.data.relativePath;
|
|
60
|
+
const targetLayer = this.detectLayer(targetPath);
|
|
61
|
+
if (!targetLayer)
|
|
62
|
+
continue;
|
|
63
|
+
if (sourceLayer.name === targetLayer.name)
|
|
64
|
+
continue;
|
|
65
|
+
if (!this.isDependencyAllowed(sourceLayer, targetLayer.name)) {
|
|
66
|
+
violations.push(this.createViolation(filePath, `Invalid layer dependency: ${sourceLayer.name} -> ${targetLayer.name}`, 1, `${sourceLayer.name} layer can only depend on: ${sourceLayer.allowedDependencies.join(', ') || 'none'}`));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { violations };
|
|
71
|
+
}
|
|
72
|
+
detectLayer(filePath) {
|
|
73
|
+
const normalizedPath = filePath.toLowerCase().replace(/\\/g, '/');
|
|
74
|
+
for (const layer of this.config.layers || []) {
|
|
75
|
+
for (const pattern of layer.patterns) {
|
|
76
|
+
if (normalizedPath.includes(pattern)) {
|
|
77
|
+
return layer;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
isDependencyAllowed(sourceLayer, targetLayerName) {
|
|
84
|
+
if (!this.config.strictMode) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
return sourceLayer.allowedDependencies.includes(targetLayerName);
|
|
88
|
+
}
|
|
89
|
+
createViolation(file, message, line, suggestion) {
|
|
90
|
+
return {
|
|
91
|
+
id: `${this.id}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
92
|
+
ruleId: this.id,
|
|
93
|
+
ruleName: this.name,
|
|
94
|
+
severity: 'error',
|
|
95
|
+
message,
|
|
96
|
+
file,
|
|
97
|
+
line,
|
|
98
|
+
suggestion,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=layer-dependencies.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layer-dependencies.rule.js","sourceRoot":"","sources":["../../../../src/core/rules/builtin/layer-dependencies.rule.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance Anti-patterns Rule
|
|
3
|
+
*
|
|
4
|
+
* Detects common performance anti-patterns in code.
|
|
5
|
+
*/
|
|
6
|
+
import { IRule, RuleContext, RuleConfig, RuleResult } from '../rule.interface.js';
|
|
7
|
+
interface PerformanceConfig extends RuleConfig {
|
|
8
|
+
checkN1Queries?: boolean;
|
|
9
|
+
checkUnboundedLoops?: boolean;
|
|
10
|
+
checkMemoryLeaks?: boolean;
|
|
11
|
+
maxLoopDepth?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare class PerformanceAntipatternsRule implements IRule {
|
|
14
|
+
readonly id = "performance-antipatterns";
|
|
15
|
+
readonly name = "Performance Anti-patterns";
|
|
16
|
+
readonly description = "Detects common performance anti-patterns in code";
|
|
17
|
+
readonly severity: "warning";
|
|
18
|
+
readonly tags: string[];
|
|
19
|
+
private config;
|
|
20
|
+
configure(options: Partial<PerformanceConfig>): void;
|
|
21
|
+
check(context: RuleContext): Promise<RuleResult>;
|
|
22
|
+
private checkN1QueryPattern;
|
|
23
|
+
private checkUnboundedLoops;
|
|
24
|
+
private checkMemoryLeakPatterns;
|
|
25
|
+
private checkSyncOperations;
|
|
26
|
+
private createViolation;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=performance-antipatterns.rule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"performance-antipatterns.rule.d.ts","sourceRoot":"","sources":["../../../../src/core/rules/builtin/performance-antipatterns.rule.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGlF,UAAU,iBAAkB,SAAQ,UAAU;IAC5C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,2BAA4B,YAAW,KAAK;IACvD,QAAQ,CAAC,EAAE,8BAA8B;IACzC,QAAQ,CAAC,IAAI,+BAA+B;IAC5C,QAAQ,CAAC,WAAW,sDAAsD;IAC1E,QAAQ,CAAC,QAAQ,EAAG,SAAS,CAAU;IACvC,QAAQ,CAAC,IAAI,WAAqD;IAElE,OAAO,CAAC,MAAM,CAOZ;IAEF,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI;IAI9C,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IA0BtD,OAAO,CAAC,mBAAmB;IAmC3B,OAAO,CAAC,mBAAmB;IAoC3B,OAAO,CAAC,uBAAuB;IA+C/B,OAAO,CAAC,mBAAmB;IAuB3B,OAAO,CAAC,eAAe;CAYxB"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance Anti-patterns Rule
|
|
3
|
+
*
|
|
4
|
+
* Detects common performance anti-patterns in code.
|
|
5
|
+
*/
|
|
6
|
+
export class PerformanceAntipatternsRule {
|
|
7
|
+
id = 'performance-antipatterns';
|
|
8
|
+
name = 'Performance Anti-patterns';
|
|
9
|
+
description = 'Detects common performance anti-patterns in code';
|
|
10
|
+
severity = 'warning';
|
|
11
|
+
tags = ['performance', 'optimization', 'best-practices'];
|
|
12
|
+
config = {
|
|
13
|
+
enabled: true,
|
|
14
|
+
severity: 'warning',
|
|
15
|
+
checkN1Queries: true,
|
|
16
|
+
checkUnboundedLoops: true,
|
|
17
|
+
checkMemoryLeaks: true,
|
|
18
|
+
maxLoopDepth: 3,
|
|
19
|
+
};
|
|
20
|
+
configure(options) {
|
|
21
|
+
this.config = { ...this.config, ...options };
|
|
22
|
+
}
|
|
23
|
+
async check(context) {
|
|
24
|
+
const violations = [];
|
|
25
|
+
for (const nodeId of context.graph.nodes()) {
|
|
26
|
+
const node = context.getNodeData(nodeId);
|
|
27
|
+
if (!node)
|
|
28
|
+
continue;
|
|
29
|
+
const filePath = node.data.relativePath;
|
|
30
|
+
const content = context.fileContents?.get(filePath);
|
|
31
|
+
if (!content)
|
|
32
|
+
continue;
|
|
33
|
+
if (this.config.checkN1Queries) {
|
|
34
|
+
this.checkN1QueryPattern(filePath, content, violations);
|
|
35
|
+
}
|
|
36
|
+
if (this.config.checkUnboundedLoops) {
|
|
37
|
+
this.checkUnboundedLoops(filePath, content, violations);
|
|
38
|
+
}
|
|
39
|
+
if (this.config.checkMemoryLeaks) {
|
|
40
|
+
this.checkMemoryLeakPatterns(filePath, content, violations);
|
|
41
|
+
}
|
|
42
|
+
this.checkSyncOperations(filePath, content, violations);
|
|
43
|
+
}
|
|
44
|
+
return { violations };
|
|
45
|
+
}
|
|
46
|
+
checkN1QueryPattern(filePath, content, violations) {
|
|
47
|
+
const lines = content.split('\n');
|
|
48
|
+
let inLoop = false;
|
|
49
|
+
let loopStartLine = 0;
|
|
50
|
+
for (let i = 0; i < lines.length; i++) {
|
|
51
|
+
const line = lines[i];
|
|
52
|
+
// Detect loop start
|
|
53
|
+
if (/\b(for|while|forEach|map|reduce|filter)\s*[\(\[]/.test(line)) {
|
|
54
|
+
inLoop = true;
|
|
55
|
+
loopStartLine = i + 1;
|
|
56
|
+
}
|
|
57
|
+
// Detect query/fetch inside loop
|
|
58
|
+
if (inLoop) {
|
|
59
|
+
if (/\.find\(|\.findOne\(|\.query\(|\.execute\(|await\s+fetch\(|axios\.\w+\(/.test(line)) {
|
|
60
|
+
violations.push(this.createViolation(filePath, 'Potential N+1 query pattern detected', i + 1, 'Consider using batch queries, eager loading, or data loaders'));
|
|
61
|
+
}
|
|
62
|
+
// Detect loop end (simplified)
|
|
63
|
+
const openBraces = (line.match(/{/g) || []).length;
|
|
64
|
+
const closeBraces = (line.match(/}/g) || []).length;
|
|
65
|
+
if (closeBraces > openBraces) {
|
|
66
|
+
inLoop = false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
checkUnboundedLoops(filePath, content, violations) {
|
|
72
|
+
const lines = content.split('\n');
|
|
73
|
+
let currentDepth = 0;
|
|
74
|
+
const maxDepth = this.config.maxLoopDepth || 3;
|
|
75
|
+
for (let i = 0; i < lines.length; i++) {
|
|
76
|
+
const line = lines[i];
|
|
77
|
+
if (/\b(for|while|do)\s*[\(\{]/.test(line)) {
|
|
78
|
+
currentDepth++;
|
|
79
|
+
if (currentDepth > maxDepth) {
|
|
80
|
+
violations.push(this.createViolation(filePath, `Deep nested loops detected (depth: ${currentDepth})`, i + 1, 'Consider refactoring to reduce loop nesting or use different algorithms'));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Check for while(true) or infinite loops
|
|
84
|
+
if (/while\s*\(\s*true\s*\)|for\s*\(\s*;\s*;\s*\)/.test(line)) {
|
|
85
|
+
violations.push(this.createViolation(filePath, 'Potentially infinite loop detected', i + 1, 'Ensure proper exit conditions exist'));
|
|
86
|
+
}
|
|
87
|
+
if (line.includes('}')) {
|
|
88
|
+
currentDepth = Math.max(0, currentDepth - 1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
checkMemoryLeakPatterns(filePath, content, violations) {
|
|
93
|
+
const lines = content.split('\n');
|
|
94
|
+
for (let i = 0; i < lines.length; i++) {
|
|
95
|
+
const line = lines[i];
|
|
96
|
+
// Check for event listeners without cleanup
|
|
97
|
+
if (/addEventListener\s*\(|\.on\s*\(/.test(line)) {
|
|
98
|
+
// Look for corresponding removeEventListener in the file
|
|
99
|
+
if (!content.includes('removeEventListener') && !content.includes('.off(')) {
|
|
100
|
+
violations.push(this.createViolation(filePath, 'Event listener without apparent cleanup', i + 1, 'Ensure event listeners are removed in cleanup/unmount'));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Check for setInterval without clearInterval
|
|
104
|
+
if (/setInterval\s*\(/.test(line)) {
|
|
105
|
+
if (!content.includes('clearInterval')) {
|
|
106
|
+
violations.push(this.createViolation(filePath, 'setInterval without clearInterval', i + 1, 'Store interval ID and clear it during cleanup'));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Check for large array accumulation in loops
|
|
110
|
+
if (/\.push\s*\(/.test(line) && /while|for/.test(content.substring(Math.max(0, content.indexOf(line) - 200), content.indexOf(line)))) {
|
|
111
|
+
// Check if there's no size limit
|
|
112
|
+
if (!/\.length\s*[<>]|\.slice\(|\.splice\(/.test(content)) {
|
|
113
|
+
violations.push(this.createViolation(filePath, 'Array accumulation in loop without apparent size limit', i + 1, 'Consider adding size limits or using streaming'));
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
checkSyncOperations(filePath, content, violations) {
|
|
120
|
+
const lines = content.split('\n');
|
|
121
|
+
const syncPatterns = [
|
|
122
|
+
{ pattern: /readFileSync\s*\(/, name: 'readFileSync' },
|
|
123
|
+
{ pattern: /writeFileSync\s*\(/, name: 'writeFileSync' },
|
|
124
|
+
{ pattern: /execSync\s*\(/, name: 'execSync' },
|
|
125
|
+
{ pattern: /spawnSync\s*\(/, name: 'spawnSync' },
|
|
126
|
+
];
|
|
127
|
+
for (let i = 0; i < lines.length; i++) {
|
|
128
|
+
for (const { pattern, name } of syncPatterns) {
|
|
129
|
+
if (pattern.test(lines[i])) {
|
|
130
|
+
violations.push(this.createViolation(filePath, `Synchronous operation '${name}' may block event loop`, i + 1, `Consider using async version or worker threads`));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
createViolation(file, message, line, suggestion) {
|
|
136
|
+
return {
|
|
137
|
+
id: `${this.id}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
138
|
+
ruleId: this.id,
|
|
139
|
+
ruleName: this.name,
|
|
140
|
+
severity: 'warning',
|
|
141
|
+
message,
|
|
142
|
+
file,
|
|
143
|
+
line,
|
|
144
|
+
suggestion,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=performance-antipatterns.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"performance-antipatterns.rule.js","sourceRoot":"","sources":["../../../../src/core/rules/builtin/performance-antipatterns.rule.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resilience Patterns Rule
|
|
3
|
+
*
|
|
4
|
+
* Validates resilience patterns like circuit breaker, retry, and timeout.
|
|
5
|
+
*/
|
|
6
|
+
import { IRule, RuleContext, RuleConfig, RuleResult } from '../rule.interface.js';
|
|
7
|
+
interface ResilienceConfig extends RuleConfig {
|
|
8
|
+
requireCircuitBreaker?: boolean;
|
|
9
|
+
requireRetry?: boolean;
|
|
10
|
+
requireTimeout?: boolean;
|
|
11
|
+
externalCallPatterns?: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare class ResiliencePatternsRule implements IRule {
|
|
14
|
+
readonly id = "resilience-patterns";
|
|
15
|
+
readonly name = "Resilience Patterns";
|
|
16
|
+
readonly description = "Validates resilience patterns like circuit breaker, retry, and timeout";
|
|
17
|
+
readonly severity: "warning";
|
|
18
|
+
readonly tags: string[];
|
|
19
|
+
private config;
|
|
20
|
+
configure(options: Partial<ResilienceConfig>): void;
|
|
21
|
+
check(context: RuleContext): Promise<RuleResult>;
|
|
22
|
+
private checkExternalCalls;
|
|
23
|
+
private checkResiliencePatterns;
|
|
24
|
+
private checkErrorHandling;
|
|
25
|
+
private hasTimeout;
|
|
26
|
+
private createViolation;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=resilience-patterns.rule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resilience-patterns.rule.d.ts","sourceRoot":"","sources":["../../../../src/core/rules/builtin/resilience-patterns.rule.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAIlF,UAAU,gBAAiB,SAAQ,UAAU;IAC3C,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAED,qBAAa,sBAAuB,YAAW,KAAK;IAClD,QAAQ,CAAC,EAAE,yBAAyB;IACpC,QAAQ,CAAC,IAAI,yBAAyB;IACtC,QAAQ,CAAC,WAAW,4EAA4E;IAChG,QAAQ,CAAC,QAAQ,EAAG,SAAS,CAAU;IACvC,QAAQ,CAAC,IAAI,WAA6C;IAE1D,OAAO,CAAC,MAAM,CAOZ;IAEF,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI;IAI7C,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAmBtD,OAAO,CAAC,kBAAkB;IA4B1B,OAAO,CAAC,uBAAuB;IA0C/B,OAAO,CAAC,kBAAkB;IAgC1B,OAAO,CAAC,UAAU;IAYlB,OAAO,CAAC,eAAe;CAYxB"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resilience Patterns Rule
|
|
3
|
+
*
|
|
4
|
+
* Validates resilience patterns like circuit breaker, retry, and timeout.
|
|
5
|
+
*/
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
export class ResiliencePatternsRule {
|
|
8
|
+
id = 'resilience-patterns';
|
|
9
|
+
name = 'Resilience Patterns';
|
|
10
|
+
description = 'Validates resilience patterns like circuit breaker, retry, and timeout';
|
|
11
|
+
severity = 'warning';
|
|
12
|
+
tags = ['resilience', 'reliability', 'patterns'];
|
|
13
|
+
config = {
|
|
14
|
+
enabled: true,
|
|
15
|
+
severity: 'warning',
|
|
16
|
+
requireCircuitBreaker: true,
|
|
17
|
+
requireRetry: true,
|
|
18
|
+
requireTimeout: true,
|
|
19
|
+
externalCallPatterns: ['fetch', 'axios', 'http', 'request', 'got'],
|
|
20
|
+
};
|
|
21
|
+
configure(options) {
|
|
22
|
+
this.config = { ...this.config, ...options };
|
|
23
|
+
}
|
|
24
|
+
async check(context) {
|
|
25
|
+
const violations = [];
|
|
26
|
+
for (const nodeId of context.graph.nodes()) {
|
|
27
|
+
const node = context.getNodeData(nodeId);
|
|
28
|
+
if (!node)
|
|
29
|
+
continue;
|
|
30
|
+
const filePath = node.data.relativePath;
|
|
31
|
+
const content = context.fileContents?.get(filePath);
|
|
32
|
+
if (!content)
|
|
33
|
+
continue;
|
|
34
|
+
this.checkExternalCalls(filePath, content, violations);
|
|
35
|
+
this.checkResiliencePatterns(filePath, content, violations);
|
|
36
|
+
this.checkErrorHandling(filePath, content, violations);
|
|
37
|
+
}
|
|
38
|
+
return { violations };
|
|
39
|
+
}
|
|
40
|
+
checkExternalCalls(filePath, content, violations) {
|
|
41
|
+
const lines = content.split('\n');
|
|
42
|
+
const callPatterns = this.config.externalCallPatterns || [];
|
|
43
|
+
for (let i = 0; i < lines.length; i++) {
|
|
44
|
+
const line = lines[i];
|
|
45
|
+
for (const pattern of callPatterns) {
|
|
46
|
+
const regex = new RegExp(`\\b${pattern}\\s*[\\(.]`, 'i');
|
|
47
|
+
if (regex.test(line)) {
|
|
48
|
+
// Check for timeout in surrounding context
|
|
49
|
+
const contextStart = Math.max(0, i - 10);
|
|
50
|
+
const contextEnd = Math.min(lines.length, i + 10);
|
|
51
|
+
const context = lines.slice(contextStart, contextEnd).join('\n');
|
|
52
|
+
if (this.config.requireTimeout && !this.hasTimeout(context)) {
|
|
53
|
+
violations.push(this.createViolation(filePath, `External call without timeout configuration`, i + 1, 'Add timeout to external service calls'));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
checkResiliencePatterns(filePath, content, violations) {
|
|
60
|
+
const fileName = path.basename(filePath).toLowerCase();
|
|
61
|
+
// Skip if not a service or client file
|
|
62
|
+
if (!fileName.includes('service') && !fileName.includes('client') && !fileName.includes('adapter')) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const hasExternalCalls = this.config.externalCallPatterns?.some(p => content.toLowerCase().includes(p));
|
|
66
|
+
if (!hasExternalCalls)
|
|
67
|
+
return;
|
|
68
|
+
const hasCircuitBreaker = content.includes('circuitBreaker') ||
|
|
69
|
+
content.includes('CircuitBreaker') ||
|
|
70
|
+
content.includes('@CircuitBreaker');
|
|
71
|
+
const hasRetry = content.includes('retry') ||
|
|
72
|
+
content.includes('Retry') ||
|
|
73
|
+
content.includes('@Retryable') ||
|
|
74
|
+
content.includes('maxRetries');
|
|
75
|
+
if (this.config.requireCircuitBreaker && !hasCircuitBreaker) {
|
|
76
|
+
violations.push(this.createViolation(filePath, 'Service with external calls missing circuit breaker', 1, 'Implement circuit breaker pattern for fault tolerance'));
|
|
77
|
+
}
|
|
78
|
+
if (this.config.requireRetry && !hasRetry) {
|
|
79
|
+
violations.push(this.createViolation(filePath, 'Service with external calls missing retry logic', 1, 'Implement retry with exponential backoff for transient failures'));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
checkErrorHandling(filePath, content, violations) {
|
|
83
|
+
const lines = content.split('\n');
|
|
84
|
+
for (let i = 0; i < lines.length; i++) {
|
|
85
|
+
const line = lines[i];
|
|
86
|
+
// Check for empty catch blocks
|
|
87
|
+
if (/catch\s*\([^)]*\)\s*\{\s*\}/.test(line) ||
|
|
88
|
+
(/catch\s*\([^)]*\)\s*\{/.test(line) && lines[i + 1]?.trim() === '}')) {
|
|
89
|
+
violations.push(this.createViolation(filePath, 'Empty catch block swallows errors silently', i + 1, 'Log the error or handle it appropriately'));
|
|
90
|
+
}
|
|
91
|
+
// Check for catch blocks that only console.log
|
|
92
|
+
if (/catch\s*\([^)]*\)\s*\{/.test(line)) {
|
|
93
|
+
const nextLines = lines.slice(i + 1, i + 4).join('\n');
|
|
94
|
+
if (/console\.(log|error)/.test(nextLines) && !/throw|return|reject/.test(nextLines)) {
|
|
95
|
+
violations.push(this.createViolation(filePath, 'Catch block only logs error without proper handling', i + 1, 'Consider rethrowing, returning error result, or using circuit breaker'));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
hasTimeout(context) {
|
|
101
|
+
const timeoutPatterns = [
|
|
102
|
+
/timeout/i,
|
|
103
|
+
/AbortController/,
|
|
104
|
+
/signal/,
|
|
105
|
+
/deadline/i,
|
|
106
|
+
/\d+\s*\*\s*1000/, // Common timeout patterns like 30 * 1000
|
|
107
|
+
];
|
|
108
|
+
return timeoutPatterns.some(p => p.test(context));
|
|
109
|
+
}
|
|
110
|
+
createViolation(file, message, line, suggestion) {
|
|
111
|
+
return {
|
|
112
|
+
id: `${this.id}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
113
|
+
ruleId: this.id,
|
|
114
|
+
ruleName: this.name,
|
|
115
|
+
severity: 'warning',
|
|
116
|
+
message,
|
|
117
|
+
file,
|
|
118
|
+
line,
|
|
119
|
+
suggestion,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=resilience-patterns.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resilience-patterns.rule.js","sourceRoot":"","sources":["../../../../src/core/rules/builtin/resilience-patterns.rule.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Context Rule
|
|
3
|
+
*
|
|
4
|
+
* Validates security context propagation and authorization patterns.
|
|
5
|
+
*/
|
|
6
|
+
import { IRule, RuleContext, RuleConfig, RuleResult } from '../rule.interface.js';
|
|
7
|
+
interface SecurityContextConfig extends RuleConfig {
|
|
8
|
+
requireAuthentication?: boolean;
|
|
9
|
+
requireAuthorization?: boolean;
|
|
10
|
+
sensitiveRoutes?: string[];
|
|
11
|
+
publicRoutes?: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare class SecurityContextRule implements IRule {
|
|
14
|
+
readonly id = "security-context";
|
|
15
|
+
readonly name = "Security Context Propagation";
|
|
16
|
+
readonly description = "Validates security context propagation and authorization patterns";
|
|
17
|
+
readonly severity: "error";
|
|
18
|
+
readonly tags: string[];
|
|
19
|
+
private config;
|
|
20
|
+
configure(options: Partial<SecurityContextConfig>): void;
|
|
21
|
+
check(context: RuleContext): Promise<RuleResult>;
|
|
22
|
+
private isSecurityRelevantFile;
|
|
23
|
+
private checkAuthenticationMiddleware;
|
|
24
|
+
private checkAuthorizationDecorators;
|
|
25
|
+
private checkSecurityHeaders;
|
|
26
|
+
private checkSensitiveDataHandling;
|
|
27
|
+
private isPublicRoute;
|
|
28
|
+
private isSensitiveRoute;
|
|
29
|
+
private createViolation;
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=security-context.rule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-context.rule.d.ts","sourceRoot":"","sources":["../../../../src/core/rules/builtin/security-context.rule.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAIlF,UAAU,qBAAsB,SAAQ,UAAU;IAChD,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,qBAAa,mBAAoB,YAAW,KAAK;IAC/C,QAAQ,CAAC,EAAE,sBAAsB;IACjC,QAAQ,CAAC,IAAI,kCAAkC;IAC/C,QAAQ,CAAC,WAAW,uEAAuE;IAC3F,QAAQ,CAAC,QAAQ,EAAG,OAAO,CAAU;IACrC,QAAQ,CAAC,IAAI,WAAmD;IAEhE,OAAO,CAAC,MAAM,CAOZ;IAEF,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,qBAAqB,CAAC,GAAG,IAAI;IAIlD,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAwBtD,OAAO,CAAC,sBAAsB;IAK9B,OAAO,CAAC,6BAA6B;IAsDrC,OAAO,CAAC,4BAA4B;IAyBpC,OAAO,CAAC,oBAAoB;IAmB5B,OAAO,CAAC,0BAA0B;IAuBlC,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,eAAe;CAYxB"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Context Rule
|
|
3
|
+
*
|
|
4
|
+
* Validates security context propagation and authorization patterns.
|
|
5
|
+
*/
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
export class SecurityContextRule {
|
|
8
|
+
id = 'security-context';
|
|
9
|
+
name = 'Security Context Propagation';
|
|
10
|
+
description = 'Validates security context propagation and authorization patterns';
|
|
11
|
+
severity = 'error';
|
|
12
|
+
tags = ['security', 'authentication', 'authorization'];
|
|
13
|
+
config = {
|
|
14
|
+
enabled: true,
|
|
15
|
+
severity: 'error',
|
|
16
|
+
requireAuthentication: true,
|
|
17
|
+
requireAuthorization: true,
|
|
18
|
+
sensitiveRoutes: ['/admin', '/api/users', '/api/settings'],
|
|
19
|
+
publicRoutes: ['/health', '/public', '/auth/login'],
|
|
20
|
+
};
|
|
21
|
+
configure(options) {
|
|
22
|
+
this.config = { ...this.config, ...options };
|
|
23
|
+
}
|
|
24
|
+
async check(context) {
|
|
25
|
+
const violations = [];
|
|
26
|
+
for (const nodeId of context.graph.nodes()) {
|
|
27
|
+
const node = context.getNodeData(nodeId);
|
|
28
|
+
if (!node)
|
|
29
|
+
continue;
|
|
30
|
+
const filePath = node.data.relativePath;
|
|
31
|
+
const content = context.fileContents?.get(filePath);
|
|
32
|
+
if (!content)
|
|
33
|
+
continue;
|
|
34
|
+
const fileName = path.basename(filePath).toLowerCase();
|
|
35
|
+
if (this.isSecurityRelevantFile(fileName)) {
|
|
36
|
+
this.checkAuthenticationMiddleware(filePath, content, violations);
|
|
37
|
+
this.checkAuthorizationDecorators(filePath, content, violations);
|
|
38
|
+
this.checkSecurityHeaders(filePath, content, violations);
|
|
39
|
+
this.checkSensitiveDataHandling(filePath, content, violations);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { violations };
|
|
43
|
+
}
|
|
44
|
+
isSecurityRelevantFile(fileName) {
|
|
45
|
+
const relevantPatterns = ['controller', 'handler', 'router', 'middleware', 'guard', 'auth'];
|
|
46
|
+
return relevantPatterns.some(p => fileName.includes(p));
|
|
47
|
+
}
|
|
48
|
+
checkAuthenticationMiddleware(filePath, content, violations) {
|
|
49
|
+
const lines = content.split('\n');
|
|
50
|
+
// Look for routes/endpoints
|
|
51
|
+
const routePatterns = [
|
|
52
|
+
/@(?:Get|Post|Put|Delete|Patch)\s*\(\s*['"]([^'"]+)['"]\s*\)/,
|
|
53
|
+
/router\.(?:get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]/,
|
|
54
|
+
];
|
|
55
|
+
for (let i = 0; i < lines.length; i++) {
|
|
56
|
+
const line = lines[i];
|
|
57
|
+
for (const pattern of routePatterns) {
|
|
58
|
+
const match = line.match(pattern);
|
|
59
|
+
if (match) {
|
|
60
|
+
const route = match[1];
|
|
61
|
+
// Check if it's a public route
|
|
62
|
+
if (this.isPublicRoute(route))
|
|
63
|
+
continue;
|
|
64
|
+
// Look for auth guards/middleware in surrounding context
|
|
65
|
+
const contextStart = Math.max(0, i - 5);
|
|
66
|
+
const contextEnd = Math.min(lines.length, i + 2);
|
|
67
|
+
const context = lines.slice(contextStart, contextEnd).join('\n');
|
|
68
|
+
const hasAuth = /@UseGuards|@Auth|authenticate|isAuthenticated|requireAuth|@Public\(false\)/.test(context);
|
|
69
|
+
if (!hasAuth && this.config.requireAuthentication) {
|
|
70
|
+
violations.push(this.createViolation(filePath, `Route '${route}' without authentication guard`, i + 1, 'Add authentication middleware or guard'));
|
|
71
|
+
}
|
|
72
|
+
// Check for authorization on sensitive routes
|
|
73
|
+
if (this.isSensitiveRoute(route)) {
|
|
74
|
+
const hasAuthorization = /@Roles|@Permissions|@Authorize|authorize|checkPermission/.test(context);
|
|
75
|
+
if (!hasAuthorization && this.config.requireAuthorization) {
|
|
76
|
+
violations.push(this.createViolation(filePath, `Sensitive route '${route}' without authorization`, i + 1, 'Add role-based or permission-based authorization'));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
checkAuthorizationDecorators(filePath, content, violations) {
|
|
84
|
+
const lines = content.split('\n');
|
|
85
|
+
// Check for direct database access in controllers without authorization
|
|
86
|
+
if (filePath.toLowerCase().includes('controller')) {
|
|
87
|
+
for (let i = 0; i < lines.length; i++) {
|
|
88
|
+
const line = lines[i];
|
|
89
|
+
if (/repository\.|\.find\(|\.save\(|\.delete\(|\.update\(/.test(line)) {
|
|
90
|
+
// Check if there's authorization above
|
|
91
|
+
const contextAbove = lines.slice(Math.max(0, i - 10), i).join('\n');
|
|
92
|
+
if (!/@Roles|@Permissions|authorize|checkPermission/.test(contextAbove)) {
|
|
93
|
+
violations.push(this.createViolation(filePath, 'Direct data access in controller without authorization check', i + 1, 'Add authorization check before data operations'));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
checkSecurityHeaders(filePath, content, violations) {
|
|
100
|
+
// Check for response handling without security headers
|
|
101
|
+
if (content.includes('res.send') || content.includes('res.json')) {
|
|
102
|
+
const hasHelmet = content.includes('helmet') || content.includes('Helmet');
|
|
103
|
+
const hasSecurityHeaders = content.includes('X-Content-Type-Options') ||
|
|
104
|
+
content.includes('X-Frame-Options') ||
|
|
105
|
+
content.includes('Content-Security-Policy');
|
|
106
|
+
if (!hasHelmet && !hasSecurityHeaders) {
|
|
107
|
+
violations.push(this.createViolation(filePath, 'Response handling without security headers', 1, 'Use helmet middleware or set security headers manually'));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
checkSensitiveDataHandling(filePath, content, violations) {
|
|
112
|
+
const lines = content.split('\n');
|
|
113
|
+
const sensitiveFields = ['password', 'token', 'secret', 'apiKey', 'creditCard', 'ssn'];
|
|
114
|
+
for (let i = 0; i < lines.length; i++) {
|
|
115
|
+
const line = lines[i].toLowerCase();
|
|
116
|
+
for (const field of sensitiveFields) {
|
|
117
|
+
if (line.includes(field)) {
|
|
118
|
+
// Check if it's being exposed in response
|
|
119
|
+
if (/res\.(json|send)\s*\(/.test(lines[i]) || /return\s+\{/.test(lines[i])) {
|
|
120
|
+
violations.push(this.createViolation(filePath, `Potential sensitive data '${field}' in response`, i + 1, 'Exclude sensitive fields from API responses using DTOs or serialization'));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
isPublicRoute(route) {
|
|
127
|
+
return this.config.publicRoutes?.some(p => route.startsWith(p)) || false;
|
|
128
|
+
}
|
|
129
|
+
isSensitiveRoute(route) {
|
|
130
|
+
return this.config.sensitiveRoutes?.some(p => route.startsWith(p)) || false;
|
|
131
|
+
}
|
|
132
|
+
createViolation(file, message, line, suggestion) {
|
|
133
|
+
return {
|
|
134
|
+
id: `${this.id}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
135
|
+
ruleId: this.id,
|
|
136
|
+
ruleName: this.name,
|
|
137
|
+
severity: 'error',
|
|
138
|
+
message,
|
|
139
|
+
file,
|
|
140
|
+
line,
|
|
141
|
+
suggestion,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=security-context.rule.js.map
|