verification-layer 0.4.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 +345 -0
- package/dist/audit/evidence.d.ts +25 -0
- package/dist/audit/evidence.d.ts.map +1 -0
- package/dist/audit/evidence.js +70 -0
- package/dist/audit/evidence.js.map +1 -0
- package/dist/audit/index.d.ts +54 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +159 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +199 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +77 -0
- package/dist/config.js.map +1 -0
- package/dist/fixer/index.d.ts +11 -0
- package/dist/fixer/index.d.ts.map +1 -0
- package/dist/fixer/index.js +171 -0
- package/dist/fixer/index.js.map +1 -0
- package/dist/fixer/strategies.d.ts +3 -0
- package/dist/fixer/strategies.d.ts.map +1 -0
- package/dist/fixer/strategies.js +199 -0
- package/dist/fixer/strategies.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/reporters/audit-report.d.ts +13 -0
- package/dist/reporters/audit-report.d.ts.map +1 -0
- package/dist/reporters/audit-report.js +526 -0
- package/dist/reporters/audit-report.js.map +1 -0
- package/dist/reporters/fix-report.d.ts +3 -0
- package/dist/reporters/fix-report.d.ts.map +1 -0
- package/dist/reporters/fix-report.js +70 -0
- package/dist/reporters/fix-report.js.map +1 -0
- package/dist/reporters/index.d.ts +3 -0
- package/dist/reporters/index.d.ts.map +1 -0
- package/dist/reporters/index.js +425 -0
- package/dist/reporters/index.js.map +1 -0
- package/dist/reporters/remediation-guides.d.ts +25 -0
- package/dist/reporters/remediation-guides.d.ts.map +1 -0
- package/dist/reporters/remediation-guides.js +636 -0
- package/dist/reporters/remediation-guides.js.map +1 -0
- package/dist/scan.d.ts +3 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +96 -0
- package/dist/scan.js.map +1 -0
- package/dist/scanners/access/index.d.ts +3 -0
- package/dist/scanners/access/index.d.ts.map +1 -0
- package/dist/scanners/access/index.js +102 -0
- package/dist/scanners/access/index.js.map +1 -0
- package/dist/scanners/audit/index.d.ts +3 -0
- package/dist/scanners/audit/index.d.ts.map +1 -0
- package/dist/scanners/audit/index.js +94 -0
- package/dist/scanners/audit/index.js.map +1 -0
- package/dist/scanners/encryption/index.d.ts +3 -0
- package/dist/scanners/encryption/index.d.ts.map +1 -0
- package/dist/scanners/encryption/index.js +86 -0
- package/dist/scanners/encryption/index.js.map +1 -0
- package/dist/scanners/phi/index.d.ts +3 -0
- package/dist/scanners/phi/index.d.ts.map +1 -0
- package/dist/scanners/phi/index.js +47 -0
- package/dist/scanners/phi/index.js.map +1 -0
- package/dist/scanners/phi/patterns.d.ts +13 -0
- package/dist/scanners/phi/patterns.d.ts.map +1 -0
- package/dist/scanners/phi/patterns.js +242 -0
- package/dist/scanners/phi/patterns.js.map +1 -0
- package/dist/scanners/retention/index.d.ts +3 -0
- package/dist/scanners/retention/index.d.ts.map +1 -0
- package/dist/scanners/retention/index.js +102 -0
- package/dist/scanners/retention/index.js.map +1 -0
- package/dist/scanners/security/index.d.ts +3 -0
- package/dist/scanners/security/index.d.ts.map +1 -0
- package/dist/scanners/security/index.js +280 -0
- package/dist/scanners/security/index.js.map +1 -0
- package/dist/stack-detector/index.d.ts +26 -0
- package/dist/stack-detector/index.d.ts.map +1 -0
- package/dist/stack-detector/index.js +317 -0
- package/dist/stack-detector/index.js.map +1 -0
- package/dist/stack-detector/stack-guides.d.ts +16 -0
- package/dist/stack-detector/stack-guides.d.ts.map +1 -0
- package/dist/stack-detector/stack-guides.js +772 -0
- package/dist/stack-detector/stack-guides.js.map +1 -0
- package/dist/types.d.ts +143 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/context.d.ts +3 -0
- package/dist/utils/context.d.ts.map +1 -0
- package/dist/utils/context.js +14 -0
- package/dist/utils/context.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
export const PHI_PATTERNS = [
|
|
2
|
+
// === SSN and identifiers ===
|
|
3
|
+
{
|
|
4
|
+
id: 'ssn-hardcoded',
|
|
5
|
+
regex: /\b\d{3}-\d{2}-\d{4}\b/,
|
|
6
|
+
severity: 'critical',
|
|
7
|
+
title: 'Potential SSN detected',
|
|
8
|
+
description: 'A pattern matching Social Security Number format was found in the code.',
|
|
9
|
+
recommendation: 'Remove hardcoded SSN. Use secure storage and encryption for sensitive identifiers.',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: 'medical-record-number',
|
|
13
|
+
regex: /\b(mrn|medical.?record.?number)\s*[:=]\s*['"`]\d+['"`]/i,
|
|
14
|
+
severity: 'high',
|
|
15
|
+
title: 'Medical Record Number exposure',
|
|
16
|
+
description: 'A hardcoded medical record number was detected.',
|
|
17
|
+
recommendation: 'Never hardcode MRNs. Fetch from secure, encrypted storage.',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'dob-exposed',
|
|
21
|
+
regex: /\b(date.?of.?birth|dob|birth.?date)\s*[:=]\s*['"`]/i,
|
|
22
|
+
severity: 'high',
|
|
23
|
+
title: 'Date of birth exposure',
|
|
24
|
+
description: 'Date of birth information may be hardcoded or improperly handled.',
|
|
25
|
+
recommendation: 'Encrypt DOB at rest and in transit. Apply minimum necessary principle.',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'diagnosis-code',
|
|
29
|
+
regex: /\b(icd.?10|diagnosis.?code|icd.?code)\s*[:=]\s*['"`][A-Z]\d{2}/i,
|
|
30
|
+
severity: 'medium',
|
|
31
|
+
title: 'Diagnosis code in source',
|
|
32
|
+
description: 'ICD-10 diagnosis codes found in source code.',
|
|
33
|
+
recommendation: 'Load diagnosis codes from secure configuration, not source code.',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'phi-in-url',
|
|
37
|
+
regex: /\/(patient|user)\/\d+\/(ssn|dob|mrn|diagnosis)/i,
|
|
38
|
+
severity: 'high',
|
|
39
|
+
title: 'PHI identifier in URL pattern',
|
|
40
|
+
description: 'URL pattern suggests PHI may be exposed in URLs.',
|
|
41
|
+
recommendation: 'Never include PHI in URLs. Use opaque tokens or encrypted identifiers.',
|
|
42
|
+
},
|
|
43
|
+
// === PHI Logging Detection ===
|
|
44
|
+
{
|
|
45
|
+
id: 'patient-name-log',
|
|
46
|
+
regex: /console\.(log|info|debug|warn|error)\s*\([^)]*patient.*name/i,
|
|
47
|
+
severity: 'high',
|
|
48
|
+
title: 'Patient name in console output',
|
|
49
|
+
description: 'Patient names may be logged to console, exposing PHI.',
|
|
50
|
+
recommendation: 'Remove patient identifiers from logs. Use anonymized IDs for debugging.',
|
|
51
|
+
fixType: 'phi-console-log',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'phi-console-log',
|
|
55
|
+
regex: /console\.(log|info|debug|warn|error)\s*\([^)]*(ssn|social.?security|diagnosis|medical.?record|health.?info|patient.?data|dob|birth.?date)/i,
|
|
56
|
+
severity: 'high',
|
|
57
|
+
title: 'PHI data in console output',
|
|
58
|
+
description: 'Sensitive health information may be logged to console.',
|
|
59
|
+
recommendation: 'Never log PHI to console. Use structured logging with PHI redaction.',
|
|
60
|
+
fixType: 'phi-console-log',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'phi-json-stringify-log',
|
|
64
|
+
regex: /console\.(log|info|debug)\s*\(\s*JSON\.stringify\s*\([^)]*patient/i,
|
|
65
|
+
severity: 'high',
|
|
66
|
+
title: 'Patient object serialized to console',
|
|
67
|
+
description: 'Patient objects are being serialized and logged, potentially exposing all PHI fields.',
|
|
68
|
+
recommendation: 'Create a sanitized version of patient objects for logging, excluding PHI fields.',
|
|
69
|
+
fixType: 'phi-console-log',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'phi-template-log',
|
|
73
|
+
regex: /console\.(log|info|debug)\s*\(\s*`[^`]*(patient|ssn|diagnosis|dob|\$\{.*patient)/i,
|
|
74
|
+
severity: 'high',
|
|
75
|
+
title: 'PHI in template literal log',
|
|
76
|
+
description: 'Template literal logging may expose PHI data.',
|
|
77
|
+
recommendation: 'Avoid interpolating PHI into log messages.',
|
|
78
|
+
fixType: 'phi-console-log',
|
|
79
|
+
},
|
|
80
|
+
// === Insecure Storage Detection ===
|
|
81
|
+
{
|
|
82
|
+
id: 'phi-localstorage',
|
|
83
|
+
regex: /localStorage\.(setItem|getItem)\s*\(\s*['"`][^'"`]*(patient|ssn|diagnosis|medical|health|dob|mrn)/i,
|
|
84
|
+
severity: 'critical',
|
|
85
|
+
title: 'PHI stored in localStorage',
|
|
86
|
+
description: 'PHI data is being stored in localStorage which is not encrypted and persists indefinitely.',
|
|
87
|
+
recommendation: 'Never store PHI in localStorage. Use encrypted server-side storage with proper access controls.',
|
|
88
|
+
fixType: 'phi-localstorage',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: 'phi-sessionstorage',
|
|
92
|
+
regex: /sessionStorage\.(setItem|getItem)\s*\(\s*['"`][^'"`]*(patient|ssn|diagnosis|medical|health|dob|mrn)/i,
|
|
93
|
+
severity: 'high',
|
|
94
|
+
title: 'PHI stored in sessionStorage',
|
|
95
|
+
description: 'PHI data is being stored in sessionStorage which is not encrypted.',
|
|
96
|
+
recommendation: 'Avoid storing PHI in browser storage. Use secure, encrypted server-side sessions.',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'phi-cookie-storage',
|
|
100
|
+
regex: /document\.cookie\s*=.*?(patient|ssn|diagnosis|medical|health|dob|mrn)/i,
|
|
101
|
+
severity: 'critical',
|
|
102
|
+
title: 'PHI stored in cookies',
|
|
103
|
+
description: 'PHI data may be stored in browser cookies without encryption.',
|
|
104
|
+
recommendation: 'Never store PHI in cookies. Use encrypted server-side sessions with secure, httpOnly cookies for session IDs only.',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 'phi-indexeddb',
|
|
108
|
+
regex: /indexedDB|IDBDatabase.*?(patient|health|medical|diagnosis)/i,
|
|
109
|
+
severity: 'high',
|
|
110
|
+
title: 'PHI potentially stored in IndexedDB',
|
|
111
|
+
description: 'PHI may be stored in IndexedDB which lacks built-in encryption.',
|
|
112
|
+
recommendation: 'If using IndexedDB for PHI, implement client-side encryption and proper key management.',
|
|
113
|
+
},
|
|
114
|
+
// === Email and contact PHI ===
|
|
115
|
+
{
|
|
116
|
+
id: 'email-phi-context',
|
|
117
|
+
regex: /patient.*email|email.*patient/i,
|
|
118
|
+
severity: 'medium',
|
|
119
|
+
title: 'Patient email handling detected',
|
|
120
|
+
description: 'Code handles patient email addresses which are PHI.',
|
|
121
|
+
recommendation: 'Ensure patient emails are encrypted and access is logged.',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'phone-phi-context',
|
|
125
|
+
regex: /patient.*(phone|mobile|cell)|phone.*patient/i,
|
|
126
|
+
severity: 'medium',
|
|
127
|
+
title: 'Patient phone handling detected',
|
|
128
|
+
description: 'Code handles patient phone numbers which are PHI.',
|
|
129
|
+
recommendation: 'Ensure patient contact info is encrypted and access is logged.',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: 'address-phi-context',
|
|
133
|
+
regex: /patient.*(address|street|city|zip)|address.*patient/i,
|
|
134
|
+
severity: 'medium',
|
|
135
|
+
title: 'Patient address handling detected',
|
|
136
|
+
description: 'Code handles patient addresses which are PHI.',
|
|
137
|
+
recommendation: 'Ensure patient addresses are encrypted and access is logged.',
|
|
138
|
+
},
|
|
139
|
+
// === PHI in URL Query Parameters ===
|
|
140
|
+
{
|
|
141
|
+
id: 'phi-query-param',
|
|
142
|
+
regex: /[?&](ssn|social.?security|patient.?id|mrn|dob|birth.?date|diagnosis|medical.?record)=/i,
|
|
143
|
+
severity: 'critical',
|
|
144
|
+
title: 'PHI in URL query parameter',
|
|
145
|
+
description: 'PHI identifiers are being passed as URL query parameters, which may be logged in server logs, browser history, and referrer headers.',
|
|
146
|
+
recommendation: 'Never pass PHI in URLs. Use POST requests with encrypted body or session-based lookups.',
|
|
147
|
+
fixType: 'phi-url-param',
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: 'phi-url-interpolation',
|
|
151
|
+
regex: /\$\{[^}]*(ssn|patientId|mrn|dob|diagnosis)[^}]*\}.*[?&]/i,
|
|
152
|
+
severity: 'critical',
|
|
153
|
+
title: 'PHI interpolated into URL',
|
|
154
|
+
description: 'PHI values are being interpolated into URLs, exposing sensitive data.',
|
|
155
|
+
recommendation: 'Use opaque tokens or encrypted references instead of PHI in URLs.',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: 'phi-fetch-url',
|
|
159
|
+
regex: /fetch\s*\(\s*`[^`]*[?&](patient|ssn|mrn|dob)/i,
|
|
160
|
+
severity: 'high',
|
|
161
|
+
title: 'PHI in fetch URL',
|
|
162
|
+
description: 'Fetch request URL contains PHI identifiers.',
|
|
163
|
+
recommendation: 'Pass PHI in request body with proper encryption, not in URLs.',
|
|
164
|
+
},
|
|
165
|
+
// === PHI in HTTP Headers ===
|
|
166
|
+
{
|
|
167
|
+
id: 'phi-header-set',
|
|
168
|
+
regex: /setHeader\s*\(\s*['"`][^'"`]*(patient|ssn|mrn|dob|diagnosis|medical)/i,
|
|
169
|
+
severity: 'critical',
|
|
170
|
+
title: 'PHI in HTTP header',
|
|
171
|
+
description: 'PHI data is being set in HTTP headers, which may be logged or cached.',
|
|
172
|
+
recommendation: 'Never transmit PHI in HTTP headers. Use encrypted request/response body.',
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: 'phi-header-object',
|
|
176
|
+
regex: /headers\s*[:=]\s*\{[^}]*(patient|ssn|mrn|dob|diagnosis|x-patient|x-medical)/i,
|
|
177
|
+
severity: 'critical',
|
|
178
|
+
title: 'PHI in headers object',
|
|
179
|
+
description: 'Headers object contains PHI-related fields.',
|
|
180
|
+
recommendation: 'Remove PHI from headers. Transmit sensitive data in encrypted request body only.',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: 'phi-authorization-header',
|
|
184
|
+
regex: /authorization.*patient|patient.*authorization/i,
|
|
185
|
+
severity: 'high',
|
|
186
|
+
title: 'Patient data in authorization context',
|
|
187
|
+
description: 'Patient identifiers may be exposed in authorization headers.',
|
|
188
|
+
recommendation: 'Use opaque session tokens for authorization, not patient identifiers.',
|
|
189
|
+
},
|
|
190
|
+
// === PHI in Email ===
|
|
191
|
+
{
|
|
192
|
+
id: 'phi-email-body',
|
|
193
|
+
regex: /(sendMail|sendEmail|send_email|mailer\.send)\s*\([^)]*patient/i,
|
|
194
|
+
severity: 'high',
|
|
195
|
+
title: 'PHI in email content',
|
|
196
|
+
description: 'Patient data may be included in email body, which is typically unencrypted.',
|
|
197
|
+
recommendation: 'Avoid sending PHI via email. Use secure patient portals with authentication.',
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: 'phi-email-template',
|
|
201
|
+
regex: /(email|mail).*template.*patient|patient.*(email|mail).*template/i,
|
|
202
|
+
severity: 'high',
|
|
203
|
+
title: 'PHI in email template',
|
|
204
|
+
description: 'Email templates may contain patient data placeholders.',
|
|
205
|
+
recommendation: 'Do not include PHI in email templates. Send secure links to authenticated portals instead.',
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: 'phi-email-subject',
|
|
209
|
+
regex: /subject\s*[:=].*patient|subject.*diagnosis|subject.*medical/i,
|
|
210
|
+
severity: 'medium',
|
|
211
|
+
title: 'Potential PHI in email subject',
|
|
212
|
+
description: 'Email subject lines may contain patient-related information.',
|
|
213
|
+
recommendation: 'Keep email subjects generic. Never include patient names, diagnoses, or identifiers.',
|
|
214
|
+
},
|
|
215
|
+
// === PHI Logging Without Redaction ===
|
|
216
|
+
{
|
|
217
|
+
id: 'phi-logger-unredacted',
|
|
218
|
+
regex: /logger\.(info|debug|warn|error)\s*\([^)]*patient(?!.*redact|.*mask|.*sanitize)/i,
|
|
219
|
+
severity: 'high',
|
|
220
|
+
title: 'PHI logged without redaction',
|
|
221
|
+
description: 'Patient data is being logged without apparent redaction or masking.',
|
|
222
|
+
recommendation: 'Implement PHI redaction in logging. Use structured logging with automatic PII masking.',
|
|
223
|
+
fixType: 'phi-log-unredacted',
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
id: 'phi-log-file',
|
|
227
|
+
regex: /writeFile.*log.*patient|patient.*writeFile.*log/i,
|
|
228
|
+
severity: 'high',
|
|
229
|
+
title: 'PHI written to log file',
|
|
230
|
+
description: 'Patient data may be written directly to log files.',
|
|
231
|
+
recommendation: 'Use structured logging with PHI redaction. Never write raw PHI to log files.',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
id: 'phi-debug-output',
|
|
235
|
+
regex: /debug\s*[:=]\s*true.*patient|patient.*debug\s*[:=]\s*true/i,
|
|
236
|
+
severity: 'medium',
|
|
237
|
+
title: 'Debug mode with patient data',
|
|
238
|
+
description: 'Debug mode enabled in code handling patient data may expose PHI.',
|
|
239
|
+
recommendation: 'Ensure debug logging excludes PHI or uses proper redaction.',
|
|
240
|
+
},
|
|
241
|
+
];
|
|
242
|
+
//# sourceMappingURL=patterns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../../src/scanners/phi/patterns.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/scanners/retention/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAwB,MAAM,gBAAgB,CAAC;AAuDpE,eAAO,MAAM,gBAAgB,EAAE,OAiD9B,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { DEFAULT_CONFIG } from '../../config.js';
|
|
3
|
+
import { getContextLines } from '../../utils/context.js';
|
|
4
|
+
const RETENTION_ISSUES = [
|
|
5
|
+
{
|
|
6
|
+
regex: /deleteAfter\s*[:=]\s*(\d+)\s*(day|hour|minute)/i,
|
|
7
|
+
id: 'short-retention',
|
|
8
|
+
check: (match) => {
|
|
9
|
+
const value = parseInt(match[1]);
|
|
10
|
+
const unit = match[2].toLowerCase();
|
|
11
|
+
// HIPAA requires 6 years minimum for most records
|
|
12
|
+
if (unit === 'day' && value < 2190)
|
|
13
|
+
return true; // ~6 years in days
|
|
14
|
+
if (unit === 'hour' || unit === 'minute')
|
|
15
|
+
return true;
|
|
16
|
+
return false;
|
|
17
|
+
},
|
|
18
|
+
severity: 'high',
|
|
19
|
+
title: 'PHI retention period may be too short',
|
|
20
|
+
description: 'Data deletion configured with period shorter than HIPAA requirements.',
|
|
21
|
+
recommendation: 'HIPAA requires PHI retention for 6 years from creation or last effective date.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
regex: /\.delete\s*\(\s*\)(?!.*audit|.*log)/i,
|
|
25
|
+
id: 'unlogged-delete',
|
|
26
|
+
severity: 'medium',
|
|
27
|
+
title: 'Data deletion without apparent logging',
|
|
28
|
+
description: 'Data deletion operation without visible audit logging.',
|
|
29
|
+
recommendation: 'Log all PHI deletions with timestamp, user, and record identifiers.',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
regex: /truncate\s+table|drop\s+table/i,
|
|
33
|
+
id: 'bulk-delete',
|
|
34
|
+
severity: 'critical',
|
|
35
|
+
title: 'Bulk data deletion operation',
|
|
36
|
+
description: 'Bulk deletion (TRUNCATE/DROP) could delete PHI without proper retention.',
|
|
37
|
+
recommendation: 'Implement soft-delete with retention periods before permanent deletion.',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
regex: /backup.*disable|disable.*backup/i,
|
|
41
|
+
id: 'backup-disabled',
|
|
42
|
+
severity: 'high',
|
|
43
|
+
title: 'Backup may be disabled',
|
|
44
|
+
description: 'Code pattern suggests backups might be disabled.',
|
|
45
|
+
recommendation: 'Maintain encrypted backups with proper retention for disaster recovery.',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
regex: /cache.*patient|patient.*cache/i,
|
|
49
|
+
id: 'phi-cache',
|
|
50
|
+
severity: 'medium',
|
|
51
|
+
title: 'PHI caching detected',
|
|
52
|
+
description: 'Patient data may be cached, requiring retention policy consideration.',
|
|
53
|
+
recommendation: 'Ensure cached PHI has appropriate TTL and is encrypted at rest.',
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
export const retentionScanner = {
|
|
57
|
+
name: 'Data Retention Scanner',
|
|
58
|
+
category: 'data-retention',
|
|
59
|
+
async scan(files, options) {
|
|
60
|
+
const findings = [];
|
|
61
|
+
const config = options.config ?? DEFAULT_CONFIG;
|
|
62
|
+
const contextSize = config.contextLines ?? 2;
|
|
63
|
+
const codeExtensions = ['.ts', '.js', '.tsx', '.jsx', '.py', '.java', '.go', '.rb', '.php', '.sql', '.yaml', '.yml'];
|
|
64
|
+
const codeFiles = files.filter(f => codeExtensions.some(ext => f.endsWith(ext)));
|
|
65
|
+
for (const filePath of codeFiles) {
|
|
66
|
+
try {
|
|
67
|
+
const content = await readFile(filePath, 'utf-8');
|
|
68
|
+
const lines = content.split('\n');
|
|
69
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
70
|
+
const line = lines[lineNum];
|
|
71
|
+
for (const issue of RETENTION_ISSUES) {
|
|
72
|
+
const match = line.match(issue.regex);
|
|
73
|
+
if (match) {
|
|
74
|
+
// Check if there's a custom check function
|
|
75
|
+
if ('check' in issue && issue.check) {
|
|
76
|
+
if (!issue.check(match))
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
findings.push({
|
|
80
|
+
id: `retention-${issue.id}-${lineNum}`,
|
|
81
|
+
category: 'data-retention',
|
|
82
|
+
severity: issue.severity,
|
|
83
|
+
title: issue.title,
|
|
84
|
+
description: issue.description,
|
|
85
|
+
file: filePath,
|
|
86
|
+
line: lineNum + 1,
|
|
87
|
+
recommendation: issue.recommendation,
|
|
88
|
+
hipaaReference: '§164.530(j)',
|
|
89
|
+
context: getContextLines(lines, lineNum, contextSize),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Skip files that can't be read
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return findings;
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/scanners/retention/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,gBAAgB,GAAG;IACvB;QACE,KAAK,EAAE,iDAAiD;QACxD,EAAE,EAAE,iBAAiB;QACrB,KAAK,EAAE,CAAC,KAAuB,EAAE,EAAE;YACjC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACjC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YACpC,kDAAkD;YAClD,IAAI,IAAI,KAAK,KAAK,IAAI,KAAK,GAAG,IAAI;gBAAE,OAAO,IAAI,CAAC,CAAC,mBAAmB;YACpE,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAC;YACtD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,QAAQ,EAAE,MAAe;QACzB,KAAK,EAAE,uCAAuC;QAC9C,WAAW,EAAE,uEAAuE;QACpF,cAAc,EAAE,gFAAgF;KACjG;IACD;QACE,KAAK,EAAE,sCAAsC;QAC7C,EAAE,EAAE,iBAAiB;QACrB,QAAQ,EAAE,QAAiB;QAC3B,KAAK,EAAE,wCAAwC;QAC/C,WAAW,EAAE,wDAAwD;QACrE,cAAc,EAAE,qEAAqE;KACtF;IACD;QACE,KAAK,EAAE,gCAAgC;QACvC,EAAE,EAAE,aAAa;QACjB,QAAQ,EAAE,UAAmB;QAC7B,KAAK,EAAE,8BAA8B;QACrC,WAAW,EAAE,0EAA0E;QACvF,cAAc,EAAE,yEAAyE;KAC1F;IACD;QACE,KAAK,EAAE,kCAAkC;QACzC,EAAE,EAAE,iBAAiB;QACrB,QAAQ,EAAE,MAAe;QACzB,KAAK,EAAE,wBAAwB;QAC/B,WAAW,EAAE,kDAAkD;QAC/D,cAAc,EAAE,yEAAyE;KAC1F;IACD;QACE,KAAK,EAAE,gCAAgC;QACvC,EAAE,EAAE,WAAW;QACf,QAAQ,EAAE,QAAiB;QAC3B,KAAK,EAAE,sBAAsB;QAC7B,WAAW,EAAE,uEAAuE;QACpF,cAAc,EAAE,iEAAiE;KAClF;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAY;IACvC,IAAI,EAAE,wBAAwB;IAC9B,QAAQ,EAAE,gBAAgB;IAE1B,KAAK,CAAC,IAAI,CAAC,KAAe,EAAE,OAAoB;QAC9C,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,cAAc,CAAC;QAChD,MAAM,WAAW,GAAG,MAAM,CAAC,YAAY,IAAI,CAAC,CAAC;QAC7C,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACrH,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAEjF,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAClD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAElC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC;oBACxD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;oBAE5B,KAAK,MAAM,KAAK,IAAI,gBAAgB,EAAE,CAAC;wBACrC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;wBACtC,IAAI,KAAK,EAAE,CAAC;4BACV,2CAA2C;4BAC3C,IAAI,OAAO,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;gCACpC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;oCAAE,SAAS;4BACpC,CAAC;4BAED,QAAQ,CAAC,IAAI,CAAC;gCACZ,EAAE,EAAE,aAAa,KAAK,CAAC,EAAE,IAAI,OAAO,EAAE;gCACtC,QAAQ,EAAE,gBAAgB;gCAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ;gCACxB,KAAK,EAAE,KAAK,CAAC,KAAK;gCAClB,WAAW,EAAE,KAAK,CAAC,WAAW;gCAC9B,IAAI,EAAE,QAAQ;gCACd,IAAI,EAAE,OAAO,GAAG,CAAC;gCACjB,cAAc,EAAE,KAAK,CAAC,cAAc;gCACpC,cAAc,EAAE,aAAa;gCAC7B,OAAO,EAAE,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC;6BACtD,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,gCAAgC;YAClC,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/scanners/security/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAiC,MAAM,gBAAgB,CAAC;AAuP7E,eAAO,MAAM,eAAe,EAAE,OAoD7B,CAAC"}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { DEFAULT_CONFIG } from '../../config.js';
|
|
3
|
+
import { getContextLines } from '../../utils/context.js';
|
|
4
|
+
const SECURITY_PATTERNS = [
|
|
5
|
+
// === Hardcoded Passwords ===
|
|
6
|
+
{
|
|
7
|
+
regex: /password\s*[:=]\s*['"`][^'"`]{4,}['"`]/i,
|
|
8
|
+
id: 'hardcoded-password',
|
|
9
|
+
severity: 'critical',
|
|
10
|
+
title: 'Hardcoded password detected',
|
|
11
|
+
description: 'A password appears to be hardcoded in the source code.',
|
|
12
|
+
recommendation: 'Use environment variables or a secrets manager for credentials. Never commit passwords to source control.',
|
|
13
|
+
fixType: 'hardcoded-password',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
regex: /pwd\s*[:=]\s*['"`][^'"`]{4,}['"`]/i,
|
|
17
|
+
id: 'hardcoded-pwd',
|
|
18
|
+
severity: 'critical',
|
|
19
|
+
title: 'Hardcoded password (pwd) detected',
|
|
20
|
+
description: 'A password appears to be hardcoded using "pwd" variable.',
|
|
21
|
+
recommendation: 'Use environment variables or a secrets manager for credentials.',
|
|
22
|
+
fixType: 'hardcoded-password',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
regex: /secret\s*[:=]\s*['"`][^'"`]{8,}['"`]/i,
|
|
26
|
+
id: 'hardcoded-secret',
|
|
27
|
+
severity: 'critical',
|
|
28
|
+
title: 'Hardcoded secret detected',
|
|
29
|
+
description: 'A secret value appears to be hardcoded in the source code.',
|
|
30
|
+
recommendation: 'Use environment variables or a secrets manager for secrets.',
|
|
31
|
+
fixType: 'hardcoded-secret',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
regex: /credentials?\s*[:=]\s*\{[^}]*password\s*:/i,
|
|
35
|
+
id: 'credentials-object',
|
|
36
|
+
severity: 'high',
|
|
37
|
+
title: 'Credentials object with password',
|
|
38
|
+
description: 'A credentials object containing password field was detected.',
|
|
39
|
+
recommendation: 'Load credentials from secure configuration, not source code.',
|
|
40
|
+
},
|
|
41
|
+
// === API Keys Exposure ===
|
|
42
|
+
{
|
|
43
|
+
regex: /api[_-]?key\s*[:=]\s*['"`][A-Za-z0-9_-]{20,}['"`]/i,
|
|
44
|
+
id: 'api-key-exposed',
|
|
45
|
+
severity: 'critical',
|
|
46
|
+
title: 'API key exposed in source',
|
|
47
|
+
description: 'An API key appears to be hardcoded in the source code.',
|
|
48
|
+
recommendation: 'Use environment variables for API keys. Add to .gitignore and use .env files.',
|
|
49
|
+
fixType: 'api-key-exposed',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
regex: /apikey\s*[:=]\s*['"`][A-Za-z0-9_-]{20,}['"`]/i,
|
|
53
|
+
id: 'apikey-exposed',
|
|
54
|
+
severity: 'critical',
|
|
55
|
+
title: 'API key (apikey) exposed in source',
|
|
56
|
+
description: 'An API key appears to be hardcoded.',
|
|
57
|
+
recommendation: 'Use environment variables for API keys.',
|
|
58
|
+
fixType: 'api-key-exposed',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
regex: /(sk|pk)[_-](live|test)[_-][A-Za-z0-9]{20,}/i,
|
|
62
|
+
id: 'stripe-key-exposed',
|
|
63
|
+
severity: 'critical',
|
|
64
|
+
title: 'Stripe API key exposed',
|
|
65
|
+
description: 'A Stripe API key pattern was detected in the source code.',
|
|
66
|
+
recommendation: 'Never commit Stripe keys. Use environment variables and restrict key permissions.',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
regex: /AKIA[0-9A-Z]{16}/,
|
|
70
|
+
id: 'aws-key-exposed',
|
|
71
|
+
severity: 'critical',
|
|
72
|
+
title: 'AWS Access Key exposed',
|
|
73
|
+
description: 'An AWS Access Key ID pattern was detected.',
|
|
74
|
+
recommendation: 'Rotate this key immediately. Use IAM roles or environment variables instead.',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
regex: /bearer\s+[A-Za-z0-9_.-]{20,}/i,
|
|
78
|
+
id: 'bearer-token-exposed',
|
|
79
|
+
severity: 'high',
|
|
80
|
+
title: 'Bearer token in source',
|
|
81
|
+
description: 'A bearer token appears to be hardcoded.',
|
|
82
|
+
recommendation: 'Tokens should be fetched at runtime, not hardcoded.',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
regex: /auth[_-]?token\s*[:=]\s*['"`][A-Za-z0-9_.-]{20,}['"`]/i,
|
|
86
|
+
id: 'auth-token-exposed',
|
|
87
|
+
severity: 'critical',
|
|
88
|
+
title: 'Auth token exposed in source',
|
|
89
|
+
description: 'An authentication token appears to be hardcoded.',
|
|
90
|
+
recommendation: 'Use secure token management. Never commit tokens to source control.',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
regex: /private[_-]?key\s*[:=]\s*['"`]-----BEGIN/i,
|
|
94
|
+
id: 'private-key-exposed',
|
|
95
|
+
severity: 'critical',
|
|
96
|
+
title: 'Private key exposed in source',
|
|
97
|
+
description: 'A private key appears to be embedded in source code.',
|
|
98
|
+
recommendation: 'Never commit private keys. Use secure key management services.',
|
|
99
|
+
},
|
|
100
|
+
// === Database Credentials ===
|
|
101
|
+
{
|
|
102
|
+
regex: /mongodb(\+srv)?:\/\/[^:]+:[^@]+@/i,
|
|
103
|
+
id: 'mongodb-uri-credentials',
|
|
104
|
+
severity: 'critical',
|
|
105
|
+
title: 'MongoDB URI with credentials',
|
|
106
|
+
description: 'A MongoDB connection string with embedded credentials was detected.',
|
|
107
|
+
recommendation: 'Use environment variables for database connection strings.',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
regex: /postgres(ql)?:\/\/[^:]+:[^@]+@/i,
|
|
111
|
+
id: 'postgres-uri-credentials',
|
|
112
|
+
severity: 'critical',
|
|
113
|
+
title: 'PostgreSQL URI with credentials',
|
|
114
|
+
description: 'A PostgreSQL connection string with embedded credentials was detected.',
|
|
115
|
+
recommendation: 'Use environment variables for database connection strings.',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
regex: /mysql:\/\/[^:]+:[^@]+@/i,
|
|
119
|
+
id: 'mysql-uri-credentials',
|
|
120
|
+
severity: 'critical',
|
|
121
|
+
title: 'MySQL URI with credentials',
|
|
122
|
+
description: 'A MySQL connection string with embedded credentials was detected.',
|
|
123
|
+
recommendation: 'Use environment variables for database connection strings.',
|
|
124
|
+
},
|
|
125
|
+
// === Input Sanitization Issues ===
|
|
126
|
+
{
|
|
127
|
+
regex: /\.innerHTML\s*=\s*[^'"`\s;]+/i,
|
|
128
|
+
id: 'innerhtml-unsanitized',
|
|
129
|
+
severity: 'high',
|
|
130
|
+
title: 'Unsanitized innerHTML assignment',
|
|
131
|
+
description: 'Direct innerHTML assignment without sanitization can lead to XSS vulnerabilities.',
|
|
132
|
+
recommendation: 'Use textContent for text, or sanitize HTML with DOMPurify before innerHTML assignment.',
|
|
133
|
+
fixType: 'innerhtml-unsanitized',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
regex: /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html:/i,
|
|
137
|
+
id: 'dangerous-innerhtml-react',
|
|
138
|
+
severity: 'high',
|
|
139
|
+
title: 'dangerouslySetInnerHTML usage',
|
|
140
|
+
description: 'Using dangerouslySetInnerHTML can expose the application to XSS attacks.',
|
|
141
|
+
recommendation: 'Sanitize content with DOMPurify before using dangerouslySetInnerHTML.',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
regex: /eval\s*\(\s*[^)]*\)/i,
|
|
145
|
+
id: 'eval-usage',
|
|
146
|
+
severity: 'critical',
|
|
147
|
+
title: 'eval() usage detected',
|
|
148
|
+
description: 'Using eval() can execute arbitrary code and is a security risk.',
|
|
149
|
+
recommendation: 'Avoid eval(). Use safer alternatives like JSON.parse() for data parsing.',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
regex: /new\s+Function\s*\([^)]*\)/i,
|
|
153
|
+
id: 'function-constructor',
|
|
154
|
+
severity: 'high',
|
|
155
|
+
title: 'Function constructor usage',
|
|
156
|
+
description: 'The Function constructor can execute arbitrary code like eval().',
|
|
157
|
+
recommendation: 'Avoid dynamic code execution. Use predefined functions instead.',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
regex: /document\.write\s*\(/i,
|
|
161
|
+
id: 'document-write',
|
|
162
|
+
severity: 'medium',
|
|
163
|
+
title: 'document.write usage',
|
|
164
|
+
description: 'document.write can be exploited for XSS and blocks page rendering.',
|
|
165
|
+
recommendation: 'Use DOM manipulation methods (appendChild, insertAdjacentHTML) instead.',
|
|
166
|
+
},
|
|
167
|
+
// === SQL Injection Risks ===
|
|
168
|
+
{
|
|
169
|
+
regex: /\b(SELECT|INSERT|UPDATE|DELETE)\b.*['"`]\s*\+\s*\w+\s*\+?\s*['"`]?/i,
|
|
170
|
+
id: 'sql-string-concat',
|
|
171
|
+
severity: 'critical',
|
|
172
|
+
title: 'SQL query string concatenation',
|
|
173
|
+
description: 'Building SQL queries with string concatenation is vulnerable to SQL injection.',
|
|
174
|
+
recommendation: 'Use parameterized queries or prepared statements. Never concatenate user input into SQL.',
|
|
175
|
+
fixType: 'sql-injection-concat',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
regex: /\bSELECT\b.*\$\{[^}]+\}|\bWHERE\b.*\$\{[^}]+\}|\bINSERT\s+INTO\b.*\$\{[^}]+\}|\bUPDATE\b.*\bSET\b.*\$\{[^}]+\}|\bDELETE\s+FROM\b.*\$\{[^}]+\}/i,
|
|
179
|
+
id: 'sql-template-literal',
|
|
180
|
+
severity: 'critical',
|
|
181
|
+
title: 'SQL query with template literal interpolation',
|
|
182
|
+
description: 'Interpolating variables directly into SQL queries enables SQL injection.',
|
|
183
|
+
recommendation: 'Use parameterized queries. Pass variables as parameters, not interpolated strings.',
|
|
184
|
+
fixType: 'sql-injection-template',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
regex: /\.(query|execute|raw|sql)\s*\(\s*`[^`]*\$\{/i,
|
|
188
|
+
id: 'query-template-injection',
|
|
189
|
+
severity: 'critical',
|
|
190
|
+
title: 'Database query with template interpolation',
|
|
191
|
+
description: 'Template literal interpolation in database queries can lead to injection attacks.',
|
|
192
|
+
recommendation: 'Use parameterized queries: query("SELECT * FROM users WHERE id = $1", [userId])',
|
|
193
|
+
fixType: 'sql-injection-template',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
regex: /\.(query|execute|sql)\s*\(\s*['"][^'"]*['"]\s*\+/i,
|
|
197
|
+
id: 'execute-string-concat',
|
|
198
|
+
severity: 'critical',
|
|
199
|
+
title: 'SQL execute with string concatenation',
|
|
200
|
+
description: 'Concatenating strings in SQL execute statements enables injection.',
|
|
201
|
+
recommendation: 'Use parameterized queries instead of string concatenation.',
|
|
202
|
+
fixType: 'sql-injection-concat',
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
regex: /\$\{[^}]+\}\s*\)\s*$.*\b(SELECT|INSERT|UPDATE|DELETE)\b/i,
|
|
206
|
+
id: 'raw-query-injection',
|
|
207
|
+
severity: 'critical',
|
|
208
|
+
title: 'Raw SQL query with interpolation',
|
|
209
|
+
description: 'Raw SQL queries with interpolated values are vulnerable to injection.',
|
|
210
|
+
recommendation: 'Even with raw queries, use parameter binding for user-supplied values.',
|
|
211
|
+
fixType: 'sql-injection-template',
|
|
212
|
+
},
|
|
213
|
+
// === Cookie Security ===
|
|
214
|
+
{
|
|
215
|
+
regex: /res\.cookie\s*\([^)]+\)(?!.*httpOnly)/i,
|
|
216
|
+
id: 'cookie-no-httponly',
|
|
217
|
+
severity: 'high',
|
|
218
|
+
title: 'Cookie without httpOnly flag',
|
|
219
|
+
description: 'Cookies without httpOnly can be accessed by JavaScript, enabling XSS attacks.',
|
|
220
|
+
recommendation: 'Add httpOnly: true and secure: true to all cookies, especially session cookies.',
|
|
221
|
+
fixType: 'cookie-insecure',
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
regex: /cookie\s*:\s*\{[^}]*\}(?!.*httpOnly)/i,
|
|
225
|
+
id: 'cookie-config-insecure',
|
|
226
|
+
severity: 'high',
|
|
227
|
+
title: 'Cookie configuration missing security flags',
|
|
228
|
+
description: 'Cookie configuration is missing httpOnly and/or secure flags.',
|
|
229
|
+
recommendation: 'Always set httpOnly: true, secure: true for cookies handling sensitive data.',
|
|
230
|
+
fixType: 'cookie-insecure',
|
|
231
|
+
},
|
|
232
|
+
];
|
|
233
|
+
export const securityScanner = {
|
|
234
|
+
name: 'Security Scanner',
|
|
235
|
+
category: 'access-control', // Using access-control category for now
|
|
236
|
+
async scan(files, options) {
|
|
237
|
+
const findings = [];
|
|
238
|
+
const config = options.config ?? DEFAULT_CONFIG;
|
|
239
|
+
const contextSize = config.contextLines ?? 2;
|
|
240
|
+
const codeExtensions = ['.ts', '.js', '.tsx', '.jsx', '.py', '.java', '.go', '.rb', '.php', '.env', '.sql'];
|
|
241
|
+
const codeFiles = files.filter(f => codeExtensions.some(ext => f.endsWith(ext)));
|
|
242
|
+
for (const filePath of codeFiles) {
|
|
243
|
+
// Skip test files for some patterns to reduce noise
|
|
244
|
+
const isTestFile = filePath.includes('test') || filePath.includes('spec') || filePath.includes('mock');
|
|
245
|
+
try {
|
|
246
|
+
const content = await readFile(filePath, 'utf-8');
|
|
247
|
+
const lines = content.split('\n');
|
|
248
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
249
|
+
const line = lines[lineNum];
|
|
250
|
+
for (const pattern of SECURITY_PATTERNS) {
|
|
251
|
+
if (pattern.regex.test(line)) {
|
|
252
|
+
// Skip some false positives in test files
|
|
253
|
+
if (isTestFile && ['hardcoded-password', 'hardcoded-secret'].includes(pattern.id)) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
findings.push({
|
|
257
|
+
id: `security-${pattern.id}-${lineNum}`,
|
|
258
|
+
category: 'access-control',
|
|
259
|
+
severity: pattern.severity,
|
|
260
|
+
title: pattern.title,
|
|
261
|
+
description: pattern.description,
|
|
262
|
+
file: filePath,
|
|
263
|
+
line: lineNum + 1,
|
|
264
|
+
recommendation: pattern.recommendation,
|
|
265
|
+
hipaaReference: '§164.312(a)(1), §164.312(d)',
|
|
266
|
+
context: getContextLines(lines, lineNum, contextSize),
|
|
267
|
+
fixType: pattern.fixType,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// Skip files that can't be read
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return findings;
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
//# sourceMappingURL=index.js.map
|