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,636 @@
|
|
|
1
|
+
export const REMEDIATION_GUIDES = [
|
|
2
|
+
// === PHI Exposure Guides ===
|
|
3
|
+
{
|
|
4
|
+
id: 'dob-exposure',
|
|
5
|
+
matchPatterns: ['Date of birth exposure', 'dob-exposed'],
|
|
6
|
+
hipaaImpact: "Date of birth is one of the 18 HIPAA identifiers that makes health information \"Protected Health Information\" (PHI).\nExposing DOB in code, logs, or unencrypted storage violates the HIPAA Privacy Rule (§164.502) and Security Rule (§164.312).\nBreaches involving DOB can result in fines of $100-$50,000 per violation, with annual maximums of $1.5 million per violation category.",
|
|
7
|
+
options: [
|
|
8
|
+
{
|
|
9
|
+
title: 'Option 1: Encrypt at Rest with AES-256',
|
|
10
|
+
description: 'Encrypt DOB before storing in database. Use AES-256-GCM for HIPAA-compliant encryption.',
|
|
11
|
+
language: 'typescript',
|
|
12
|
+
code: `import crypto from 'crypto';
|
|
13
|
+
|
|
14
|
+
// Encryption configuration (store key in secure vault like AWS KMS)
|
|
15
|
+
const ENCRYPTION_KEY = process.env.PHI_ENCRYPTION_KEY!; // 32 bytes for AES-256
|
|
16
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
17
|
+
|
|
18
|
+
interface EncryptedData {
|
|
19
|
+
encrypted: string;
|
|
20
|
+
iv: string;
|
|
21
|
+
tag: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function encryptPHI(plaintext: string): EncryptedData {
|
|
25
|
+
const iv = crypto.randomBytes(16);
|
|
26
|
+
const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(ENCRYPTION_KEY, 'hex'), iv);
|
|
27
|
+
|
|
28
|
+
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
|
|
29
|
+
encrypted += cipher.final('hex');
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
encrypted,
|
|
33
|
+
iv: iv.toString('hex'),
|
|
34
|
+
tag: cipher.getAuthTag().toString('hex')
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Usage
|
|
39
|
+
const patient = {
|
|
40
|
+
id: 'patient-123',
|
|
41
|
+
dateOfBirth: encryptPHI('1990-05-15'), // Store encrypted
|
|
42
|
+
};`
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
title: 'Option 2: Use Environment Variables for Test Data',
|
|
46
|
+
description: 'For development/test environments, load sample PHI from environment variables instead of hardcoding.',
|
|
47
|
+
language: 'typescript',
|
|
48
|
+
code: `// .env.development (add to .gitignore!)
|
|
49
|
+
// SAMPLE_DOB=1990-01-15
|
|
50
|
+
|
|
51
|
+
// config/sample-data.ts
|
|
52
|
+
export const samplePatient = {
|
|
53
|
+
firstName: process.env.SAMPLE_FIRST_NAME || 'Test',
|
|
54
|
+
lastName: process.env.SAMPLE_LAST_NAME || 'Patient',
|
|
55
|
+
dateOfBirth: process.env.SAMPLE_DOB || '2000-01-01',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// For CI/CD, use secrets management
|
|
59
|
+
// GitHub Actions: secrets.SAMPLE_DOB
|
|
60
|
+
// AWS: AWS Secrets Manager`
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
title: 'Option 3: Use Tokenization',
|
|
64
|
+
description: 'Replace sensitive data with non-sensitive tokens that map to real values in a secure vault.',
|
|
65
|
+
language: 'typescript',
|
|
66
|
+
code: `import { v4 as uuidv4 } from 'uuid';
|
|
67
|
+
|
|
68
|
+
class PHITokenVault {
|
|
69
|
+
private vault: Map<string, string> = new Map();
|
|
70
|
+
|
|
71
|
+
tokenize(sensitiveData: string): string {
|
|
72
|
+
const token = 'PHI_' + uuidv4();
|
|
73
|
+
this.vault.set(token, sensitiveData);
|
|
74
|
+
return token;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
detokenize(token: string): string | null {
|
|
78
|
+
return this.vault.get(token) || null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Usage
|
|
83
|
+
const vault = new PHITokenVault();
|
|
84
|
+
const patient = {
|
|
85
|
+
id: 'patient-123',
|
|
86
|
+
dobToken: vault.tokenize('1990-05-15'),
|
|
87
|
+
};`
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
documentation: [
|
|
91
|
+
{ title: 'HIPAA Security Rule - Encryption Standards', url: 'https://www.hhs.gov/hipaa/for-professionals/security/guidance/index.html' },
|
|
92
|
+
{ title: 'NIST Cryptographic Standards', url: 'https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines' },
|
|
93
|
+
{ title: 'Node.js Crypto Documentation', url: 'https://nodejs.org/api/crypto.html' }
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'phi-console-log',
|
|
98
|
+
matchPatterns: ['PHI data in console output', 'Patient name in console', 'phi-console-log', 'Patient object serialized'],
|
|
99
|
+
hipaaImpact: "Logging PHI to console or log files creates unauthorized copies of protected health information.\nConsole logs may be captured by monitoring tools, stored in plain text, or visible to unauthorized personnel.\nThis violates HIPAA's minimum necessary standard (§164.502(b)) and access controls (§164.312(a)(1)).",
|
|
100
|
+
options: [
|
|
101
|
+
{
|
|
102
|
+
title: 'Option 1: Use Structured Logging with PHI Redaction',
|
|
103
|
+
description: 'Implement a logging wrapper that automatically redacts PHI fields.',
|
|
104
|
+
language: 'typescript',
|
|
105
|
+
code: `import pino from 'pino';
|
|
106
|
+
|
|
107
|
+
// PHI fields to redact
|
|
108
|
+
const PHI_FIELDS = [
|
|
109
|
+
'ssn', 'socialSecurityNumber', 'dateOfBirth', 'dob',
|
|
110
|
+
'mrn', 'medicalRecordNumber', 'diagnosis', 'medication'
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
const logger = pino({
|
|
114
|
+
redact: {
|
|
115
|
+
paths: PHI_FIELDS.concat(PHI_FIELDS.map(f => '*.' + f)),
|
|
116
|
+
censor: '[PHI REDACTED]'
|
|
117
|
+
},
|
|
118
|
+
level: process.env.LOG_LEVEL || 'info'
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Usage - PHI is automatically redacted
|
|
122
|
+
logger.info({ patient: { id: '123', ssn: '123-45-6789' } }, 'Patient loaded');
|
|
123
|
+
// Output: {"patient":{"id":"123","ssn":"[PHI REDACTED]"}}`
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
title: 'Option 2: Create a Safe Logging Wrapper',
|
|
127
|
+
description: 'Create utility functions that strip PHI before logging.',
|
|
128
|
+
language: 'typescript',
|
|
129
|
+
code: `interface SafePatient {
|
|
130
|
+
id: string;
|
|
131
|
+
hasInsurance: boolean;
|
|
132
|
+
appointmentCount: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function toSafeLog<T extends { id: string }>(patient: T): SafePatient {
|
|
136
|
+
return {
|
|
137
|
+
id: patient.id,
|
|
138
|
+
hasInsurance: 'insurance' in patient,
|
|
139
|
+
appointmentCount: 'appointments' in patient
|
|
140
|
+
? (patient as any).appointments?.length || 0
|
|
141
|
+
: 0
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Usage
|
|
146
|
+
function processPatient(patient: Patient) {
|
|
147
|
+
console.log('Processing patient:', toSafeLog(patient));
|
|
148
|
+
// Logs: { id: '123', hasInsurance: true, appointmentCount: 3 }
|
|
149
|
+
}`
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
documentation: [
|
|
153
|
+
{ title: 'Pino Logger - Redaction', url: 'https://getpino.io/#/docs/redaction' },
|
|
154
|
+
{ title: 'Winston Logger', url: 'https://github.com/winstonjs/winston' },
|
|
155
|
+
{ title: 'OWASP Logging Cheat Sheet', url: 'https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html' }
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: 'phi-storage',
|
|
160
|
+
matchPatterns: ['PHI stored in localStorage', 'PHI stored in sessionStorage', 'PHI stored in cookies', 'phi-localstorage'],
|
|
161
|
+
hipaaImpact: "Browser storage (localStorage, sessionStorage, cookies) is not encrypted and can be accessed by any JavaScript on the page.\nThis creates severe XSS risks where attackers could steal PHI. Browser storage also persists across sessions.\nThis violates HIPAA encryption requirements (§164.312(a)(2)(iv)) and access controls.",
|
|
162
|
+
options: [
|
|
163
|
+
{
|
|
164
|
+
title: 'Option 1: Use Server-Side Sessions Only',
|
|
165
|
+
description: 'Store only a session ID in cookies, keep PHI server-side.',
|
|
166
|
+
language: 'typescript',
|
|
167
|
+
code: `import session from 'express-session';
|
|
168
|
+
import RedisStore from 'connect-redis';
|
|
169
|
+
import { createClient } from 'redis';
|
|
170
|
+
|
|
171
|
+
const redisClient = createClient({ url: process.env.REDIS_URL });
|
|
172
|
+
|
|
173
|
+
app.use(session({
|
|
174
|
+
store: new RedisStore({ client: redisClient }),
|
|
175
|
+
secret: process.env.SESSION_SECRET!,
|
|
176
|
+
resave: false,
|
|
177
|
+
saveUninitialized: false,
|
|
178
|
+
cookie: {
|
|
179
|
+
secure: true, // HTTPS only
|
|
180
|
+
httpOnly: true, // No JavaScript access
|
|
181
|
+
sameSite: 'strict',
|
|
182
|
+
maxAge: 15 * 60 * 1000 // 15 minutes
|
|
183
|
+
}
|
|
184
|
+
}));
|
|
185
|
+
|
|
186
|
+
// Store PHI in session, not browser
|
|
187
|
+
app.get('/api/patient/:id', async (req, res) => {
|
|
188
|
+
const patient = await getPatient(req.params.id);
|
|
189
|
+
req.session.currentPatient = patient; // Server-side only
|
|
190
|
+
res.json({ id: patient.id }); // Only send ID to client
|
|
191
|
+
});`
|
|
192
|
+
}
|
|
193
|
+
],
|
|
194
|
+
documentation: [
|
|
195
|
+
{ title: 'OWASP Session Management', url: 'https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html' },
|
|
196
|
+
{ title: 'Express Session', url: 'https://github.com/expressjs/session' }
|
|
197
|
+
]
|
|
198
|
+
},
|
|
199
|
+
// === Audit Logging Guides ===
|
|
200
|
+
{
|
|
201
|
+
id: 'no-audit-logging',
|
|
202
|
+
matchPatterns: ['No audit logging framework detected', 'audit-framework-missing'],
|
|
203
|
+
hipaaImpact: "HIPAA requires covered entities to implement audit controls that record and examine activity in systems containing PHI (§164.312(b)).\nWithout audit logging, you cannot detect unauthorized access, investigate breaches, or demonstrate compliance during audits.\nMissing audit logs can result in automatic assumption of breach during investigations.",
|
|
204
|
+
options: [
|
|
205
|
+
{
|
|
206
|
+
title: 'Option 1: Implement Winston with HIPAA-Compliant Configuration',
|
|
207
|
+
description: 'Set up Winston logger with file rotation, timestamps, and structured format.',
|
|
208
|
+
language: 'typescript',
|
|
209
|
+
code: `import winston from 'winston';
|
|
210
|
+
import DailyRotateFile from 'winston-daily-rotate-file';
|
|
211
|
+
|
|
212
|
+
const auditLogger = winston.createLogger({
|
|
213
|
+
level: 'info',
|
|
214
|
+
format: winston.format.combine(
|
|
215
|
+
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
|
|
216
|
+
winston.format.json()
|
|
217
|
+
),
|
|
218
|
+
defaultMeta: { service: 'healthcare-app' },
|
|
219
|
+
transports: [
|
|
220
|
+
new DailyRotateFile({
|
|
221
|
+
filename: 'logs/audit-%DATE%.log',
|
|
222
|
+
datePattern: 'YYYY-MM-DD',
|
|
223
|
+
maxFiles: '2190d', // 6 years retention
|
|
224
|
+
zippedArchive: true,
|
|
225
|
+
})
|
|
226
|
+
]
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
export function auditLog(action: string, details: {
|
|
230
|
+
userId: string;
|
|
231
|
+
patientId?: string;
|
|
232
|
+
resource: string;
|
|
233
|
+
success: boolean;
|
|
234
|
+
}) {
|
|
235
|
+
auditLogger.info({ action, ...details, timestamp: new Date().toISOString() });
|
|
236
|
+
}`
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
title: 'Option 2: Implement Pino with Async Logging',
|
|
240
|
+
description: 'Use Pino for high-performance async logging.',
|
|
241
|
+
language: 'typescript',
|
|
242
|
+
code: `import pino from 'pino';
|
|
243
|
+
|
|
244
|
+
const transport = pino.transport({
|
|
245
|
+
targets: [
|
|
246
|
+
{
|
|
247
|
+
target: 'pino/file',
|
|
248
|
+
options: { destination: './logs/audit.log' },
|
|
249
|
+
level: 'info'
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const logger = pino({
|
|
255
|
+
level: 'info',
|
|
256
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
|
257
|
+
redact: ['*.ssn', '*.dateOfBirth']
|
|
258
|
+
}, transport);
|
|
259
|
+
|
|
260
|
+
// Audit middleware
|
|
261
|
+
export function auditMiddleware(req: any, res: any, next: any) {
|
|
262
|
+
res.on('finish', () => {
|
|
263
|
+
logger.info({
|
|
264
|
+
type: 'http_request',
|
|
265
|
+
method: req.method,
|
|
266
|
+
path: req.path,
|
|
267
|
+
statusCode: res.statusCode,
|
|
268
|
+
userId: req.user?.id
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
next();
|
|
272
|
+
}`
|
|
273
|
+
}
|
|
274
|
+
],
|
|
275
|
+
documentation: [
|
|
276
|
+
{ title: 'Winston Documentation', url: 'https://github.com/winstonjs/winston' },
|
|
277
|
+
{ title: 'Pino Documentation', url: 'https://getpino.io/' },
|
|
278
|
+
{ title: 'HIPAA Audit Controls Guide', url: 'https://www.hhs.gov/hipaa/for-professionals/security/guidance/audit-controls/index.html' }
|
|
279
|
+
]
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: 'phi-read-no-audit',
|
|
283
|
+
matchPatterns: ['PHI read operation may lack audit logging', 'phi-access-unlogged'],
|
|
284
|
+
hipaaImpact: "Every access to PHI must be logged per HIPAA's audit control requirements (§164.312(b)).\nWithout logging PHI access, you cannot detect unauthorized access, provide access reports to patients, or investigate breaches.",
|
|
285
|
+
options: [
|
|
286
|
+
{
|
|
287
|
+
title: 'Option 1: Create an Audited Repository Pattern',
|
|
288
|
+
description: 'Wrap data access with automatic audit logging.',
|
|
289
|
+
language: 'typescript',
|
|
290
|
+
code: `import { auditLog } from './audit-logger';
|
|
291
|
+
|
|
292
|
+
class AuditedPatientRepository {
|
|
293
|
+
constructor(private db: Database, private getContext: () => { userId: string }) {}
|
|
294
|
+
|
|
295
|
+
async findById(patientId: string): Promise<Patient | null> {
|
|
296
|
+
const ctx = this.getContext();
|
|
297
|
+
const patient = await this.db.patients.findUnique({ where: { id: patientId } });
|
|
298
|
+
|
|
299
|
+
auditLog('PHI_READ', {
|
|
300
|
+
userId: ctx.userId,
|
|
301
|
+
patientId,
|
|
302
|
+
resource: 'patient',
|
|
303
|
+
success: !!patient,
|
|
304
|
+
fieldsAccessed: patient ? Object.keys(patient) : []
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
return patient;
|
|
308
|
+
}
|
|
309
|
+
}`
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
title: 'Option 2: Use Prisma Middleware',
|
|
313
|
+
description: 'Add audit logging middleware to Prisma client.',
|
|
314
|
+
language: 'typescript',
|
|
315
|
+
code: `import { PrismaClient } from '@prisma/client';
|
|
316
|
+
import { auditLog } from './audit-logger';
|
|
317
|
+
|
|
318
|
+
const prisma = new PrismaClient();
|
|
319
|
+
const PHI_MODELS = ['Patient', 'MedicalRecord', 'Prescription'];
|
|
320
|
+
|
|
321
|
+
prisma.$use(async (params, next) => {
|
|
322
|
+
const { model, action, args } = params;
|
|
323
|
+
|
|
324
|
+
if (!model || !PHI_MODELS.includes(model)) {
|
|
325
|
+
return next(params);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const result = await next(params);
|
|
329
|
+
|
|
330
|
+
auditLog('PHI_' + action.toUpperCase(), {
|
|
331
|
+
userId: getCurrentUserId(),
|
|
332
|
+
resource: model.toLowerCase(),
|
|
333
|
+
patientId: args?.where?.id || result?.id,
|
|
334
|
+
success: true
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
return result;
|
|
338
|
+
});`
|
|
339
|
+
}
|
|
340
|
+
],
|
|
341
|
+
documentation: [
|
|
342
|
+
{ title: 'Prisma Middleware', url: 'https://www.prisma.io/docs/concepts/components/prisma-client/middleware' },
|
|
343
|
+
{ title: 'HIPAA Audit Requirements', url: 'https://www.hhs.gov/hipaa/for-professionals/security/guidance/audit-controls/index.html' }
|
|
344
|
+
]
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: 'data-deletion-no-audit',
|
|
348
|
+
matchPatterns: ['Data deletion without apparent logging', 'unlogged-delete'],
|
|
349
|
+
hipaaImpact: "PHI deletion must be logged to comply with HIPAA audit requirements and to prove proper data retention/destruction.\nHIPAA requires maintaining records of PHI dispositions for 6 years.",
|
|
350
|
+
options: [
|
|
351
|
+
{
|
|
352
|
+
title: 'Option 1: Soft Delete with Audit Trail',
|
|
353
|
+
description: 'Never hard delete PHI - use soft deletes with audit logging.',
|
|
354
|
+
language: 'typescript',
|
|
355
|
+
code: `async function softDeletePatient(
|
|
356
|
+
patientId: string,
|
|
357
|
+
userId: string,
|
|
358
|
+
reason: string
|
|
359
|
+
): Promise<void> {
|
|
360
|
+
await prisma.$transaction(async (tx) => {
|
|
361
|
+
await tx.patient.update({
|
|
362
|
+
where: { id: patientId },
|
|
363
|
+
data: {
|
|
364
|
+
deletedAt: new Date(),
|
|
365
|
+
deletedBy: userId,
|
|
366
|
+
deletionReason: reason
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
await tx.phiAuditLog.create({
|
|
371
|
+
data: {
|
|
372
|
+
action: 'PHI_SOFT_DELETE',
|
|
373
|
+
resourceType: 'patient',
|
|
374
|
+
resourceId: patientId,
|
|
375
|
+
userId,
|
|
376
|
+
reason
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
}`
|
|
381
|
+
}
|
|
382
|
+
],
|
|
383
|
+
documentation: [
|
|
384
|
+
{ title: 'HIPAA Retention Requirements', url: 'https://www.hhs.gov/hipaa/for-professionals/faq/580/does-hipaa-require-covered-entities-to-keep-medical-records/index.html' }
|
|
385
|
+
]
|
|
386
|
+
},
|
|
387
|
+
// === Security Guides ===
|
|
388
|
+
{
|
|
389
|
+
id: 'hardcoded-credentials',
|
|
390
|
+
matchPatterns: ['Hardcoded password', 'Hardcoded secret', 'API key exposed', 'hardcoded-password', 'hardcoded-secret', 'api-key-exposed'],
|
|
391
|
+
hipaaImpact: "Hardcoded credentials in source code violate HIPAA's access control requirements (§164.312(d)).\nCredentials in code can be exposed through version control, logs, or decompilation, creating unauthorized access paths to PHI.",
|
|
392
|
+
options: [
|
|
393
|
+
{
|
|
394
|
+
title: 'Option 1: Use Environment Variables',
|
|
395
|
+
description: 'Load credentials from environment variables with validation.',
|
|
396
|
+
language: 'typescript',
|
|
397
|
+
code: `import { z } from 'zod';
|
|
398
|
+
|
|
399
|
+
const envSchema = z.object({
|
|
400
|
+
DATABASE_URL: z.string().url(),
|
|
401
|
+
API_KEY: z.string().min(20),
|
|
402
|
+
JWT_SECRET: z.string().min(32),
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
function loadEnv() {
|
|
406
|
+
const result = envSchema.safeParse(process.env);
|
|
407
|
+
if (!result.success) {
|
|
408
|
+
console.error('Missing environment variables:', result.error.format());
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
return result.data;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export const env = loadEnv();`
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
title: 'Option 2: Use AWS Secrets Manager',
|
|
418
|
+
description: 'Store and rotate secrets using AWS Secrets Manager.',
|
|
419
|
+
language: 'typescript',
|
|
420
|
+
code: `import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
|
|
421
|
+
|
|
422
|
+
const client = new SecretsManagerClient({ region: process.env.AWS_REGION });
|
|
423
|
+
|
|
424
|
+
async function getSecrets(): Promise<{ apiKey: string; dbUrl: string }> {
|
|
425
|
+
const response = await client.send(
|
|
426
|
+
new GetSecretValueCommand({ SecretId: 'healthcare-app/production' })
|
|
427
|
+
);
|
|
428
|
+
return JSON.parse(response.SecretString!);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Usage
|
|
432
|
+
const secrets = await getSecrets();
|
|
433
|
+
const db = new Database(secrets.dbUrl);`
|
|
434
|
+
}
|
|
435
|
+
],
|
|
436
|
+
documentation: [
|
|
437
|
+
{ title: 'AWS Secrets Manager', url: 'https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html' },
|
|
438
|
+
{ title: 'dotenv for Node.js', url: 'https://github.com/motdotla/dotenv' },
|
|
439
|
+
{ title: 'OWASP Secrets Management', url: 'https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html' }
|
|
440
|
+
]
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
id: 'sql-injection',
|
|
444
|
+
matchPatterns: ['SQL query string concatenation', 'SQL query with template literal', 'Database query with template', 'sql-injection'],
|
|
445
|
+
hipaaImpact: "SQL injection can allow attackers to extract entire databases containing PHI, modify medical records, or bypass authentication.\nA single SQL injection vulnerability can result in a massive PHI breach affecting thousands of patients.",
|
|
446
|
+
options: [
|
|
447
|
+
{
|
|
448
|
+
title: 'Option 1: Use Parameterized Queries',
|
|
449
|
+
description: 'Always use parameterized queries or prepared statements.',
|
|
450
|
+
language: 'typescript',
|
|
451
|
+
code: `// BAD - SQL Injection vulnerable
|
|
452
|
+
const user = await db.query(
|
|
453
|
+
'SELECT * FROM users WHERE id = ' + userId
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
// GOOD - Parameterized query (PostgreSQL)
|
|
457
|
+
const user = await db.query(
|
|
458
|
+
'SELECT * FROM users WHERE id = $1',
|
|
459
|
+
[userId]
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
// GOOD - Parameterized query (MySQL)
|
|
463
|
+
const user = await db.query(
|
|
464
|
+
'SELECT * FROM users WHERE id = ?',
|
|
465
|
+
[userId]
|
|
466
|
+
);`
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
title: 'Option 2: Use an ORM (Prisma)',
|
|
470
|
+
description: 'ORMs automatically use parameterized queries.',
|
|
471
|
+
language: 'typescript',
|
|
472
|
+
code: `import { PrismaClient } from '@prisma/client';
|
|
473
|
+
|
|
474
|
+
const prisma = new PrismaClient();
|
|
475
|
+
|
|
476
|
+
// Prisma automatically parameterizes - SAFE
|
|
477
|
+
const user = await prisma.user.findUnique({
|
|
478
|
+
where: { id: userId }
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
const users = await prisma.user.findMany({
|
|
482
|
+
where: {
|
|
483
|
+
role: userRole,
|
|
484
|
+
createdAt: { gte: startDate }
|
|
485
|
+
}
|
|
486
|
+
});`
|
|
487
|
+
}
|
|
488
|
+
],
|
|
489
|
+
documentation: [
|
|
490
|
+
{ title: 'OWASP SQL Injection Prevention', url: 'https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html' },
|
|
491
|
+
{ title: 'Prisma Documentation', url: 'https://www.prisma.io/docs/' }
|
|
492
|
+
]
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
id: 'xss-innerhtml',
|
|
496
|
+
matchPatterns: ['Unsanitized innerHTML', 'dangerouslySetInnerHTML', 'innerHTML', 'document.write'],
|
|
497
|
+
hipaaImpact: "Cross-Site Scripting (XSS) through innerHTML can allow attackers to steal session cookies, capture PHI displayed on screen, or perform actions on behalf of authenticated users.",
|
|
498
|
+
options: [
|
|
499
|
+
{
|
|
500
|
+
title: 'Option 1: Use textContent Instead',
|
|
501
|
+
description: 'For text-only content, use textContent which is XSS-safe.',
|
|
502
|
+
language: 'typescript',
|
|
503
|
+
code: `// BAD - XSS vulnerable
|
|
504
|
+
element.innerHTML = userInput;
|
|
505
|
+
|
|
506
|
+
// GOOD - Safe for text content
|
|
507
|
+
element.textContent = userInput;
|
|
508
|
+
|
|
509
|
+
// React - Use normal JSX (auto-escaped)
|
|
510
|
+
function PatientName({ name }: { name: string }) {
|
|
511
|
+
return <span>{name}</span>; // Safe
|
|
512
|
+
}`
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
title: 'Option 2: Sanitize HTML with DOMPurify',
|
|
516
|
+
description: 'When HTML is required, sanitize with DOMPurify.',
|
|
517
|
+
language: 'typescript',
|
|
518
|
+
code: `import DOMPurify from 'dompurify';
|
|
519
|
+
|
|
520
|
+
const purifyConfig = {
|
|
521
|
+
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br'],
|
|
522
|
+
ALLOWED_ATTR: []
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
// Sanitize before setting innerHTML
|
|
526
|
+
element.innerHTML = DOMPurify.sanitize(userHtml, purifyConfig);
|
|
527
|
+
|
|
528
|
+
// React with dangerouslySetInnerHTML
|
|
529
|
+
function RichText({ html }: { html: string }) {
|
|
530
|
+
const sanitized = DOMPurify.sanitize(html, purifyConfig);
|
|
531
|
+
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
|
|
532
|
+
}`
|
|
533
|
+
}
|
|
534
|
+
],
|
|
535
|
+
documentation: [
|
|
536
|
+
{ title: 'DOMPurify', url: 'https://github.com/cure53/DOMPurify' },
|
|
537
|
+
{ title: 'OWASP XSS Prevention', url: 'https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html' }
|
|
538
|
+
]
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
id: 'http-not-https',
|
|
542
|
+
matchPatterns: ['Unencrypted HTTP URL', 'http-url', 'HTTP endpoint'],
|
|
543
|
+
hipaaImpact: "HIPAA requires encryption of PHI in transit (§164.312(e)(1)).\nHTTP transmits data in plain text, allowing interception of PHI through network sniffing or man-in-the-middle attacks.",
|
|
544
|
+
options: [
|
|
545
|
+
{
|
|
546
|
+
title: 'Option 1: Convert to HTTPS',
|
|
547
|
+
description: 'Simply change http:// to https:// for external URLs.',
|
|
548
|
+
language: 'typescript',
|
|
549
|
+
code: `// BAD
|
|
550
|
+
const apiUrl = 'http://api.healthcare.com/patients';
|
|
551
|
+
|
|
552
|
+
// GOOD
|
|
553
|
+
const apiUrl = 'https://api.healthcare.com/patients';
|
|
554
|
+
|
|
555
|
+
// Better - use environment variables
|
|
556
|
+
const apiUrl = process.env.API_URL; // Set to https:// in env`
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
title: 'Option 2: Enforce HTTPS at Application Level',
|
|
560
|
+
description: 'Add middleware to redirect and enforce HTTPS.',
|
|
561
|
+
language: 'typescript',
|
|
562
|
+
code: `import helmet from 'helmet';
|
|
563
|
+
|
|
564
|
+
app.use(helmet({
|
|
565
|
+
hsts: {
|
|
566
|
+
maxAge: 31536000,
|
|
567
|
+
includeSubDomains: true,
|
|
568
|
+
preload: true
|
|
569
|
+
}
|
|
570
|
+
}));
|
|
571
|
+
|
|
572
|
+
// Redirect HTTP to HTTPS
|
|
573
|
+
app.use((req, res, next) => {
|
|
574
|
+
if (req.header('x-forwarded-proto') !== 'https' &&
|
|
575
|
+
process.env.NODE_ENV === 'production') {
|
|
576
|
+
return res.redirect('https://' + req.header('host') + req.url);
|
|
577
|
+
}
|
|
578
|
+
next();
|
|
579
|
+
});`
|
|
580
|
+
}
|
|
581
|
+
],
|
|
582
|
+
documentation: [
|
|
583
|
+
{ title: 'Helmet.js Security', url: 'https://helmetjs.github.io/' },
|
|
584
|
+
{ title: 'HIPAA Transmission Security', url: 'https://www.hhs.gov/hipaa/for-professionals/security/guidance/transmission-security/index.html' }
|
|
585
|
+
]
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
id: 'diagnosis-code-exposure',
|
|
589
|
+
matchPatterns: ['Diagnosis code in source', 'ICD-10', 'diagnosis-code'],
|
|
590
|
+
hipaaImpact: "ICD-10 diagnosis codes are considered PHI when associated with a patient.\nHardcoded diagnosis codes may indicate test data containing real patient information or improper handling of medical classifications.",
|
|
591
|
+
options: [
|
|
592
|
+
{
|
|
593
|
+
title: 'Option 1: Load from Secure Configuration',
|
|
594
|
+
description: 'Store diagnosis codes in encrypted configuration or database.',
|
|
595
|
+
language: 'typescript',
|
|
596
|
+
code: `// BAD - hardcoded diagnosis codes
|
|
597
|
+
const testPatient = {
|
|
598
|
+
diagnosis: 'F32.1', // Major depressive disorder
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
// GOOD - load from secure source
|
|
602
|
+
async function getDiagnosisCodes() {
|
|
603
|
+
return prisma.diagnosisCode.findMany({
|
|
604
|
+
select: { code: true, description: true }
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// For test data, use obviously fake codes
|
|
609
|
+
const TEST_DIAGNOSIS = {
|
|
610
|
+
code: 'TEST-001',
|
|
611
|
+
description: 'Test Diagnosis - Not Real',
|
|
612
|
+
isTestData: true
|
|
613
|
+
};`
|
|
614
|
+
}
|
|
615
|
+
],
|
|
616
|
+
documentation: [
|
|
617
|
+
{ title: 'FHIR Terminology Service', url: 'https://www.hl7.org/fhir/terminology-service.html' },
|
|
618
|
+
{ title: 'ICD-10 Code Set', url: 'https://www.cms.gov/medicare/coding-billing/icd-10-codes' }
|
|
619
|
+
]
|
|
620
|
+
}
|
|
621
|
+
];
|
|
622
|
+
/**
|
|
623
|
+
* Find the best matching remediation guide for a finding
|
|
624
|
+
*/
|
|
625
|
+
export function getRemediationGuide(finding) {
|
|
626
|
+
for (const guide of REMEDIATION_GUIDES) {
|
|
627
|
+
for (const pattern of guide.matchPatterns) {
|
|
628
|
+
if (finding.title.toLowerCase().includes(pattern.toLowerCase()) ||
|
|
629
|
+
finding.id.toLowerCase().includes(pattern.toLowerCase())) {
|
|
630
|
+
return guide;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
//# sourceMappingURL=remediation-guides.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remediation-guides.js","sourceRoot":"","sources":["../../src/reporters/remediation-guides.ts"],"names":[],"mappings":"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"}
|
package/dist/scan.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAmD,MAAM,YAAY,CAAC;AAgC3G,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CA+EpE"}
|