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
package/dist/scan.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { glob } from 'glob';
|
|
2
|
+
import { loadConfig, isPathIgnored } from './config.js';
|
|
3
|
+
import { phiScanner } from './scanners/phi/index.js';
|
|
4
|
+
import { encryptionScanner } from './scanners/encryption/index.js';
|
|
5
|
+
import { auditScanner } from './scanners/audit/index.js';
|
|
6
|
+
import { accessScanner } from './scanners/access/index.js';
|
|
7
|
+
import { retentionScanner } from './scanners/retention/index.js';
|
|
8
|
+
import { securityScanner } from './scanners/security/index.js';
|
|
9
|
+
import { detectStack, getStackDisplayName } from './stack-detector/index.js';
|
|
10
|
+
import { getStackSummary } from './stack-detector/stack-guides.js';
|
|
11
|
+
const ALL_CATEGORIES = [
|
|
12
|
+
'phi-exposure',
|
|
13
|
+
'encryption',
|
|
14
|
+
'audit-logging',
|
|
15
|
+
'access-control',
|
|
16
|
+
'data-retention',
|
|
17
|
+
];
|
|
18
|
+
const scanners = {
|
|
19
|
+
'phi-exposure': phiScanner,
|
|
20
|
+
'encryption': encryptionScanner,
|
|
21
|
+
'audit-logging': auditScanner,
|
|
22
|
+
'access-control': accessScanner,
|
|
23
|
+
'data-retention': retentionScanner,
|
|
24
|
+
};
|
|
25
|
+
// Additional scanners that run with specific categories
|
|
26
|
+
const additionalScanners = {
|
|
27
|
+
'access-control': [securityScanner], // Security scanner runs with access-control
|
|
28
|
+
};
|
|
29
|
+
export async function scan(options) {
|
|
30
|
+
const startTime = Date.now();
|
|
31
|
+
// Load configuration
|
|
32
|
+
const config = await loadConfig(options.path, options.configFile);
|
|
33
|
+
const optionsWithConfig = { ...options, config };
|
|
34
|
+
const categories = options.categories ?? config.categories ?? ALL_CATEGORIES;
|
|
35
|
+
// Get all files to scan
|
|
36
|
+
const defaultExclude = [
|
|
37
|
+
'**/node_modules/**',
|
|
38
|
+
'**/dist/**',
|
|
39
|
+
'**/build/**',
|
|
40
|
+
'**/.git/**',
|
|
41
|
+
'**/coverage/**',
|
|
42
|
+
];
|
|
43
|
+
const excludePatterns = [
|
|
44
|
+
...defaultExclude,
|
|
45
|
+
...(options.exclude ?? []),
|
|
46
|
+
...(config.exclude ?? []),
|
|
47
|
+
];
|
|
48
|
+
const files = await glob('**/*', {
|
|
49
|
+
cwd: options.path,
|
|
50
|
+
nodir: true,
|
|
51
|
+
ignore: excludePatterns,
|
|
52
|
+
absolute: true,
|
|
53
|
+
});
|
|
54
|
+
// Filter out ignored paths from config
|
|
55
|
+
const filteredFiles = files.filter(f => !isPathIgnored(f, config));
|
|
56
|
+
// Run scanners for selected categories
|
|
57
|
+
const findings = [];
|
|
58
|
+
for (const category of categories) {
|
|
59
|
+
const scanner = scanners[category];
|
|
60
|
+
if (scanner) {
|
|
61
|
+
const categoryFindings = await scanner.scan(filteredFiles, optionsWithConfig);
|
|
62
|
+
findings.push(...categoryFindings);
|
|
63
|
+
}
|
|
64
|
+
// Run additional scanners for this category
|
|
65
|
+
const additional = additionalScanners[category];
|
|
66
|
+
if (additional) {
|
|
67
|
+
for (const extraScanner of additional) {
|
|
68
|
+
const extraFindings = await extraScanner.scan(filteredFiles, optionsWithConfig);
|
|
69
|
+
findings.push(...extraFindings);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Sort findings by severity
|
|
74
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
75
|
+
findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
76
|
+
// Detect project stack
|
|
77
|
+
const detectedStack = await detectStack(options.path);
|
|
78
|
+
const stackDisplayNames = getStackDisplayName(detectedStack);
|
|
79
|
+
const stackRecommendations = getStackSummary(detectedStack);
|
|
80
|
+
const stack = {
|
|
81
|
+
framework: detectedStack.framework,
|
|
82
|
+
database: detectedStack.database,
|
|
83
|
+
auth: detectedStack.auth,
|
|
84
|
+
frameworkDisplay: stackDisplayNames.framework,
|
|
85
|
+
databaseDisplay: stackDisplayNames.database,
|
|
86
|
+
authDisplay: stackDisplayNames.auth,
|
|
87
|
+
recommendations: stackRecommendations,
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
findings,
|
|
91
|
+
scannedFiles: filteredFiles.length,
|
|
92
|
+
scanDuration: Date.now() - startTime,
|
|
93
|
+
stack,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=scan.js.map
|
package/dist/scan.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/scanners/access/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAwB,MAAM,gBAAgB,CAAC;AA+DpE,eAAO,MAAM,aAAa,EAAE,OA2C3B,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 ACCESS_CONTROL_ISSUES = [
|
|
5
|
+
{
|
|
6
|
+
regex: /\*\s*FROM\s+(patient|user|health|medical)/i,
|
|
7
|
+
id: 'select-star',
|
|
8
|
+
severity: 'medium',
|
|
9
|
+
title: 'SELECT * on sensitive table',
|
|
10
|
+
description: 'Using SELECT * may retrieve more PHI than necessary.',
|
|
11
|
+
recommendation: 'Select only required columns to minimize PHI exposure (minimum necessary).',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
regex: /role\s*[:=]\s*['"`](admin|root|superuser)['"`]/i,
|
|
15
|
+
id: 'hardcoded-admin',
|
|
16
|
+
severity: 'high',
|
|
17
|
+
title: 'Hardcoded admin role',
|
|
18
|
+
description: 'Hardcoded administrative role assignment detected.',
|
|
19
|
+
recommendation: 'Use role-based access control (RBAC) with proper authentication.',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
regex: /bypass.*auth|auth.*bypass|skip.*auth/i,
|
|
23
|
+
id: 'auth-bypass',
|
|
24
|
+
severity: 'critical',
|
|
25
|
+
title: 'Potential authentication bypass',
|
|
26
|
+
description: 'Code pattern suggests authentication may be bypassed.',
|
|
27
|
+
recommendation: 'Remove any authentication bypass mechanisms in production code.',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
regex: /isAdmin\s*[:=]\s*true|admin\s*[:=]\s*true/i,
|
|
31
|
+
id: 'admin-flag',
|
|
32
|
+
severity: 'medium',
|
|
33
|
+
title: 'Hardcoded admin flag',
|
|
34
|
+
description: 'Admin privileges set via hardcoded flag.',
|
|
35
|
+
recommendation: 'Determine admin status through secure authentication flow.',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
regex: /public\s+(static\s+)?.*password|password.*public/i,
|
|
39
|
+
id: 'public-password',
|
|
40
|
+
severity: 'critical',
|
|
41
|
+
title: 'Password field with public visibility',
|
|
42
|
+
description: 'Password field may have public accessibility.',
|
|
43
|
+
recommendation: 'Password fields should be private and never exposed.',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
regex: /allow.*origin.*\*/i,
|
|
47
|
+
id: 'cors-wildcard',
|
|
48
|
+
severity: 'high',
|
|
49
|
+
title: 'CORS wildcard origin',
|
|
50
|
+
description: 'CORS configured to allow all origins.',
|
|
51
|
+
recommendation: 'Restrict CORS to specific trusted domains for PHI-handling endpoints.',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
regex: /session.*expires?\s*[:=]\s*0|maxAge\s*:\s*0/i,
|
|
55
|
+
id: 'no-session-expiry',
|
|
56
|
+
severity: 'high',
|
|
57
|
+
title: 'Session without expiration',
|
|
58
|
+
description: 'Session configured without expiration.',
|
|
59
|
+
recommendation: 'Implement automatic session timeout for PHI access (HIPAA recommends 15 min idle).',
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
export const accessScanner = {
|
|
63
|
+
name: 'Access Control Scanner',
|
|
64
|
+
category: 'access-control',
|
|
65
|
+
async scan(files, options) {
|
|
66
|
+
const findings = [];
|
|
67
|
+
const config = options.config ?? DEFAULT_CONFIG;
|
|
68
|
+
const contextSize = config.contextLines ?? 2;
|
|
69
|
+
const codeExtensions = ['.ts', '.js', '.tsx', '.jsx', '.py', '.java', '.go', '.rb', '.php', '.sql'];
|
|
70
|
+
const codeFiles = files.filter(f => codeExtensions.some(ext => f.endsWith(ext)));
|
|
71
|
+
for (const filePath of codeFiles) {
|
|
72
|
+
try {
|
|
73
|
+
const content = await readFile(filePath, 'utf-8');
|
|
74
|
+
const lines = content.split('\n');
|
|
75
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
76
|
+
const line = lines[lineNum];
|
|
77
|
+
for (const issue of ACCESS_CONTROL_ISSUES) {
|
|
78
|
+
if (issue.regex.test(line)) {
|
|
79
|
+
findings.push({
|
|
80
|
+
id: `access-${issue.id}-${lineNum}`,
|
|
81
|
+
category: 'access-control',
|
|
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.312(a)(1), §164.312(d)',
|
|
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/access/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,qBAAqB,GAAG;IAC5B;QACE,KAAK,EAAE,4CAA4C;QACnD,EAAE,EAAE,aAAa;QACjB,QAAQ,EAAE,QAAiB;QAC3B,KAAK,EAAE,6BAA6B;QACpC,WAAW,EAAE,sDAAsD;QACnE,cAAc,EAAE,4EAA4E;KAC7F;IACD;QACE,KAAK,EAAE,iDAAiD;QACxD,EAAE,EAAE,iBAAiB;QACrB,QAAQ,EAAE,MAAe;QACzB,KAAK,EAAE,sBAAsB;QAC7B,WAAW,EAAE,oDAAoD;QACjE,cAAc,EAAE,kEAAkE;KACnF;IACD;QACE,KAAK,EAAE,uCAAuC;QAC9C,EAAE,EAAE,aAAa;QACjB,QAAQ,EAAE,UAAmB;QAC7B,KAAK,EAAE,iCAAiC;QACxC,WAAW,EAAE,uDAAuD;QACpE,cAAc,EAAE,iEAAiE;KAClF;IACD;QACE,KAAK,EAAE,4CAA4C;QACnD,EAAE,EAAE,YAAY;QAChB,QAAQ,EAAE,QAAiB;QAC3B,KAAK,EAAE,sBAAsB;QAC7B,WAAW,EAAE,0CAA0C;QACvD,cAAc,EAAE,4DAA4D;KAC7E;IACD;QACE,KAAK,EAAE,mDAAmD;QAC1D,EAAE,EAAE,iBAAiB;QACrB,QAAQ,EAAE,UAAmB;QAC7B,KAAK,EAAE,uCAAuC;QAC9C,WAAW,EAAE,+CAA+C;QAC5D,cAAc,EAAE,sDAAsD;KACvE;IACD;QACE,KAAK,EAAE,oBAAoB;QAC3B,EAAE,EAAE,eAAe;QACnB,QAAQ,EAAE,MAAe;QACzB,KAAK,EAAE,sBAAsB;QAC7B,WAAW,EAAE,uCAAuC;QACpD,cAAc,EAAE,uEAAuE;KACxF;IACD;QACE,KAAK,EAAE,8CAA8C;QACrD,EAAE,EAAE,mBAAmB;QACvB,QAAQ,EAAE,MAAe;QACzB,KAAK,EAAE,4BAA4B;QACnC,WAAW,EAAE,wCAAwC;QACrD,cAAc,EAAE,oFAAoF;KACrG;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAY;IACpC,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,CAAC,CAAC;QACpG,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,qBAAqB,EAAE,CAAC;wBAC1C,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;4BAC3B,QAAQ,CAAC,IAAI,CAAC;gCACZ,EAAE,EAAE,UAAU,KAAK,CAAC,EAAE,IAAI,OAAO,EAAE;gCACnC,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,6BAA6B;gCAC7C,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/audit/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAwB,MAAM,gBAAgB,CAAC;AAiBpE,eAAO,MAAM,YAAY,EAAE,OAuF1B,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { DEFAULT_CONFIG } from '../../config.js';
|
|
3
|
+
import { getContextLines } from '../../utils/context.js';
|
|
4
|
+
const LOGGING_FRAMEWORKS = [
|
|
5
|
+
'winston', 'bunyan', 'pino', 'log4js', 'morgan',
|
|
6
|
+
'logging', 'logger', 'structlog', 'loguru',
|
|
7
|
+
];
|
|
8
|
+
const AUDIT_REQUIRED_ACTIONS = [
|
|
9
|
+
{ pattern: /\.(create|insert|save|add)\s*\(/i, action: 'create' },
|
|
10
|
+
{ pattern: /\.(update|modify|patch|put)\s*\(/i, action: 'update' },
|
|
11
|
+
{ pattern: /\.(delete|remove|destroy)\s*\(/i, action: 'delete' },
|
|
12
|
+
{ pattern: /\.(read|get|find|fetch|select)\s*\(/i, action: 'read' },
|
|
13
|
+
{ pattern: /\.(login|authenticate|authorize)\s*\(/i, action: 'auth' },
|
|
14
|
+
];
|
|
15
|
+
export const auditScanner = {
|
|
16
|
+
name: 'Audit Logging Scanner',
|
|
17
|
+
category: 'audit-logging',
|
|
18
|
+
async scan(files, options) {
|
|
19
|
+
const findings = [];
|
|
20
|
+
const config = options.config ?? DEFAULT_CONFIG;
|
|
21
|
+
const contextSize = config.contextLines ?? 2;
|
|
22
|
+
// Check for logging framework presence
|
|
23
|
+
const packageJsonFiles = files.filter(f => f.endsWith('package.json'));
|
|
24
|
+
let hasLoggingFramework = false;
|
|
25
|
+
for (const pkgFile of packageJsonFiles) {
|
|
26
|
+
try {
|
|
27
|
+
const content = await readFile(pkgFile, 'utf-8');
|
|
28
|
+
if (LOGGING_FRAMEWORKS.some(fw => content.includes(fw))) {
|
|
29
|
+
hasLoggingFramework = true;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Skip
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!hasLoggingFramework && packageJsonFiles.length > 0) {
|
|
38
|
+
findings.push({
|
|
39
|
+
id: 'audit-no-framework',
|
|
40
|
+
category: 'audit-logging',
|
|
41
|
+
severity: 'high',
|
|
42
|
+
title: 'No audit logging framework detected',
|
|
43
|
+
description: 'No recognized logging framework found in dependencies.',
|
|
44
|
+
file: packageJsonFiles[0],
|
|
45
|
+
recommendation: 'Implement structured audit logging using winston, pino, or similar.',
|
|
46
|
+
hipaaReference: '§164.312(b)',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// Check code files for unlogged operations
|
|
50
|
+
const codeExtensions = ['.ts', '.js', '.tsx', '.jsx', '.py', '.java', '.go'];
|
|
51
|
+
const codeFiles = files.filter(f => codeExtensions.some(ext => f.endsWith(ext)));
|
|
52
|
+
for (const filePath of codeFiles) {
|
|
53
|
+
// Skip test files
|
|
54
|
+
if (filePath.includes('test') || filePath.includes('spec')) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const content = await readFile(filePath, 'utf-8');
|
|
59
|
+
const lines = content.split('\n');
|
|
60
|
+
// Simple heuristic: check if file has PHI-related operations without logging
|
|
61
|
+
const hasPhiKeywords = /patient|health|medical|diagnosis|treatment/i.test(content);
|
|
62
|
+
const hasLogging = /\.(log|info|warn|error|audit)\s*\(/i.test(content) ||
|
|
63
|
+
/logger\./i.test(content);
|
|
64
|
+
if (hasPhiKeywords) {
|
|
65
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
66
|
+
const line = lines[lineNum];
|
|
67
|
+
for (const { pattern, action } of AUDIT_REQUIRED_ACTIONS) {
|
|
68
|
+
if (pattern.test(line) && !hasLogging) {
|
|
69
|
+
findings.push({
|
|
70
|
+
id: `audit-unlogged-${action}-${lineNum}`,
|
|
71
|
+
category: 'audit-logging',
|
|
72
|
+
severity: 'medium',
|
|
73
|
+
title: `PHI ${action} operation may lack audit logging`,
|
|
74
|
+
description: `A ${action} operation on PHI-related data was found without apparent audit logging in this file.`,
|
|
75
|
+
file: filePath,
|
|
76
|
+
line: lineNum + 1,
|
|
77
|
+
recommendation: `Log all ${action} operations on PHI with timestamp, user ID, and action details.`,
|
|
78
|
+
hipaaReference: '§164.312(b)',
|
|
79
|
+
context: getContextLines(lines, lineNum, contextSize),
|
|
80
|
+
});
|
|
81
|
+
break; // One finding per file for this pattern
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Skip files that can't be read
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return findings;
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/scanners/audit/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,kBAAkB,GAAG;IACzB,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ;IAC/C,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ;CAC3C,CAAC;AAEF,MAAM,sBAAsB,GAAG;IAC7B,EAAE,OAAO,EAAE,kCAAkC,EAAE,MAAM,EAAE,QAAQ,EAAE;IACjE,EAAE,OAAO,EAAE,mCAAmC,EAAE,MAAM,EAAE,QAAQ,EAAE;IAClE,EAAE,OAAO,EAAE,iCAAiC,EAAE,MAAM,EAAE,QAAQ,EAAE;IAChE,EAAE,OAAO,EAAE,sCAAsC,EAAE,MAAM,EAAE,MAAM,EAAE;IACnE,EAAE,OAAO,EAAE,wCAAwC,EAAE,MAAM,EAAE,MAAM,EAAE;CACtE,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAY;IACnC,IAAI,EAAE,uBAAuB;IAC7B,QAAQ,EAAE,eAAe;IAEzB,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;QAE7C,uCAAuC;QACvC,MAAM,gBAAgB,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC;QACvE,IAAI,mBAAmB,GAAG,KAAK,CAAC;QAEhC,KAAK,MAAM,OAAO,IAAI,gBAAgB,EAAE,CAAC;YACvC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBACjD,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;oBACxD,mBAAmB,GAAG,IAAI,CAAC;oBAC3B,MAAM;gBACR,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO;YACT,CAAC;QACH,CAAC;QAED,IAAI,CAAC,mBAAmB,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxD,QAAQ,CAAC,IAAI,CAAC;gBACZ,EAAE,EAAE,oBAAoB;gBACxB,QAAQ,EAAE,eAAe;gBACzB,QAAQ,EAAE,MAAM;gBAChB,KAAK,EAAE,qCAAqC;gBAC5C,WAAW,EAAE,wDAAwD;gBACrE,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC;gBACzB,cAAc,EAAE,qEAAqE;gBACrF,cAAc,EAAE,aAAa;aAC9B,CAAC,CAAC;QACL,CAAC;QAED,2CAA2C;QAC3C,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QAC7E,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,kBAAkB;YAClB,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3D,SAAS;YACX,CAAC;YAED,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,6EAA6E;gBAC7E,MAAM,cAAc,GAAG,6CAA6C,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACnF,MAAM,UAAU,GAAG,qCAAqC,CAAC,IAAI,CAAC,OAAO,CAAC;oBACpD,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBAE5C,IAAI,cAAc,EAAE,CAAC;oBACnB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC;wBACxD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;wBAE5B,KAAK,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,sBAAsB,EAAE,CAAC;4BACzD,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;gCACtC,QAAQ,CAAC,IAAI,CAAC;oCACZ,EAAE,EAAE,kBAAkB,MAAM,IAAI,OAAO,EAAE;oCACzC,QAAQ,EAAE,eAAe;oCACzB,QAAQ,EAAE,QAAQ;oCAClB,KAAK,EAAE,OAAO,MAAM,mCAAmC;oCACvD,WAAW,EAAE,KAAK,MAAM,uFAAuF;oCAC/G,IAAI,EAAE,QAAQ;oCACd,IAAI,EAAE,OAAO,GAAG,CAAC;oCACjB,cAAc,EAAE,WAAW,MAAM,iEAAiE;oCAClG,cAAc,EAAE,aAAa;oCAC7B,OAAO,EAAE,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC;iCACtD,CAAC,CAAC;gCACH,MAAM,CAAC,wCAAwC;4BACjD,CAAC;wBACH,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/encryption/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAiC,MAAM,gBAAgB,CAAC;AAiC7E,eAAO,MAAM,iBAAiB,EAAE,OAkE/B,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { isSafeHttpUrl, DEFAULT_CONFIG } from '../../config.js';
|
|
3
|
+
import { getContextLines } from '../../utils/context.js';
|
|
4
|
+
const WEAK_CRYPTO_PATTERNS = [
|
|
5
|
+
{ regex: /\bmd5\s*\(/i, issue: 'MD5 hash function', severity: 'high' },
|
|
6
|
+
{ regex: /\bsha1\s*\(/i, issue: 'SHA1 hash function', severity: 'medium' },
|
|
7
|
+
{ regex: /\bdes\b/i, issue: 'DES encryption', severity: 'critical' },
|
|
8
|
+
{ regex: /\b(rc4|arcfour)\b/i, issue: 'RC4 encryption', severity: 'critical' },
|
|
9
|
+
{ regex: /createCipher\s*\(/i, issue: 'Deprecated cipher method', severity: 'high' },
|
|
10
|
+
{ regex: /\bECB\b/, issue: 'ECB mode encryption', severity: 'high' },
|
|
11
|
+
];
|
|
12
|
+
const MISSING_ENCRYPTION_PATTERNS = [
|
|
13
|
+
{ regex: /http:\/\/(?!localhost|127\.0\.0\.1)/i, issue: 'Unencrypted HTTP URL', severity: 'high', checkSafe: true, fixType: 'http-url' },
|
|
14
|
+
{ regex: /ssl\s*[:=]\s*false/i, issue: 'SSL disabled', severity: 'critical' },
|
|
15
|
+
{ regex: /verify\s*[:=]\s*false.*ssl/i, issue: 'SSL verification disabled', severity: 'critical' },
|
|
16
|
+
{ regex: /rejectUnauthorized\s*:\s*false/i, issue: 'TLS certificate validation disabled', severity: 'critical' },
|
|
17
|
+
// Unencrypted backup patterns
|
|
18
|
+
{ regex: /backup.*encrypt\s*[:=]\s*false|encrypt\s*[:=]\s*false.*backup/i, issue: 'Backup encryption disabled', severity: 'critical', fixType: 'backup-unencrypted' },
|
|
19
|
+
{ regex: /mysqldump(?!.*--ssl).*password|pg_dump(?!.*--ssl)/i, issue: 'Database backup without SSL', severity: 'high' },
|
|
20
|
+
{ regex: /backup.*(\.sql|\.csv|\.json|\.txt)\b(?!.*encrypt|.*gpg|.*aes)/i, issue: 'Unencrypted backup file format', severity: 'high', fixType: 'backup-unencrypted' },
|
|
21
|
+
{ regex: /writeFile.*backup.*patient|patient.*backup.*writeFile/i, issue: 'PHI backup without encryption', severity: 'critical', fixType: 'backup-unencrypted' },
|
|
22
|
+
{ regex: /s3.*upload.*backup(?!.*encrypt|.*sse|.*kms)/i, issue: 'S3 backup without server-side encryption', severity: 'high' },
|
|
23
|
+
{ regex: /backup.*storage(?!.*encrypt)|storage.*backup(?!.*encrypt)/i, issue: 'Backup storage without encryption specified', severity: 'medium' },
|
|
24
|
+
];
|
|
25
|
+
export const encryptionScanner = {
|
|
26
|
+
name: 'Encryption Scanner',
|
|
27
|
+
category: 'encryption',
|
|
28
|
+
async scan(files, options) {
|
|
29
|
+
const findings = [];
|
|
30
|
+
const config = options.config ?? DEFAULT_CONFIG;
|
|
31
|
+
const contextSize = config.contextLines ?? 2;
|
|
32
|
+
const codeExtensions = ['.ts', '.js', '.tsx', '.jsx', '.py', '.java', '.go', '.rb', '.php', '.env', '.yaml', '.yml', '.json', '.xml'];
|
|
33
|
+
const codeFiles = files.filter(f => codeExtensions.some(ext => f.endsWith(ext)));
|
|
34
|
+
for (const filePath of codeFiles) {
|
|
35
|
+
try {
|
|
36
|
+
const content = await readFile(filePath, 'utf-8');
|
|
37
|
+
const lines = content.split('\n');
|
|
38
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
39
|
+
const line = lines[lineNum];
|
|
40
|
+
for (const pattern of WEAK_CRYPTO_PATTERNS) {
|
|
41
|
+
if (pattern.regex.test(line)) {
|
|
42
|
+
findings.push({
|
|
43
|
+
id: `enc-weak-${lineNum}`,
|
|
44
|
+
category: 'encryption',
|
|
45
|
+
severity: pattern.severity,
|
|
46
|
+
title: `Weak cryptography: ${pattern.issue}`,
|
|
47
|
+
description: `${pattern.issue} is not suitable for protecting PHI.`,
|
|
48
|
+
file: filePath,
|
|
49
|
+
line: lineNum + 1,
|
|
50
|
+
recommendation: 'Use AES-256-GCM for encryption and SHA-256 or stronger for hashing.',
|
|
51
|
+
hipaaReference: '§164.312(a)(2)(iv), §164.312(e)(2)(ii)',
|
|
52
|
+
context: getContextLines(lines, lineNum, contextSize),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (const pattern of MISSING_ENCRYPTION_PATTERNS) {
|
|
57
|
+
if (pattern.regex.test(line)) {
|
|
58
|
+
// Check if this is a safe HTTP URL (CDN, namespace, etc.)
|
|
59
|
+
if (pattern.checkSafe && isSafeHttpUrl(line, config)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
findings.push({
|
|
63
|
+
id: `enc-missing-${lineNum}`,
|
|
64
|
+
category: 'encryption',
|
|
65
|
+
severity: pattern.severity,
|
|
66
|
+
title: `Encryption issue: ${pattern.issue}`,
|
|
67
|
+
description: `${pattern.issue} may expose PHI during transmission.`,
|
|
68
|
+
file: filePath,
|
|
69
|
+
line: lineNum + 1,
|
|
70
|
+
recommendation: 'Enforce TLS 1.2+ for all data transmission containing PHI.',
|
|
71
|
+
hipaaReference: '§164.312(e)(1)',
|
|
72
|
+
context: getContextLines(lines, lineNum, contextSize),
|
|
73
|
+
fixType: pattern.fixType,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Skip files that can't be read
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return findings;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/scanners/encryption/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/scanners/phi/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAwB,MAAM,gBAAgB,CAAC;AAKpE,eAAO,MAAM,UAAU,EAAE,OA8CxB,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { DEFAULT_CONFIG } from '../../config.js';
|
|
3
|
+
import { getContextLines } from '../../utils/context.js';
|
|
4
|
+
import { PHI_PATTERNS } from './patterns.js';
|
|
5
|
+
export const phiScanner = {
|
|
6
|
+
name: 'PHI Exposure Scanner',
|
|
7
|
+
category: 'phi-exposure',
|
|
8
|
+
async scan(files, options) {
|
|
9
|
+
const findings = [];
|
|
10
|
+
const config = options.config ?? DEFAULT_CONFIG;
|
|
11
|
+
const contextSize = config.contextLines ?? 2;
|
|
12
|
+
// Filter to code files
|
|
13
|
+
const codeExtensions = ['.ts', '.js', '.tsx', '.jsx', '.py', '.java', '.go', '.rb', '.php'];
|
|
14
|
+
const codeFiles = files.filter(f => codeExtensions.some(ext => f.endsWith(ext)));
|
|
15
|
+
for (const filePath of codeFiles) {
|
|
16
|
+
try {
|
|
17
|
+
const content = await readFile(filePath, 'utf-8');
|
|
18
|
+
const lines = content.split('\n');
|
|
19
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
20
|
+
const line = lines[lineNum];
|
|
21
|
+
for (const pattern of PHI_PATTERNS) {
|
|
22
|
+
if (pattern.regex.test(line)) {
|
|
23
|
+
findings.push({
|
|
24
|
+
id: `phi-${pattern.id}-${lineNum}`,
|
|
25
|
+
category: 'phi-exposure',
|
|
26
|
+
severity: pattern.severity,
|
|
27
|
+
title: pattern.title,
|
|
28
|
+
description: pattern.description,
|
|
29
|
+
file: filePath,
|
|
30
|
+
line: lineNum + 1,
|
|
31
|
+
recommendation: pattern.recommendation,
|
|
32
|
+
hipaaReference: '§164.502, §164.514',
|
|
33
|
+
context: getContextLines(lines, lineNum, contextSize),
|
|
34
|
+
fixType: pattern.fixType,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Skip files that can't be read
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return findings;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/scanners/phi/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;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,CAAC,MAAM,UAAU,GAAY;IACjC,IAAI,EAAE,sBAAsB;IAC5B,QAAQ,EAAE,cAAc;IAExB,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;QAE7C,uBAAuB;QACvB,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QAC5F,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,OAAO,IAAI,YAAY,EAAE,CAAC;wBACnC,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;4BAC7B,QAAQ,CAAC,IAAI,CAAC;gCACZ,EAAE,EAAE,OAAO,OAAO,CAAC,EAAE,IAAI,OAAO,EAAE;gCAClC,QAAQ,EAAE,cAAc;gCACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ;gCAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;gCACpB,WAAW,EAAE,OAAO,CAAC,WAAW;gCAChC,IAAI,EAAE,QAAQ;gCACd,IAAI,EAAE,OAAO,GAAG,CAAC;gCACjB,cAAc,EAAE,OAAO,CAAC,cAAc;gCACtC,cAAc,EAAE,oBAAoB;gCACpC,OAAO,EAAE,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC;gCACrD,OAAO,EAAE,OAAO,CAAC,OAAO;6BACzB,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,13 @@
|
|
|
1
|
+
import type { Severity, FixType } from '../../types.js';
|
|
2
|
+
interface PHIPattern {
|
|
3
|
+
id: string;
|
|
4
|
+
regex: RegExp;
|
|
5
|
+
severity: Severity;
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
recommendation: string;
|
|
9
|
+
fixType?: FixType;
|
|
10
|
+
}
|
|
11
|
+
export declare const PHI_PATTERNS: PHIPattern[];
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=patterns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../../src/scanners/phi/patterns.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAExD,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,eAAO,MAAM,YAAY,EAAE,UAAU,EAuPpC,CAAC"}
|