@havoc-security/scanner 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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +22 -0
- package/dist/analyzers/AuthorizationCoverageAnalyzer.d.ts +7 -0
- package/dist/analyzers/AuthorizationCoverageAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/AuthorizationCoverageAnalyzer.js +100 -0
- package/dist/analyzers/AuthorizationCoverageAnalyzer.js.map +1 -0
- package/dist/analyzers/CredentialExposureAnalyzer.d.ts +11 -0
- package/dist/analyzers/CredentialExposureAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/CredentialExposureAnalyzer.js +262 -0
- package/dist/analyzers/CredentialExposureAnalyzer.js.map +1 -0
- package/dist/analyzers/DependencyAuditAnalyzer.d.ts +28 -0
- package/dist/analyzers/DependencyAuditAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/DependencyAuditAnalyzer.js +107 -0
- package/dist/analyzers/DependencyAuditAnalyzer.js.map +1 -0
- package/dist/analyzers/EncryptionAnalyzer.d.ts +7 -0
- package/dist/analyzers/EncryptionAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/EncryptionAnalyzer.js +170 -0
- package/dist/analyzers/EncryptionAnalyzer.js.map +1 -0
- package/dist/analyzers/FileUploadAnalyzer.d.ts +8 -0
- package/dist/analyzers/FileUploadAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/FileUploadAnalyzer.js +193 -0
- package/dist/analyzers/FileUploadAnalyzer.js.map +1 -0
- package/dist/analyzers/IdorAnalyzer.d.ts +7 -0
- package/dist/analyzers/IdorAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/IdorAnalyzer.js +91 -0
- package/dist/analyzers/IdorAnalyzer.js.map +1 -0
- package/dist/analyzers/MassAssignmentAnalyzer.d.ts +7 -0
- package/dist/analyzers/MassAssignmentAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/MassAssignmentAnalyzer.js +90 -0
- package/dist/analyzers/MassAssignmentAnalyzer.js.map +1 -0
- package/dist/analyzers/PrivilegeEscalationAnalyzer.d.ts +7 -0
- package/dist/analyzers/PrivilegeEscalationAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/PrivilegeEscalationAnalyzer.js +217 -0
- package/dist/analyzers/PrivilegeEscalationAnalyzer.js.map +1 -0
- package/dist/analyzers/RateLimitAnalyzer.d.ts +7 -0
- package/dist/analyzers/RateLimitAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/RateLimitAnalyzer.js +151 -0
- package/dist/analyzers/RateLimitAnalyzer.js.map +1 -0
- package/dist/analyzers/SessionSecurityAnalyzer.d.ts +10 -0
- package/dist/analyzers/SessionSecurityAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/SessionSecurityAnalyzer.js +295 -0
- package/dist/analyzers/SessionSecurityAnalyzer.js.map +1 -0
- package/dist/analyzers/SqlInjectionAnalyzer.d.ts +7 -0
- package/dist/analyzers/SqlInjectionAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/SqlInjectionAnalyzer.js +77 -0
- package/dist/analyzers/SqlInjectionAnalyzer.js.map +1 -0
- package/dist/analyzers/XssSurfaceAnalyzer.d.ts +7 -0
- package/dist/analyzers/XssSurfaceAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/XssSurfaceAnalyzer.js +100 -0
- package/dist/analyzers/XssSurfaceAnalyzer.js.map +1 -0
- package/dist/analyzers/index.d.ts +13 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +13 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +139 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/PhpParser.d.ts +56 -0
- package/dist/parsers/PhpParser.d.ts.map +1 -0
- package/dist/parsers/PhpParser.js +193 -0
- package/dist/parsers/PhpParser.js.map +1 -0
- package/dist/parsers/RouteParser.d.ts +87 -0
- package/dist/parsers/RouteParser.d.ts.map +1 -0
- package/dist/parsers/RouteParser.js +327 -0
- package/dist/parsers/RouteParser.js.map +1 -0
- package/dist/rules/index.d.ts +14 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +9 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/types/index.d.ts +137 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +30 -0
- package/package.json.bak +27 -0
- package/src/analyzers/AuthorizationCoverageAnalyzer.ts +213 -0
- package/src/analyzers/CredentialExposureAnalyzer.ts +312 -0
- package/src/analyzers/DependencyAuditAnalyzer.ts +135 -0
- package/src/analyzers/EncryptionAnalyzer.ts +195 -0
- package/src/analyzers/FileUploadAnalyzer.ts +239 -0
- package/src/analyzers/IdorAnalyzer.ts +118 -0
- package/src/analyzers/InsecureDeserializationAnalyzer.ts +212 -0
- package/src/analyzers/MassAssignmentAnalyzer.ts +105 -0
- package/src/analyzers/OpenRedirectAnalyzer.ts +149 -0
- package/src/analyzers/PrivilegeEscalationAnalyzer.ts +258 -0
- package/src/analyzers/RateLimitAnalyzer.ts +195 -0
- package/src/analyzers/SecurityHeaderAnalyzer.ts +263 -0
- package/src/analyzers/SessionSecurityAnalyzer.ts +342 -0
- package/src/analyzers/SqlInjectionAnalyzer.ts +99 -0
- package/src/analyzers/XssSurfaceAnalyzer.ts +112 -0
- package/src/analyzers/exclusions.ts +87 -0
- package/src/analyzers/index.ts +15 -0
- package/src/index.ts +226 -0
- package/src/parsers/PhpParser.ts +259 -0
- package/src/parsers/RouteParser.ts +384 -0
- package/src/rules/index.ts +16 -0
- package/src/types/index.ts +164 -0
- package/tests/EncryptionAnalyzer.test.ts +137 -0
- package/tests/PrivilegeEscalationAnalyzer.test.ts +141 -0
- package/tests/RateLimitAnalyzer.test.ts +112 -0
- package/tests/analyzers.test.ts +678 -0
- package/tests/auth-coverage-route-aware.test.ts +294 -0
- package/tests/credential-exposure.test.ts +142 -0
- package/tests/file-upload.test.ts +141 -0
- package/tests/fixtures/app/Http/Controllers/AdminController.php +19 -0
- package/tests/fixtures/app/Http/Controllers/PostController.php +49 -0
- package/tests/fixtures/app/Http/Controllers/PublicController.php +17 -0
- package/tests/fixtures/app/Models/Comment.php +11 -0
- package/tests/fixtures/app/Models/OpenModel.php +11 -0
- package/tests/fixtures/app/Models/Post.php +14 -0
- package/tests/fixtures/app/Models/SafeModel.php +10 -0
- package/tests/fixtures/app/Models/User.php +15 -0
- package/tests/fixtures/blade/mail.blade.php +8 -0
- package/tests/fixtures/blade/safe.blade.php +12 -0
- package/tests/fixtures/blade/vulnerable.blade.php +12 -0
- package/tests/fixtures/controllers/AdminController.php +19 -0
- package/tests/fixtures/controllers/PostController.php +49 -0
- package/tests/fixtures/controllers/PublicController.php +17 -0
- package/tests/fixtures/deserialization/safe.php +32 -0
- package/tests/fixtures/deserialization/unsafe.php +60 -0
- package/tests/fixtures/models/Comment.php +11 -0
- package/tests/fixtures/models/OpenModel.php +11 -0
- package/tests/fixtures/models/Post.php +14 -0
- package/tests/fixtures/models/SafeModel.php +10 -0
- package/tests/fixtures/models/User.php +15 -0
- package/tests/fixtures/redirect/safe.php +38 -0
- package/tests/fixtures/redirect/unsafe.php +39 -0
- package/tests/fixtures/routes/api.php +9 -0
- package/tests/fixtures/routes/web.php +18 -0
- package/tests/fixtures/security-headers/app/Http/Middleware/SecurityHeaders.php +24 -0
- package/tests/fixtures/security-headers/app/Providers/AppServiceProvider.php +16 -0
- package/tests/fixtures/sql/safe_queries.php +7 -0
- package/tests/fixtures/sql/vulnerable_queries.php +7 -0
- package/tests/new-analyzers.test.ts +373 -0
- package/tests/route-parser.test.ts +257 -0
- package/tests/scanner.test.ts +82 -0
- package/tests/session-security.test.ts +161 -0
- package/tests/types.test.ts +29 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { scan, DEFAULT_CONFIG, Severity } from '../src/index.js';
|
|
3
|
+
import type { ParsedFile } from '../src/types/index.js';
|
|
4
|
+
|
|
5
|
+
describe('scan() orchestrator', () => {
|
|
6
|
+
it('returns a valid ScanResult shape for empty input', async () => {
|
|
7
|
+
const result = await scan([], DEFAULT_CONFIG);
|
|
8
|
+
expect(result).toHaveProperty('findings');
|
|
9
|
+
expect(result).toHaveProperty('coverage');
|
|
10
|
+
expect(result).toHaveProperty('metadata');
|
|
11
|
+
expect(Array.isArray(result.findings)).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('metadata contains required fields', async () => {
|
|
15
|
+
const result = await scan([], DEFAULT_CONFIG);
|
|
16
|
+
expect(result.metadata.scannedAt).toBeTruthy();
|
|
17
|
+
expect(result.metadata.framework).toBe('laravel');
|
|
18
|
+
expect(result.metadata.filesScanned).toBe(0);
|
|
19
|
+
expect(typeof result.metadata.durationMs).toBe('number');
|
|
20
|
+
expect(result.metadata.scannerVersion).toBeTruthy();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('counts files scanned correctly', async () => {
|
|
24
|
+
const files: ParsedFile[] = [
|
|
25
|
+
{ path: 'a.php', content: '', ast: null },
|
|
26
|
+
{ path: 'b.php', content: '', ast: null },
|
|
27
|
+
];
|
|
28
|
+
const result = await scan(files, DEFAULT_CONFIG);
|
|
29
|
+
expect(result.metadata.filesScanned).toBe(2);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('runs with custom analyzers', async () => {
|
|
33
|
+
const mockAnalyzer = {
|
|
34
|
+
name: 'MockAnalyzer',
|
|
35
|
+
description: 'Test',
|
|
36
|
+
analyze: async () => [{
|
|
37
|
+
id: 'MOCK-001', severity: Severity.Low, analyzer: 'MockAnalyzer',
|
|
38
|
+
title: 'Mock', description: 'Test', file: 'test.php', line: 1, recommendation: 'Fix it',
|
|
39
|
+
}],
|
|
40
|
+
};
|
|
41
|
+
const result = await scan([], DEFAULT_CONFIG, [mockAnalyzer]);
|
|
42
|
+
expect(result.findings).toHaveLength(1);
|
|
43
|
+
expect(result.findings[0].id).toBe('MOCK-001');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('aggregates findings from multiple analyzers', async () => {
|
|
47
|
+
const a1 = {
|
|
48
|
+
name: 'A1', description: 'First',
|
|
49
|
+
analyze: async () => [{ id: 'A1-001', severity: Severity.High, analyzer: 'A1', title: 'A1', description: '', file: 'f.php', line: 1, recommendation: '' }],
|
|
50
|
+
};
|
|
51
|
+
const a2 = {
|
|
52
|
+
name: 'A2', description: 'Second',
|
|
53
|
+
analyze: async () => [{ id: 'A2-001', severity: Severity.Medium, analyzer: 'A2', title: 'A2', description: '', file: 'g.php', line: 5, recommendation: '' }],
|
|
54
|
+
};
|
|
55
|
+
const result = await scan([], DEFAULT_CONFIG, [a1, a2]);
|
|
56
|
+
expect(result.findings).toHaveLength(2);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('coverage struct has all required fields', async () => {
|
|
60
|
+
const result = await scan([], DEFAULT_CONFIG);
|
|
61
|
+
expect(result.coverage).toHaveProperty('totalRoutes');
|
|
62
|
+
expect(result.coverage).toHaveProperty('coveredRoutes');
|
|
63
|
+
expect(result.coverage).toHaveProperty('coveragePercent');
|
|
64
|
+
expect(result.coverage).toHaveProperty('totalModels');
|
|
65
|
+
expect(result.coverage).toHaveProperty('protectedModels');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('durationMs is a non-negative number', async () => {
|
|
69
|
+
const result = await scan([], DEFAULT_CONFIG);
|
|
70
|
+
expect(result.metadata.durationMs).toBeGreaterThanOrEqual(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('scanner returns empty findings for empty analyzers list', async () => {
|
|
74
|
+
const result = await scan([], DEFAULT_CONFIG, []);
|
|
75
|
+
expect(result.findings).toHaveLength(0);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('scanner version is a valid semver-like string', async () => {
|
|
79
|
+
const result = await scan([], DEFAULT_CONFIG);
|
|
80
|
+
expect(result.metadata.scannerVersion).toMatch(/^\d+\.\d+\.\d+$/);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { SessionSecurityAnalyzer } from '../src/analyzers/SessionSecurityAnalyzer.js';
|
|
3
|
+
import { DEFAULT_CONFIG } from '../src/index.js';
|
|
4
|
+
import type { ParsedFile } from '../src/types/index.js';
|
|
5
|
+
import { Severity } from '../src/types/index.js';
|
|
6
|
+
|
|
7
|
+
function makeFile(path: string, content: string): ParsedFile {
|
|
8
|
+
return { path, content, ast: null };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const analyzer = new SessionSecurityAnalyzer();
|
|
12
|
+
|
|
13
|
+
const SECURE_SESSION_CONFIG = `<?php
|
|
14
|
+
return [
|
|
15
|
+
'driver' => env('SESSION_DRIVER', 'redis'),
|
|
16
|
+
'lifetime' => env('SESSION_LIFETIME', 120),
|
|
17
|
+
'secure' => env('SESSION_SECURE_COOKIE', true),
|
|
18
|
+
'http_only' => true,
|
|
19
|
+
'same_site' => 'lax',
|
|
20
|
+
];`;
|
|
21
|
+
|
|
22
|
+
describe('SessionSecurityAnalyzer', () => {
|
|
23
|
+
it('has correct name', () => {
|
|
24
|
+
expect(analyzer.name).toBe('SessionSecurityAnalyzer');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('returns empty for empty file list', async () => {
|
|
28
|
+
expect(await analyzer.analyze([], DEFAULT_CONFIG)).toHaveLength(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Secure flag
|
|
32
|
+
it("flags 'secure' => false as HIGH", async () => {
|
|
33
|
+
const file = makeFile('config/session.php', `<?php
|
|
34
|
+
return [
|
|
35
|
+
'secure' => false,
|
|
36
|
+
'http_only' => true,
|
|
37
|
+
'same_site' => 'lax',
|
|
38
|
+
'driver' => 'redis',
|
|
39
|
+
'lifetime' => 120,
|
|
40
|
+
];`);
|
|
41
|
+
const findings = await analyzer.analyze([file], DEFAULT_CONFIG);
|
|
42
|
+
expect(findings.some(f => f.id === 'HAVOC-SESS-001' && f.severity === Severity.High)).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('does NOT flag secure => env(...) with no explicit false', async () => {
|
|
46
|
+
const file = makeFile('config/session.php', SECURE_SESSION_CONFIG);
|
|
47
|
+
const findings = await analyzer.analyze([file], DEFAULT_CONFIG);
|
|
48
|
+
expect(findings.filter(f => f.id === 'HAVOC-SESS-001')).toHaveLength(0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// http_only flag
|
|
52
|
+
it("flags 'http_only' => false as HIGH", async () => {
|
|
53
|
+
const file = makeFile('config/session.php', `<?php
|
|
54
|
+
return [
|
|
55
|
+
'http_only' => false,
|
|
56
|
+
'secure' => true,
|
|
57
|
+
'same_site' => 'lax',
|
|
58
|
+
'driver' => 'redis',
|
|
59
|
+
'lifetime' => 120,
|
|
60
|
+
];`);
|
|
61
|
+
const findings = await analyzer.analyze([file], DEFAULT_CONFIG);
|
|
62
|
+
expect(findings.some(f => f.id === 'HAVOC-SESS-002' && f.severity === Severity.High)).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// same_site
|
|
66
|
+
it("flags same_site => 'none' as MEDIUM", async () => {
|
|
67
|
+
const file = makeFile('config/session.php', `<?php
|
|
68
|
+
return [
|
|
69
|
+
'same_site' => 'none',
|
|
70
|
+
'secure' => true,
|
|
71
|
+
'http_only' => true,
|
|
72
|
+
'driver' => 'redis',
|
|
73
|
+
'lifetime' => 120,
|
|
74
|
+
];`);
|
|
75
|
+
const findings = await analyzer.analyze([file], DEFAULT_CONFIG);
|
|
76
|
+
expect(findings.some(f => f.id === 'HAVOC-SESS-003' && f.severity === Severity.Medium)).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("does NOT flag same_site => 'lax'", async () => {
|
|
80
|
+
const file = makeFile('config/session.php', SECURE_SESSION_CONFIG);
|
|
81
|
+
const findings = await analyzer.analyze([file], DEFAULT_CONFIG);
|
|
82
|
+
expect(findings.filter(f => f.id === 'HAVOC-SESS-003')).toHaveLength(0);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// File driver
|
|
86
|
+
it("flags driver => 'file' as MEDIUM", async () => {
|
|
87
|
+
const file = makeFile('config/session.php', `<?php
|
|
88
|
+
return [
|
|
89
|
+
'driver' => 'file',
|
|
90
|
+
'secure' => true,
|
|
91
|
+
'http_only' => true,
|
|
92
|
+
'same_site' => 'lax',
|
|
93
|
+
'lifetime' => 120,
|
|
94
|
+
];`);
|
|
95
|
+
const findings = await analyzer.analyze([file], DEFAULT_CONFIG);
|
|
96
|
+
expect(findings.some(f => f.id === 'HAVOC-SESS-004' && f.severity === Severity.Medium)).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Session lifetime
|
|
100
|
+
it('flags lifetime > 480 minutes as MEDIUM', async () => {
|
|
101
|
+
const file = makeFile('config/session.php', `<?php
|
|
102
|
+
return [
|
|
103
|
+
'lifetime' => 1440,
|
|
104
|
+
'driver' => 'redis',
|
|
105
|
+
'secure' => true,
|
|
106
|
+
'http_only' => true,
|
|
107
|
+
'same_site' => 'lax',
|
|
108
|
+
];`);
|
|
109
|
+
const findings = await analyzer.analyze([file], DEFAULT_CONFIG);
|
|
110
|
+
expect(findings.some(f => f.id === 'HAVOC-SESS-006' && f.severity === Severity.Medium)).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('does NOT flag lifetime <= 480 minutes', async () => {
|
|
114
|
+
const file = makeFile('config/session.php', SECURE_SESSION_CONFIG);
|
|
115
|
+
const findings = await analyzer.analyze([file], DEFAULT_CONFIG);
|
|
116
|
+
expect(findings.filter(f => f.id === 'HAVOC-SESS-006')).toHaveLength(0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// CSRF exclusions
|
|
120
|
+
it('flags non-empty $except in VerifyCsrfToken as HIGH', async () => {
|
|
121
|
+
const file = makeFile('app/Http/Middleware/VerifyCsrfToken.php', `<?php
|
|
122
|
+
class VerifyCsrfToken extends Middleware {
|
|
123
|
+
protected $except = [
|
|
124
|
+
'stripe/webhook',
|
|
125
|
+
'paypal/ipn',
|
|
126
|
+
];
|
|
127
|
+
}`);
|
|
128
|
+
const findings = await analyzer.analyze([file], DEFAULT_CONFIG);
|
|
129
|
+
expect(findings.some(f => f.id === 'HAVOC-SESS-005' && f.severity === Severity.High)).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('does NOT flag empty $except array', async () => {
|
|
133
|
+
const file = makeFile('app/Http/Middleware/VerifyCsrfToken.php', `<?php
|
|
134
|
+
class VerifyCsrfToken extends Middleware {
|
|
135
|
+
protected $except = [];
|
|
136
|
+
}`);
|
|
137
|
+
const findings = await analyzer.analyze([file], DEFAULT_CONFIG);
|
|
138
|
+
expect(findings.filter(f => f.id === 'HAVOC-SESS-005')).toHaveLength(0);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Cookie encryption
|
|
142
|
+
it('flags non-empty $except in EncryptCookies', async () => {
|
|
143
|
+
const file = makeFile('app/Http/Middleware/EncryptCookies.php', `<?php
|
|
144
|
+
class EncryptCookies extends Middleware {
|
|
145
|
+
protected $except = [
|
|
146
|
+
'analytics_id',
|
|
147
|
+
];
|
|
148
|
+
}`);
|
|
149
|
+
const findings = await analyzer.analyze([file], DEFAULT_CONFIG);
|
|
150
|
+
expect(findings.some(f => f.id === 'HAVOC-SESS-007')).toBe(true);
|
|
151
|
+
expect(findings.some(f => f.id === 'HAVOC-SESS-005')).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Clean session config — no findings
|
|
155
|
+
it('produces no findings for a well-configured session config', async () => {
|
|
156
|
+
const file = makeFile('config/session.php', SECURE_SESSION_CONFIG);
|
|
157
|
+
const findings = await analyzer.analyze([file], DEFAULT_CONFIG);
|
|
158
|
+
// Should have no findings on a clean config
|
|
159
|
+
expect(findings).toHaveLength(0);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Severity } from '../src/types/index.js';
|
|
3
|
+
|
|
4
|
+
describe('Severity enum', () => {
|
|
5
|
+
it('has Critical value', () => {
|
|
6
|
+
expect(Severity.Critical).toBe('critical');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('has High value', () => {
|
|
10
|
+
expect(Severity.High).toBe('high');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('has Medium value', () => {
|
|
14
|
+
expect(Severity.Medium).toBe('medium');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('has Low value', () => {
|
|
18
|
+
expect(Severity.Low).toBe('low');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('has Info value', () => {
|
|
22
|
+
expect(Severity.Info).toBe('info');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('has exactly 5 severity levels', () => {
|
|
26
|
+
const values = Object.values(Severity);
|
|
27
|
+
expect(values).toHaveLength(5);
|
|
28
|
+
});
|
|
29
|
+
});
|