verification-layer 0.20.0 → 0.22.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/README.md +251 -615
- package/dist/cli.js +542 -0
- package/dist/cli.js.map +1 -1
- package/dist/marketplace/index.d.ts +8 -0
- package/dist/marketplace/index.d.ts.map +1 -0
- package/dist/marketplace/index.js +7 -0
- package/dist/marketplace/index.js.map +1 -0
- package/dist/marketplace/installer.d.ts +62 -0
- package/dist/marketplace/installer.d.ts.map +1 -0
- package/dist/marketplace/installer.js +254 -0
- package/dist/marketplace/installer.js.map +1 -0
- package/dist/marketplace/registry.d.ts +52 -0
- package/dist/marketplace/registry.d.ts.map +1 -0
- package/dist/marketplace/registry.js +759 -0
- package/dist/marketplace/registry.js.map +1 -0
- package/dist/marketplace/types.d.ts +123 -0
- package/dist/marketplace/types.d.ts.map +1 -0
- package/dist/marketplace/types.js +6 -0
- package/dist/marketplace/types.js.map +1 -0
- package/dist/reporters/audit-report.d.ts.map +1 -1
- package/dist/reporters/audit-report.js +180 -0
- package/dist/reporters/audit-report.js.map +1 -1
- package/dist/reporters/index.d.ts.map +1 -1
- package/dist/reporters/index.js +2612 -5
- package/dist/reporters/index.js.map +1 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +15 -1
- package/dist/scan.js.map +1 -1
- package/dist/scanners/api-security/index.d.ts +7 -0
- package/dist/scanners/api-security/index.d.ts.map +1 -0
- package/dist/scanners/api-security/index.js +139 -0
- package/dist/scanners/api-security/index.js.map +1 -0
- package/dist/scanners/api-security/index.test.d.ts +5 -0
- package/dist/scanners/api-security/index.test.d.ts.map +1 -0
- package/dist/scanners/api-security/index.test.js +360 -0
- package/dist/scanners/api-security/index.test.js.map +1 -0
- package/dist/scanners/api-security/patterns.d.ts +32 -0
- package/dist/scanners/api-security/patterns.d.ts.map +1 -0
- package/dist/scanners/api-security/patterns.js +159 -0
- package/dist/scanners/api-security/patterns.js.map +1 -0
- package/dist/scanners/authentication/index.d.ts +7 -0
- package/dist/scanners/authentication/index.d.ts.map +1 -0
- package/dist/scanners/authentication/index.js +107 -0
- package/dist/scanners/authentication/index.js.map +1 -0
- package/dist/scanners/authentication/index.test.d.ts +5 -0
- package/dist/scanners/authentication/index.test.d.ts.map +1 -0
- package/dist/scanners/authentication/index.test.js +379 -0
- package/dist/scanners/authentication/index.test.js.map +1 -0
- package/dist/scanners/authentication/patterns.d.ts +32 -0
- package/dist/scanners/authentication/patterns.d.ts.map +1 -0
- package/dist/scanners/authentication/patterns.js +133 -0
- package/dist/scanners/authentication/patterns.js.map +1 -0
- package/dist/scanners/configuration/index.d.ts +8 -0
- package/dist/scanners/configuration/index.d.ts.map +1 -0
- package/dist/scanners/configuration/index.js +87 -0
- package/dist/scanners/configuration/index.js.map +1 -0
- package/dist/scanners/configuration/index.test.d.ts +5 -0
- package/dist/scanners/configuration/index.test.d.ts.map +1 -0
- package/dist/scanners/configuration/index.test.js +344 -0
- package/dist/scanners/configuration/index.test.js.map +1 -0
- package/dist/scanners/configuration/patterns.d.ts +32 -0
- package/dist/scanners/configuration/patterns.d.ts.map +1 -0
- package/dist/scanners/configuration/patterns.js +146 -0
- package/dist/scanners/configuration/patterns.js.map +1 -0
- package/dist/scanners/credentials/index.d.ts +7 -0
- package/dist/scanners/credentials/index.d.ts.map +1 -0
- package/dist/scanners/credentials/index.js +129 -0
- package/dist/scanners/credentials/index.js.map +1 -0
- package/dist/scanners/credentials/index.test.d.ts +5 -0
- package/dist/scanners/credentials/index.test.d.ts.map +1 -0
- package/dist/scanners/credentials/index.test.js +395 -0
- package/dist/scanners/credentials/index.test.js.map +1 -0
- package/dist/scanners/credentials/patterns.d.ts +32 -0
- package/dist/scanners/credentials/patterns.d.ts.map +1 -0
- package/dist/scanners/credentials/patterns.js +140 -0
- package/dist/scanners/credentials/patterns.js.map +1 -0
- package/dist/scanners/errors/index.d.ts +8 -0
- package/dist/scanners/errors/index.d.ts.map +1 -0
- package/dist/scanners/errors/index.js +78 -0
- package/dist/scanners/errors/index.js.map +1 -0
- package/dist/scanners/errors/index.test.d.ts +5 -0
- package/dist/scanners/errors/index.test.d.ts.map +1 -0
- package/dist/scanners/errors/index.test.js +330 -0
- package/dist/scanners/errors/index.test.js.map +1 -0
- package/dist/scanners/errors/patterns.d.ts +27 -0
- package/dist/scanners/errors/patterns.d.ts.map +1 -0
- package/dist/scanners/errors/patterns.js +97 -0
- package/dist/scanners/errors/patterns.js.map +1 -0
- package/dist/scanners/hipaa2026/index.d.ts +8 -0
- package/dist/scanners/hipaa2026/index.d.ts.map +1 -0
- package/dist/scanners/hipaa2026/index.js +345 -0
- package/dist/scanners/hipaa2026/index.js.map +1 -0
- package/dist/scanners/hipaa2026/index.test.d.ts +5 -0
- package/dist/scanners/hipaa2026/index.test.d.ts.map +1 -0
- package/dist/scanners/hipaa2026/index.test.js +332 -0
- package/dist/scanners/hipaa2026/index.test.js.map +1 -0
- package/dist/scanners/hipaa2026/patterns.d.ts +57 -0
- package/dist/scanners/hipaa2026/patterns.d.ts.map +1 -0
- package/dist/scanners/hipaa2026/patterns.js +268 -0
- package/dist/scanners/hipaa2026/patterns.js.map +1 -0
- package/dist/scanners/operational/index.d.ts +7 -0
- package/dist/scanners/operational/index.d.ts.map +1 -0
- package/dist/scanners/operational/index.js +171 -0
- package/dist/scanners/operational/index.js.map +1 -0
- package/dist/scanners/operational/index.test.d.ts +5 -0
- package/dist/scanners/operational/index.test.d.ts.map +1 -0
- package/dist/scanners/operational/index.test.js +406 -0
- package/dist/scanners/operational/index.test.js.map +1 -0
- package/dist/scanners/operational/patterns.d.ts +33 -0
- package/dist/scanners/operational/patterns.d.ts.map +1 -0
- package/dist/scanners/operational/patterns.js +151 -0
- package/dist/scanners/operational/patterns.js.map +1 -0
- package/dist/scanners/rbac/index.d.ts +7 -0
- package/dist/scanners/rbac/index.d.ts.map +1 -0
- package/dist/scanners/rbac/index.js +145 -0
- package/dist/scanners/rbac/index.js.map +1 -0
- package/dist/scanners/rbac/index.test.d.ts +5 -0
- package/dist/scanners/rbac/index.test.d.ts.map +1 -0
- package/dist/scanners/rbac/index.test.js +422 -0
- package/dist/scanners/rbac/index.test.js.map +1 -0
- package/dist/scanners/rbac/patterns.d.ts +32 -0
- package/dist/scanners/rbac/patterns.d.ts.map +1 -0
- package/dist/scanners/rbac/patterns.js +124 -0
- package/dist/scanners/rbac/patterns.js.map +1 -0
- package/dist/scanners/revocation/index.d.ts +8 -0
- package/dist/scanners/revocation/index.d.ts.map +1 -0
- package/dist/scanners/revocation/index.js +83 -0
- package/dist/scanners/revocation/index.js.map +1 -0
- package/dist/scanners/revocation/index.test.d.ts +5 -0
- package/dist/scanners/revocation/index.test.d.ts.map +1 -0
- package/dist/scanners/revocation/index.test.js +332 -0
- package/dist/scanners/revocation/index.test.js.map +1 -0
- package/dist/scanners/revocation/patterns.d.ts +27 -0
- package/dist/scanners/revocation/patterns.d.ts.map +1 -0
- package/dist/scanners/revocation/patterns.js +109 -0
- package/dist/scanners/revocation/patterns.js.map +1 -0
- package/dist/scanners/sanitization/index.d.ts +8 -0
- package/dist/scanners/sanitization/index.d.ts.map +1 -0
- package/dist/scanners/sanitization/index.js +98 -0
- package/dist/scanners/sanitization/index.js.map +1 -0
- package/dist/scanners/sanitization/index.test.d.ts +5 -0
- package/dist/scanners/sanitization/index.test.d.ts.map +1 -0
- package/dist/scanners/sanitization/index.test.js +370 -0
- package/dist/scanners/sanitization/index.test.js.map +1 -0
- package/dist/scanners/sanitization/patterns.d.ts +27 -0
- package/dist/scanners/sanitization/patterns.d.ts.map +1 -0
- package/dist/scanners/sanitization/patterns.js +117 -0
- package/dist/scanners/sanitization/patterns.js.map +1 -0
- package/dist/training/certificate.d.ts +26 -0
- package/dist/training/certificate.d.ts.map +1 -0
- package/dist/training/certificate.js +92 -0
- package/dist/training/certificate.js.map +1 -0
- package/dist/training/index.d.ts +3 -0
- package/dist/training/index.d.ts.map +1 -0
- package/dist/training/index.js +243 -0
- package/dist/training/index.js.map +1 -0
- package/dist/training/modules.d.ts +13 -0
- package/dist/training/modules.d.ts.map +1 -0
- package/dist/training/modules.js +608 -0
- package/dist/training/modules.js.map +1 -0
- package/dist/training/questions.d.ts +9 -0
- package/dist/training/questions.d.ts.map +1 -0
- package/dist/training/questions.js +505 -0
- package/dist/training/questions.js.map +1 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/npm-audit.d.ts +6 -0
- package/dist/utils/npm-audit.d.ts.map +1 -0
- package/dist/utils/npm-audit.js +95 -0
- package/dist/utils/npm-audit.js.map +1 -0
- package/dist/utils/scan-history.d.ts +59 -0
- package/dist/utils/scan-history.d.ts.map +1 -0
- package/dist/utils/scan-history.js +170 -0
- package/dist/utils/scan-history.js.map +1 -0
- package/package.json +4 -1
- package/templates/baa-verification-letter.md +105 -0
- package/templates/irp.md +545 -0
- package/templates/notice-of-privacy-practices.md +491 -0
- package/templates/physical-safeguards-checklist.md +247 -0
- package/templates/security-officer-designation.md +237 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Revocation Security Detection Patterns
|
|
3
|
+
* Detects JWT usage without revocation mechanisms and excessive token expiration
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* REVOKE-001: JWT Without Server-Side Revocation Mechanism
|
|
7
|
+
* Detects JWT usage without revocation support
|
|
8
|
+
*/
|
|
9
|
+
export const JWT_WITHOUT_REVOCATION = {
|
|
10
|
+
id: 'REVOKE-001',
|
|
11
|
+
name: 'JWT Without Server-Side Revocation Mechanism',
|
|
12
|
+
description: 'JWT token generation (jwt.sign, jsonwebtoken, jose) without server-side revocation mechanism (revoke, blacklist, allowlist, tokenStore, invalidate, denylist). HIPAA NPRM requires ability to revoke access within 1 hour.',
|
|
13
|
+
severity: 'high',
|
|
14
|
+
hipaaReference: 'NPRM §164.308(a)(3)(ii)(C) - Access Revocation',
|
|
15
|
+
patterns: [
|
|
16
|
+
// jwt.sign() calls
|
|
17
|
+
/jwt\.sign\s*\(/i,
|
|
18
|
+
// jsonwebtoken.sign() calls
|
|
19
|
+
/jsonwebtoken\.sign\s*\(/i,
|
|
20
|
+
// jose sign methods
|
|
21
|
+
/new\s+(?:SignJWT|CompactSign)\s*\(/i,
|
|
22
|
+
/jose\.(?:SignJWT|sign)/i,
|
|
23
|
+
// JWT token generation
|
|
24
|
+
/(?:create|generate)(?:Access)?Token\s*\([^)]*\)/i,
|
|
25
|
+
// Sign JWT patterns
|
|
26
|
+
/\.sign\s*\([^)]*jwt/i,
|
|
27
|
+
],
|
|
28
|
+
negativePatterns: [
|
|
29
|
+
// Revocation mechanisms
|
|
30
|
+
/revoke/i,
|
|
31
|
+
/blacklist/i,
|
|
32
|
+
/allowlist/i,
|
|
33
|
+
/whitelist/i,
|
|
34
|
+
/denylist/i,
|
|
35
|
+
/tokenStore/i,
|
|
36
|
+
/invalidate/i,
|
|
37
|
+
/revokedTokens/i,
|
|
38
|
+
/tokenBlacklist/i,
|
|
39
|
+
/redis/i, // Common for token storage
|
|
40
|
+
/session/i, // Session-based auth has built-in revocation
|
|
41
|
+
// Database token storage
|
|
42
|
+
/tokenRepository/i,
|
|
43
|
+
/saveToken/i,
|
|
44
|
+
/storeToken/i,
|
|
45
|
+
// Revocation libraries
|
|
46
|
+
/express-jwt-blacklist/i,
|
|
47
|
+
/jwt-redis/i,
|
|
48
|
+
// Comments indicating revocation is handled elsewhere
|
|
49
|
+
/revocation.*handled/i,
|
|
50
|
+
/revoke.*separate/i,
|
|
51
|
+
],
|
|
52
|
+
recommendation: 'Implement server-side token revocation mechanism. Store active tokens in Redis/database with TTL, or maintain a blacklist of revoked tokens. Example: await redis.set(`token:${userId}`, token, "EX", 3600); await redis.del(`token:${userId}`) to revoke. HIPAA requires ability to revoke access within 1 hour.',
|
|
53
|
+
category: 'access-control',
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* REVOKE-002: Excessive Token Expiration Time
|
|
57
|
+
* Detects tokens with expirations longer than recommended
|
|
58
|
+
*/
|
|
59
|
+
export const EXCESSIVE_TOKEN_EXPIRATION = {
|
|
60
|
+
id: 'REVOKE-002',
|
|
61
|
+
name: 'Excessive Token Expiration Time',
|
|
62
|
+
description: 'JWT token with excessive expiration time. Access tokens should expire within 24 hours (expiresIn: "24h" or less). Refresh tokens should not exceed 7 days for sensitive data.',
|
|
63
|
+
severity: 'medium',
|
|
64
|
+
hipaaReference: 'NPRM §164.308(a)(3)(ii)(C) - Access Revocation',
|
|
65
|
+
patterns: [
|
|
66
|
+
// expiresIn with excessive time
|
|
67
|
+
// Days: 2d, 3d, 7d, 30d, 90d, etc. (more than 1 day)
|
|
68
|
+
/expiresIn\s*:\s*['"`](?:[2-9]|[1-9]\d+)d['"`]/i,
|
|
69
|
+
// Hours: 25h, 48h, 72h, etc. (more than 24 hours)
|
|
70
|
+
/expiresIn\s*:\s*['"`](?:2[5-9]|[3-9]\d|\d{3,})h['"`]/i,
|
|
71
|
+
// Weeks, years (w, y - note: m in JWT means minutes, not months)
|
|
72
|
+
/expiresIn\s*:\s*['"`]\d+[wy]['"`]/i,
|
|
73
|
+
// Months (spelled out)
|
|
74
|
+
/expiresIn\s*:\s*['"`]\d+\s*(?:month|months|mo)['"`]/i,
|
|
75
|
+
// Numeric seconds: more than 86400 (24 hours)
|
|
76
|
+
/expiresIn\s*:\s*(?:8[7-9]\d{3}|\d{6,})\b/i,
|
|
77
|
+
// maxAge with excessive time (more than 7 days = 604800000 ms)
|
|
78
|
+
/maxAge\s*:\s*(?:6[1-9]\d{7}|[7-9]\d{8}|\d{9,})\b/i,
|
|
79
|
+
// exp claim with far future timestamps (common mistake)
|
|
80
|
+
/exp\s*:\s*Math\.floor\s*\([^)]*\+\s*(?:2[5-9]|[3-9]\d|\d{3,})\s*\*\s*(?:60\s*\*\s*60|3600)/i,
|
|
81
|
+
],
|
|
82
|
+
negativePatterns: [
|
|
83
|
+
// Acceptable expirations for access tokens (1h - 24h)
|
|
84
|
+
/expiresIn\s*:\s*['"`](?:1?\d|2[0-4])h['"`]/i,
|
|
85
|
+
/expiresIn\s*:\s*['"`]1d['"`]/i,
|
|
86
|
+
// Minutes (m in JWT means minutes, not months)
|
|
87
|
+
/expiresIn\s*:\s*['"`]\d+m['"`]/i,
|
|
88
|
+
// Seconds (s)
|
|
89
|
+
/expiresIn\s*:\s*['"`]\d+s['"`]/i,
|
|
90
|
+
// Acceptable numeric values (up to 24 hours = 86400 seconds)
|
|
91
|
+
/expiresIn\s*:\s*(?:[1-7]\d{3}|[1-8][0-5]\d{3}|86[0-3]\d{2}|8640{1,2})\b/i,
|
|
92
|
+
// Refresh tokens (explicitly marked as such)
|
|
93
|
+
/refresh.*token/i,
|
|
94
|
+
/refreshToken/i,
|
|
95
|
+
// Remember me tokens (longer expiration is acceptable)
|
|
96
|
+
/remember.*me/i,
|
|
97
|
+
/rememberMe/i,
|
|
98
|
+
// API keys (different from access tokens)
|
|
99
|
+
/api.*key/i,
|
|
100
|
+
/apiKey/i,
|
|
101
|
+
],
|
|
102
|
+
recommendation: 'Use short-lived access tokens (≤ 24 hours): expiresIn: "1h" or expiresIn: "15m". For longer sessions, implement refresh token rotation. Example: accessToken with expiresIn: "1h", refreshToken with expiresIn: "7d". Reduce attack window if token is compromised.',
|
|
103
|
+
category: 'access-control',
|
|
104
|
+
};
|
|
105
|
+
export const ALL_REVOCATION_PATTERNS = [
|
|
106
|
+
JWT_WITHOUT_REVOCATION,
|
|
107
|
+
EXCESSIVE_TOKEN_EXPIRATION,
|
|
108
|
+
];
|
|
109
|
+
//# sourceMappingURL=patterns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../../src/scanners/revocation/patterns.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAcH;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAsB;IACvD,EAAE,EAAE,YAAY;IAChB,IAAI,EAAE,8CAA8C;IACpD,WAAW,EACT,4NAA4N;IAC9N,QAAQ,EAAE,MAAM;IAChB,cAAc,EAAE,gDAAgD;IAChE,QAAQ,EAAE;QACR,mBAAmB;QACnB,iBAAiB;QAEjB,4BAA4B;QAC5B,0BAA0B;QAE1B,oBAAoB;QACpB,qCAAqC;QACrC,yBAAyB;QAEzB,uBAAuB;QACvB,kDAAkD;QAElD,oBAAoB;QACpB,sBAAsB;KACvB;IACD,gBAAgB,EAAE;QAChB,wBAAwB;QACxB,SAAS;QACT,YAAY;QACZ,YAAY;QACZ,YAAY;QACZ,WAAW;QACX,aAAa;QACb,aAAa;QACb,gBAAgB;QAChB,iBAAiB;QACjB,QAAQ,EAAE,2BAA2B;QACrC,UAAU,EAAE,6CAA6C;QAEzD,yBAAyB;QACzB,kBAAkB;QAClB,YAAY;QACZ,aAAa;QAEb,uBAAuB;QACvB,wBAAwB;QACxB,YAAY;QAEZ,sDAAsD;QACtD,sBAAsB;QACtB,mBAAmB;KACpB;IACD,cAAc,EACZ,mTAAmT;IACrT,QAAQ,EAAE,gBAAgB;CAC3B,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAsB;IAC3D,EAAE,EAAE,YAAY;IAChB,IAAI,EAAE,iCAAiC;IACvC,WAAW,EACT,+KAA+K;IACjL,QAAQ,EAAE,QAAQ;IAClB,cAAc,EAAE,gDAAgD;IAChE,QAAQ,EAAE;QACR,gCAAgC;QAChC,qDAAqD;QACrD,gDAAgD;QAEhD,kDAAkD;QAClD,uDAAuD;QAEvD,iEAAiE;QACjE,oCAAoC;QAEpC,uBAAuB;QACvB,sDAAsD;QAEtD,8CAA8C;QAC9C,2CAA2C;QAE3C,+DAA+D;QAC/D,mDAAmD;QAEnD,wDAAwD;QACxD,6FAA6F;KAC9F;IACD,gBAAgB,EAAE;QAChB,sDAAsD;QACtD,6CAA6C;QAC7C,+BAA+B;QAE/B,+CAA+C;QAC/C,iCAAiC;QAEjC,cAAc;QACd,iCAAiC;QAEjC,6DAA6D;QAC7D,0EAA0E;QAE1E,6CAA6C;QAC7C,iBAAiB;QACjB,eAAe;QAEf,uDAAuD;QACvD,eAAe;QACf,aAAa;QAEb,0CAA0C;QAC1C,WAAW;QACX,SAAS;KACV;IACD,cAAc,EACZ,qQAAqQ;IACvQ,QAAQ,EAAE,gBAAgB;CAC3B,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAwB;IAC1D,sBAAsB;IACtB,0BAA0B;CAC3B,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Sanitization Security Scanner
|
|
3
|
+
* Detects unsafe user input handling and file upload configurations
|
|
4
|
+
*/
|
|
5
|
+
import type { Scanner } from '../../types.js';
|
|
6
|
+
export declare const sanitizationScanner: Scanner;
|
|
7
|
+
export default sanitizationScanner;
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/scanners/sanitization/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAwB,MAAM,gBAAgB,CAAC;AAGpE,eAAO,MAAM,mBAAmB,EAAE,OA8GjC,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Sanitization Security Scanner
|
|
3
|
+
* Detects unsafe user input handling and file upload configurations
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs/promises';
|
|
6
|
+
import { ALL_SANITIZATION_PATTERNS } from './patterns.js';
|
|
7
|
+
export const sanitizationScanner = {
|
|
8
|
+
name: 'Input Sanitization Security Scanner',
|
|
9
|
+
category: 'access-control',
|
|
10
|
+
async scan(files, options) {
|
|
11
|
+
const findings = [];
|
|
12
|
+
// Filter to code files
|
|
13
|
+
const codeFiles = files.filter((f) => /\.(ts|tsx|js|jsx)$/.test(f));
|
|
14
|
+
for (const file of codeFiles) {
|
|
15
|
+
try {
|
|
16
|
+
const content = await fs.readFile(file, 'utf-8');
|
|
17
|
+
const lines = content.split('\n');
|
|
18
|
+
for (let i = 0; i < lines.length; i++) {
|
|
19
|
+
const line = lines[i];
|
|
20
|
+
const lineNumber = i + 1;
|
|
21
|
+
// Skip empty lines and comments
|
|
22
|
+
if (/^\s*$/.test(line) || /^\s*\/\//.test(line))
|
|
23
|
+
continue;
|
|
24
|
+
// Scan each pattern
|
|
25
|
+
for (const pattern of ALL_SANITIZATION_PATTERNS) {
|
|
26
|
+
// Check if line matches violation pattern
|
|
27
|
+
const matched = pattern.patterns.some((regex) => regex.test(line));
|
|
28
|
+
if (!matched)
|
|
29
|
+
continue;
|
|
30
|
+
// Get surrounding context (10 lines before and 5 after), excluding comments
|
|
31
|
+
const contextStart = Math.max(0, i - 10);
|
|
32
|
+
const contextEnd = Math.min(lines.length, i + 6);
|
|
33
|
+
const contextLines = lines.slice(contextStart, contextEnd);
|
|
34
|
+
// Filter out comment lines from context
|
|
35
|
+
const codeOnlyContext = contextLines
|
|
36
|
+
.filter(l => !/^\s*\/\//.test(l) && !/^\s*\/\*/.test(l) && !/^\s*\*/.test(l))
|
|
37
|
+
.join('\n');
|
|
38
|
+
// Check negative patterns (safe usage indicators)
|
|
39
|
+
// For SANITIZE-001, check context for validation
|
|
40
|
+
// For SANITIZE-002, check if config object has required fields
|
|
41
|
+
const isSafe = pattern.negativePatterns?.some((regex) => {
|
|
42
|
+
const patternStr = regex.source;
|
|
43
|
+
// For file upload patterns, check the entire config block
|
|
44
|
+
if (pattern.id === 'SANITIZE-002') {
|
|
45
|
+
// Check if the line has the required validation fields
|
|
46
|
+
// or if they appear in the surrounding context
|
|
47
|
+
return regex.test(codeOnlyContext);
|
|
48
|
+
}
|
|
49
|
+
// For SANITIZE-001, check surrounding context for validation
|
|
50
|
+
if (pattern.id === 'SANITIZE-001') {
|
|
51
|
+
// Check if validation happens in surrounding code (excluding comments)
|
|
52
|
+
return regex.test(codeOnlyContext);
|
|
53
|
+
}
|
|
54
|
+
return regex.test(line);
|
|
55
|
+
});
|
|
56
|
+
if (isSafe)
|
|
57
|
+
continue;
|
|
58
|
+
// Additional check for SANITIZE-002: ensure it's an actual configuration
|
|
59
|
+
if (pattern.id === 'SANITIZE-002') {
|
|
60
|
+
// Skip if it's just a variable reference or import
|
|
61
|
+
if (/^import\s/i.test(line) ||
|
|
62
|
+
/^const\s+\w+\s*=\s*require/i.test(line)) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// Only flag if it looks like actual middleware configuration
|
|
66
|
+
if (!/\bmulter\s*\(/i.test(line) &&
|
|
67
|
+
!/\bformidable\s*\(/i.test(line) &&
|
|
68
|
+
!/\bBusboy\s*\(/i.test(line) &&
|
|
69
|
+
!/IncomingForm\s*\(/i.test(line)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Create finding
|
|
74
|
+
const finding = {
|
|
75
|
+
id: pattern.id,
|
|
76
|
+
category: pattern.category,
|
|
77
|
+
severity: pattern.severity,
|
|
78
|
+
title: pattern.name,
|
|
79
|
+
description: `${pattern.description}\n\nCode: ${line.trim()}`,
|
|
80
|
+
file: file,
|
|
81
|
+
line: lineNumber,
|
|
82
|
+
recommendation: pattern.recommendation,
|
|
83
|
+
hipaaReference: pattern.hipaaReference,
|
|
84
|
+
confidence: 'high',
|
|
85
|
+
};
|
|
86
|
+
findings.push(finding);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
// Skip files that can't be read
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return findings;
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
export default sanitizationScanner;
|
|
98
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/scanners/sanitization/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../src/scanners/sanitization/index.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Sanitization Security Scanner Tests
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
5
|
+
import { sanitizationScanner } from './index.js';
|
|
6
|
+
import * as fs from 'fs/promises';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import * as os from 'os';
|
|
9
|
+
describe('Input Sanitization Security Scanner', () => {
|
|
10
|
+
let tempDir = '';
|
|
11
|
+
let testFiles = [];
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'sanitize-test-'));
|
|
14
|
+
});
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
// Cleanup
|
|
17
|
+
for (const file of testFiles) {
|
|
18
|
+
try {
|
|
19
|
+
await fs.unlink(file);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Ignore
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Ignore
|
|
30
|
+
}
|
|
31
|
+
testFiles = [];
|
|
32
|
+
});
|
|
33
|
+
async function createTestFile(filename, content) {
|
|
34
|
+
const filePath = path.join(tempDir, filename);
|
|
35
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
36
|
+
testFiles.push(filePath);
|
|
37
|
+
return filePath;
|
|
38
|
+
}
|
|
39
|
+
const scanOptions = {
|
|
40
|
+
path: tempDir,
|
|
41
|
+
};
|
|
42
|
+
describe('SANITIZE-001: Unsanitized User Input in Database Operations', () => {
|
|
43
|
+
it('should detect req.body in insert operation', async () => {
|
|
44
|
+
const file = await createTestFile('db-insert.ts', `
|
|
45
|
+
// VIOLATION SANITIZE-001: Direct req.body in insert
|
|
46
|
+
app.post('/users', async (req, res) => {
|
|
47
|
+
await db.insert('users').values(req.body);
|
|
48
|
+
});
|
|
49
|
+
`);
|
|
50
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
51
|
+
const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-001');
|
|
52
|
+
expect(sanitizeFindings.length).toBeGreaterThan(0);
|
|
53
|
+
expect(sanitizeFindings[0].severity).toBe('critical');
|
|
54
|
+
});
|
|
55
|
+
it('should detect req.params in query operation', async () => {
|
|
56
|
+
const file = await createTestFile('db-query.ts', `
|
|
57
|
+
// VIOLATION SANITIZE-001: Direct req.params in query
|
|
58
|
+
app.get('/users/:id', async (req, res) => {
|
|
59
|
+
const user = await db.query('SELECT * FROM users WHERE id = ' + req.params.id);
|
|
60
|
+
});
|
|
61
|
+
`);
|
|
62
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
63
|
+
expect(findings.some((f) => f.id === 'SANITIZE-001')).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
it('should detect req.query in where clause', async () => {
|
|
66
|
+
const file = await createTestFile('db-where.ts', `
|
|
67
|
+
// VIOLATION SANITIZE-001: Direct req.query in where
|
|
68
|
+
app.get('/search', async (req, res) => {
|
|
69
|
+
const results = await db.select().from('users').where('name', req.query.name);
|
|
70
|
+
});
|
|
71
|
+
`);
|
|
72
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
73
|
+
expect(findings.some((f) => f.id === 'SANITIZE-001')).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
it('should detect req.body in update operation', async () => {
|
|
76
|
+
const file = await createTestFile('db-update.ts', `
|
|
77
|
+
// VIOLATION SANITIZE-001: Direct req.body in update
|
|
78
|
+
app.put('/users/:id', async (req, res) => {
|
|
79
|
+
await db.update('users').set(req.body).where({ id: req.params.id });
|
|
80
|
+
});
|
|
81
|
+
`);
|
|
82
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
83
|
+
const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-001');
|
|
84
|
+
expect(sanitizeFindings.length).toBeGreaterThan(0);
|
|
85
|
+
});
|
|
86
|
+
it('should detect req.body in Prisma create', async () => {
|
|
87
|
+
const file = await createTestFile('prisma-create.ts', `
|
|
88
|
+
// VIOLATION SANITIZE-001: Direct req.body in Prisma
|
|
89
|
+
app.post('/users', async (req, res) => {
|
|
90
|
+
const user = await prisma.user.create({ data: req.body });
|
|
91
|
+
});
|
|
92
|
+
`);
|
|
93
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
94
|
+
expect(findings.some((f) => f.id === 'SANITIZE-001')).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
it('should detect req.body in Mongoose create', async () => {
|
|
97
|
+
const file = await createTestFile('mongoose-create.ts', `
|
|
98
|
+
// VIOLATION SANITIZE-001: Direct req.body in Mongoose
|
|
99
|
+
app.post('/users', async (req, res) => {
|
|
100
|
+
await User.create(req.body);
|
|
101
|
+
});
|
|
102
|
+
`);
|
|
103
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
104
|
+
expect(findings.some((f) => f.id === 'SANITIZE-001')).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
it('should detect SQL template literals with req.query', async () => {
|
|
107
|
+
const file = await createTestFile('sql-template.ts', `
|
|
108
|
+
// VIOLATION SANITIZE-001: SQL injection via template literal
|
|
109
|
+
app.get('/users', async (req, res) => {
|
|
110
|
+
const results = await db.query(\`SELECT * FROM users WHERE email = '\${req.query.email}'\`);
|
|
111
|
+
});
|
|
112
|
+
`);
|
|
113
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
114
|
+
expect(findings.some((f) => f.id === 'SANITIZE-001')).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
it('should detect raw SQL concatenation', async () => {
|
|
117
|
+
const file = await createTestFile('sql-concat.ts', `
|
|
118
|
+
// VIOLATION SANITIZE-001: SQL injection via concatenation
|
|
119
|
+
app.get('/search', async (req, res) => {
|
|
120
|
+
const sql = 'SELECT * FROM products WHERE name = ' + req.query.name;
|
|
121
|
+
await db.execute(sql);
|
|
122
|
+
});
|
|
123
|
+
`);
|
|
124
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
125
|
+
expect(findings.some((f) => f.id === 'SANITIZE-001')).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
it('should NOT flag when Zod validation is used', async () => {
|
|
128
|
+
const file = await createTestFile('zod-validation.ts', `
|
|
129
|
+
// SECURE: Zod validation before DB operation
|
|
130
|
+
import { z } from 'zod';
|
|
131
|
+
|
|
132
|
+
const userSchema = z.object({
|
|
133
|
+
name: z.string(),
|
|
134
|
+
email: z.string().email(),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
app.post('/users', async (req, res) => {
|
|
138
|
+
const validated = userSchema.parse(req.body);
|
|
139
|
+
await db.insert('users').values(validated);
|
|
140
|
+
});
|
|
141
|
+
`);
|
|
142
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
143
|
+
const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-001');
|
|
144
|
+
expect(sanitizeFindings.length).toBe(0);
|
|
145
|
+
});
|
|
146
|
+
it('should NOT flag when Joi validation is used', async () => {
|
|
147
|
+
const file = await createTestFile('joi-validation.ts', `
|
|
148
|
+
// SECURE: Joi validation
|
|
149
|
+
const Joi = require('joi');
|
|
150
|
+
const schema = Joi.object({ name: Joi.string() });
|
|
151
|
+
|
|
152
|
+
app.post('/users', async (req, res) => {
|
|
153
|
+
const { value } = schema.validate(req.body);
|
|
154
|
+
await db.insert('users').values(value);
|
|
155
|
+
});
|
|
156
|
+
`);
|
|
157
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
158
|
+
const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-001');
|
|
159
|
+
expect(sanitizeFindings.length).toBe(0);
|
|
160
|
+
});
|
|
161
|
+
it('should NOT flag when Yup validation is used', async () => {
|
|
162
|
+
const file = await createTestFile('yup-validation.ts', `
|
|
163
|
+
// SECURE: Yup validation
|
|
164
|
+
import * as yup from 'yup';
|
|
165
|
+
|
|
166
|
+
const schema = yup.object({ email: yup.string().email() });
|
|
167
|
+
|
|
168
|
+
app.post('/users', async (req, res) => {
|
|
169
|
+
const validated = await schema.validate(req.body);
|
|
170
|
+
await db.insert('users').values(validated);
|
|
171
|
+
});
|
|
172
|
+
`);
|
|
173
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
174
|
+
const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-001');
|
|
175
|
+
expect(sanitizeFindings.length).toBe(0);
|
|
176
|
+
});
|
|
177
|
+
it('should NOT flag when custom validation function is used', async () => {
|
|
178
|
+
const file = await createTestFile('custom-validation.ts', `
|
|
179
|
+
// SECURE: Custom validation
|
|
180
|
+
app.post('/users', async (req, res) => {
|
|
181
|
+
const sanitized = sanitizeUserInput(req.body);
|
|
182
|
+
await db.insert('users').values(sanitized);
|
|
183
|
+
});
|
|
184
|
+
`);
|
|
185
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
186
|
+
const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-001');
|
|
187
|
+
expect(sanitizeFindings.length).toBe(0);
|
|
188
|
+
});
|
|
189
|
+
it('should NOT flag when safeParse is used', async () => {
|
|
190
|
+
const file = await createTestFile('safe-parse.ts', `
|
|
191
|
+
// SECURE: safeParse validation
|
|
192
|
+
const result = schema.safeParse(req.body);
|
|
193
|
+
if (result.success) {
|
|
194
|
+
await db.insert('users').values(result.data);
|
|
195
|
+
}
|
|
196
|
+
`);
|
|
197
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
198
|
+
const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-001');
|
|
199
|
+
expect(sanitizeFindings.length).toBe(0);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
describe('SANITIZE-002: Insecure File Upload Configuration', () => {
|
|
203
|
+
it('should detect multer without fileFilter', async () => {
|
|
204
|
+
const file = await createTestFile('multer-basic.ts', `
|
|
205
|
+
// VIOLATION SANITIZE-002: Multer without validation
|
|
206
|
+
const upload = multer({ dest: 'uploads/' });
|
|
207
|
+
app.post('/upload', upload.single('file'), (req, res) => {
|
|
208
|
+
res.send('File uploaded');
|
|
209
|
+
});
|
|
210
|
+
`);
|
|
211
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
212
|
+
const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-002');
|
|
213
|
+
expect(sanitizeFindings.length).toBeGreaterThan(0);
|
|
214
|
+
expect(sanitizeFindings[0].severity).toBe('high');
|
|
215
|
+
});
|
|
216
|
+
it('should detect multer without limits', async () => {
|
|
217
|
+
const file = await createTestFile('multer-no-limits.ts', `
|
|
218
|
+
// VIOLATION SANITIZE-002: Multer without file size limits
|
|
219
|
+
const upload = multer({
|
|
220
|
+
storage: multer.diskStorage({
|
|
221
|
+
destination: 'uploads/',
|
|
222
|
+
filename: (req, file, cb) => cb(null, file.originalname)
|
|
223
|
+
})
|
|
224
|
+
});
|
|
225
|
+
`);
|
|
226
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
227
|
+
expect(findings.some((f) => f.id === 'SANITIZE-002')).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
it('should detect formidable without validation', async () => {
|
|
230
|
+
const file = await createTestFile('formidable-basic.ts', `
|
|
231
|
+
// VIOLATION SANITIZE-002: Formidable without validation
|
|
232
|
+
app.post('/upload', (req, res) => {
|
|
233
|
+
const form = new formidable.IncomingForm({ uploadDir: './uploads' });
|
|
234
|
+
form.parse(req);
|
|
235
|
+
});
|
|
236
|
+
`);
|
|
237
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
238
|
+
expect(findings.some((f) => f.id === 'SANITIZE-002')).toBe(true);
|
|
239
|
+
});
|
|
240
|
+
it('should detect Busboy without limits', async () => {
|
|
241
|
+
const file = await createTestFile('busboy-basic.ts', `
|
|
242
|
+
// VIOLATION SANITIZE-002: Busboy without limits
|
|
243
|
+
const busboy = new Busboy({ headers: req.headers });
|
|
244
|
+
busboy.on('file', (fieldname, file, filename) => {
|
|
245
|
+
file.pipe(fs.createWriteStream('./uploads/' + filename));
|
|
246
|
+
});
|
|
247
|
+
`);
|
|
248
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
249
|
+
expect(findings.some((f) => f.id === 'SANITIZE-002')).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
it('should NOT flag multer with fileFilter', async () => {
|
|
252
|
+
const file = await createTestFile('multer-secure.ts', `
|
|
253
|
+
// SECURE: Multer with fileFilter and limits
|
|
254
|
+
const upload = multer({
|
|
255
|
+
dest: 'uploads/',
|
|
256
|
+
fileFilter: (req, file, cb) => {
|
|
257
|
+
const allowedMimes = ['image/jpeg', 'image/png'];
|
|
258
|
+
if (allowedMimes.includes(file.mimetype)) {
|
|
259
|
+
cb(null, true);
|
|
260
|
+
} else {
|
|
261
|
+
cb(new Error('Invalid file type'));
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
limits: { fileSize: 5 * 1024 * 1024 }
|
|
265
|
+
});
|
|
266
|
+
`);
|
|
267
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
268
|
+
const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-002');
|
|
269
|
+
expect(sanitizeFindings.length).toBe(0);
|
|
270
|
+
});
|
|
271
|
+
it('should NOT flag multer with mimetype validation', async () => {
|
|
272
|
+
const file = await createTestFile('multer-mimetype.ts', `
|
|
273
|
+
// SECURE: Multer with mimetype validation
|
|
274
|
+
const upload = multer({
|
|
275
|
+
storage: multer.diskStorage({
|
|
276
|
+
destination: 'uploads/',
|
|
277
|
+
filename: (req, file, cb) => {
|
|
278
|
+
if (!file.mimetype.startsWith('image/')) {
|
|
279
|
+
return cb(new Error('Invalid file type'));
|
|
280
|
+
}
|
|
281
|
+
cb(null, file.originalname);
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
});
|
|
285
|
+
`);
|
|
286
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
287
|
+
const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-002');
|
|
288
|
+
expect(sanitizeFindings.length).toBe(0);
|
|
289
|
+
});
|
|
290
|
+
it('should NOT flag formidable with file validation', async () => {
|
|
291
|
+
const file = await createTestFile('formidable-secure.ts', `
|
|
292
|
+
// SECURE: Formidable with validation
|
|
293
|
+
const form = new formidable.IncomingForm({
|
|
294
|
+
uploadDir: './uploads',
|
|
295
|
+
maxFileSize: 10 * 1024 * 1024,
|
|
296
|
+
filter: function ({ name, originalFilename, mimetype }) {
|
|
297
|
+
return mimetype && mimetype.includes('image');
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
`);
|
|
301
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
302
|
+
const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-002');
|
|
303
|
+
expect(sanitizeFindings.length).toBe(0);
|
|
304
|
+
});
|
|
305
|
+
it('should NOT flag Busboy with limits and validation', async () => {
|
|
306
|
+
const file = await createTestFile('busboy-secure.ts', `
|
|
307
|
+
// SECURE: Busboy with limits
|
|
308
|
+
const busboy = new Busboy({
|
|
309
|
+
headers: req.headers,
|
|
310
|
+
limits: {
|
|
311
|
+
fileSize: 10 * 1024 * 1024,
|
|
312
|
+
files: 1
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
|
|
317
|
+
if (!mimetype.startsWith('image/')) {
|
|
318
|
+
file.resume();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
file.pipe(fs.createWriteStream('./uploads/' + filename));
|
|
322
|
+
});
|
|
323
|
+
`);
|
|
324
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
325
|
+
const sanitizeFindings = findings.filter((f) => f.id === 'SANITIZE-002');
|
|
326
|
+
expect(sanitizeFindings.length).toBe(0);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
describe('Combined violations', () => {
|
|
330
|
+
it('should detect both SANITIZE-001 and SANITIZE-002 in same file', async () => {
|
|
331
|
+
const file = await createTestFile('combined.ts', `
|
|
332
|
+
const upload = multer({ dest: 'uploads/' });
|
|
333
|
+
|
|
334
|
+
app.post('/upload-profile', upload.single('avatar'), async (req, res) => {
|
|
335
|
+
await db.insert('users').values(req.body);
|
|
336
|
+
});
|
|
337
|
+
`);
|
|
338
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
339
|
+
expect(findings.some((f) => f.id === 'SANITIZE-001')).toBe(true);
|
|
340
|
+
expect(findings.some((f) => f.id === 'SANITIZE-002')).toBe(true);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
it('should provide correct HIPAA references', async () => {
|
|
344
|
+
const file = await createTestFile('hipaa-refs.ts', `
|
|
345
|
+
app.post('/test', async (req, res) => {
|
|
346
|
+
await db.insert('users').values(req.body);
|
|
347
|
+
const upload = multer({ dest: 'uploads/' });
|
|
348
|
+
});
|
|
349
|
+
`);
|
|
350
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
351
|
+
const sanitize001 = findings.find((f) => f.id === 'SANITIZE-001');
|
|
352
|
+
const sanitize002 = findings.find((f) => f.id === 'SANITIZE-002');
|
|
353
|
+
expect(sanitize001?.hipaaReference).toContain('NPRM Anti-malware');
|
|
354
|
+
expect(sanitize002?.hipaaReference).toContain('NPRM Anti-malware');
|
|
355
|
+
});
|
|
356
|
+
it('should have correct severity levels', async () => {
|
|
357
|
+
const file = await createTestFile('severity.ts', `
|
|
358
|
+
app.post('/test', async (req, res) => {
|
|
359
|
+
await db.query(req.body);
|
|
360
|
+
});
|
|
361
|
+
const upload = multer({});
|
|
362
|
+
`);
|
|
363
|
+
const findings = await sanitizationScanner.scan([file], scanOptions);
|
|
364
|
+
const sanitize001 = findings.find((f) => f.id === 'SANITIZE-001');
|
|
365
|
+
const sanitize002 = findings.find((f) => f.id === 'SANITIZE-002');
|
|
366
|
+
expect(sanitize001?.severity).toBe('critical');
|
|
367
|
+
expect(sanitize002?.severity).toBe('high');
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
//# sourceMappingURL=index.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../../src/scanners/sanitization/index.test.ts"],"names":[],"mappings":"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"}
|