tlc-claude-code 1.4.7 → 1.4.9

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.
Files changed (170) hide show
  1. package/docker-compose.dev.yml +6 -3
  2. package/package.json +1 -1
  3. package/server/index.js +229 -14
  4. package/server/lib/compliance/control-mapper.js +401 -0
  5. package/server/lib/compliance/control-mapper.test.js +117 -0
  6. package/server/lib/compliance/evidence-linker.js +296 -0
  7. package/server/lib/compliance/evidence-linker.test.js +121 -0
  8. package/server/lib/compliance/gdpr-checklist.js +416 -0
  9. package/server/lib/compliance/gdpr-checklist.test.js +131 -0
  10. package/server/lib/compliance/hipaa-checklist.js +277 -0
  11. package/server/lib/compliance/hipaa-checklist.test.js +101 -0
  12. package/server/lib/compliance/iso27001-checklist.js +287 -0
  13. package/server/lib/compliance/iso27001-checklist.test.js +99 -0
  14. package/server/lib/compliance/multi-framework-reporter.js +284 -0
  15. package/server/lib/compliance/multi-framework-reporter.test.js +127 -0
  16. package/server/lib/compliance/pci-dss-checklist.js +214 -0
  17. package/server/lib/compliance/pci-dss-checklist.test.js +95 -0
  18. package/server/lib/compliance/trust-centre.js +187 -0
  19. package/server/lib/compliance/trust-centre.test.js +93 -0
  20. package/server/lib/dashboard/api-server.js +155 -0
  21. package/server/lib/dashboard/api-server.test.js +155 -0
  22. package/server/lib/dashboard/health-api.js +199 -0
  23. package/server/lib/dashboard/health-api.test.js +122 -0
  24. package/server/lib/dashboard/notes-api.js +234 -0
  25. package/server/lib/dashboard/notes-api.test.js +134 -0
  26. package/server/lib/dashboard/router-api.js +176 -0
  27. package/server/lib/dashboard/router-api.test.js +132 -0
  28. package/server/lib/dashboard/tasks-api.js +289 -0
  29. package/server/lib/dashboard/tasks-api.test.js +161 -0
  30. package/server/lib/dashboard/tlc-introspection.js +197 -0
  31. package/server/lib/dashboard/tlc-introspection.test.js +138 -0
  32. package/server/lib/dashboard/version-api.js +222 -0
  33. package/server/lib/dashboard/version-api.test.js +112 -0
  34. package/server/lib/dashboard/websocket-server.js +104 -0
  35. package/server/lib/dashboard/websocket-server.test.js +118 -0
  36. package/server/lib/deploy/branch-classifier.js +163 -0
  37. package/server/lib/deploy/branch-classifier.test.js +164 -0
  38. package/server/lib/deploy/deployment-approval.js +299 -0
  39. package/server/lib/deploy/deployment-approval.test.js +296 -0
  40. package/server/lib/deploy/deployment-audit.js +374 -0
  41. package/server/lib/deploy/deployment-audit.test.js +307 -0
  42. package/server/lib/deploy/deployment-executor.js +335 -0
  43. package/server/lib/deploy/deployment-executor.test.js +329 -0
  44. package/server/lib/deploy/deployment-rules.js +163 -0
  45. package/server/lib/deploy/deployment-rules.test.js +188 -0
  46. package/server/lib/deploy/rollback-manager.js +379 -0
  47. package/server/lib/deploy/rollback-manager.test.js +321 -0
  48. package/server/lib/deploy/security-gates.js +236 -0
  49. package/server/lib/deploy/security-gates.test.js +222 -0
  50. package/server/lib/k8s/gitops-config.js +188 -0
  51. package/server/lib/k8s/gitops-config.test.js +59 -0
  52. package/server/lib/k8s/helm-generator.js +196 -0
  53. package/server/lib/k8s/helm-generator.test.js +59 -0
  54. package/server/lib/k8s/kustomize-generator.js +176 -0
  55. package/server/lib/k8s/kustomize-generator.test.js +58 -0
  56. package/server/lib/k8s/network-policy.js +114 -0
  57. package/server/lib/k8s/network-policy.test.js +53 -0
  58. package/server/lib/k8s/pod-security.js +114 -0
  59. package/server/lib/k8s/pod-security.test.js +55 -0
  60. package/server/lib/k8s/rbac-generator.js +132 -0
  61. package/server/lib/k8s/rbac-generator.test.js +57 -0
  62. package/server/lib/k8s/resource-manager.js +172 -0
  63. package/server/lib/k8s/resource-manager.test.js +60 -0
  64. package/server/lib/k8s/secrets-encryption.js +168 -0
  65. package/server/lib/k8s/secrets-encryption.test.js +49 -0
  66. package/server/lib/monitoring/alert-manager.js +238 -0
  67. package/server/lib/monitoring/alert-manager.test.js +106 -0
  68. package/server/lib/monitoring/health-check.js +226 -0
  69. package/server/lib/monitoring/health-check.test.js +176 -0
  70. package/server/lib/monitoring/incident-manager.js +230 -0
  71. package/server/lib/monitoring/incident-manager.test.js +98 -0
  72. package/server/lib/monitoring/log-aggregator.js +147 -0
  73. package/server/lib/monitoring/log-aggregator.test.js +89 -0
  74. package/server/lib/monitoring/metrics-collector.js +337 -0
  75. package/server/lib/monitoring/metrics-collector.test.js +172 -0
  76. package/server/lib/monitoring/status-page.js +214 -0
  77. package/server/lib/monitoring/status-page.test.js +105 -0
  78. package/server/lib/monitoring/uptime-monitor.js +194 -0
  79. package/server/lib/monitoring/uptime-monitor.test.js +109 -0
  80. package/server/lib/network/fail2ban-config.js +294 -0
  81. package/server/lib/network/fail2ban-config.test.js +275 -0
  82. package/server/lib/network/firewall-manager.js +252 -0
  83. package/server/lib/network/firewall-manager.test.js +254 -0
  84. package/server/lib/network/geoip-filter.js +282 -0
  85. package/server/lib/network/geoip-filter.test.js +264 -0
  86. package/server/lib/network/rate-limiter.js +229 -0
  87. package/server/lib/network/rate-limiter.test.js +293 -0
  88. package/server/lib/network/request-validator.js +351 -0
  89. package/server/lib/network/request-validator.test.js +345 -0
  90. package/server/lib/network/security-headers.js +251 -0
  91. package/server/lib/network/security-headers.test.js +283 -0
  92. package/server/lib/network/tls-config.js +210 -0
  93. package/server/lib/network/tls-config.test.js +248 -0
  94. package/server/lib/security/auth-security.js +369 -0
  95. package/server/lib/security/auth-security.test.js +448 -0
  96. package/server/lib/security/cis-benchmark.js +152 -0
  97. package/server/lib/security/cis-benchmark.test.js +137 -0
  98. package/server/lib/security/compose-templates.js +312 -0
  99. package/server/lib/security/compose-templates.test.js +229 -0
  100. package/server/lib/security/container-runtime.js +456 -0
  101. package/server/lib/security/container-runtime.test.js +503 -0
  102. package/server/lib/security/cors-validator.js +278 -0
  103. package/server/lib/security/cors-validator.test.js +310 -0
  104. package/server/lib/security/crypto-utils.js +253 -0
  105. package/server/lib/security/crypto-utils.test.js +409 -0
  106. package/server/lib/security/dockerfile-linter.js +459 -0
  107. package/server/lib/security/dockerfile-linter.test.js +483 -0
  108. package/server/lib/security/dockerfile-templates.js +278 -0
  109. package/server/lib/security/dockerfile-templates.test.js +164 -0
  110. package/server/lib/security/error-sanitizer.js +426 -0
  111. package/server/lib/security/error-sanitizer.test.js +331 -0
  112. package/server/lib/security/headers-generator.js +368 -0
  113. package/server/lib/security/headers-generator.test.js +398 -0
  114. package/server/lib/security/image-scanner.js +83 -0
  115. package/server/lib/security/image-scanner.test.js +106 -0
  116. package/server/lib/security/input-validator.js +352 -0
  117. package/server/lib/security/input-validator.test.js +330 -0
  118. package/server/lib/security/network-policy.js +174 -0
  119. package/server/lib/security/network-policy.test.js +164 -0
  120. package/server/lib/security/output-encoder.js +237 -0
  121. package/server/lib/security/output-encoder.test.js +276 -0
  122. package/server/lib/security/path-validator.js +359 -0
  123. package/server/lib/security/path-validator.test.js +293 -0
  124. package/server/lib/security/query-builder.js +421 -0
  125. package/server/lib/security/query-builder.test.js +318 -0
  126. package/server/lib/security/secret-detector.js +290 -0
  127. package/server/lib/security/secret-detector.test.js +354 -0
  128. package/server/lib/security/secrets-validator.js +137 -0
  129. package/server/lib/security/secrets-validator.test.js +120 -0
  130. package/server/lib/security-testing/dast-runner.js +154 -0
  131. package/server/lib/security-testing/dast-runner.test.js +62 -0
  132. package/server/lib/security-testing/dependency-scanner.js +172 -0
  133. package/server/lib/security-testing/dependency-scanner.test.js +64 -0
  134. package/server/lib/security-testing/pentest-runner.js +230 -0
  135. package/server/lib/security-testing/pentest-runner.test.js +60 -0
  136. package/server/lib/security-testing/sast-runner.js +136 -0
  137. package/server/lib/security-testing/sast-runner.test.js +62 -0
  138. package/server/lib/security-testing/secret-scanner.js +153 -0
  139. package/server/lib/security-testing/secret-scanner.test.js +66 -0
  140. package/server/lib/security-testing/security-gate.js +216 -0
  141. package/server/lib/security-testing/security-gate.test.js +115 -0
  142. package/server/lib/security-testing/security-reporter.js +303 -0
  143. package/server/lib/security-testing/security-reporter.test.js +114 -0
  144. package/server/lib/standards/audit-checker.js +546 -0
  145. package/server/lib/standards/audit-checker.test.js +415 -0
  146. package/server/lib/standards/cleanup-executor.js +452 -0
  147. package/server/lib/standards/cleanup-executor.test.js +293 -0
  148. package/server/lib/standards/refactor-stepper.js +425 -0
  149. package/server/lib/standards/refactor-stepper.test.js +298 -0
  150. package/server/lib/standards/standards-injector.js +167 -0
  151. package/server/lib/standards/standards-injector.test.js +232 -0
  152. package/server/lib/user-management.test.js +284 -0
  153. package/server/lib/vps/backup-manager.js +157 -0
  154. package/server/lib/vps/backup-manager.test.js +59 -0
  155. package/server/lib/vps/caddy-config.js +159 -0
  156. package/server/lib/vps/caddy-config.test.js +48 -0
  157. package/server/lib/vps/compose-orchestrator.js +219 -0
  158. package/server/lib/vps/compose-orchestrator.test.js +50 -0
  159. package/server/lib/vps/database-config.js +208 -0
  160. package/server/lib/vps/database-config.test.js +47 -0
  161. package/server/lib/vps/deploy-script.js +211 -0
  162. package/server/lib/vps/deploy-script.test.js +53 -0
  163. package/server/lib/vps/secrets-manager.js +148 -0
  164. package/server/lib/vps/secrets-manager.test.js +58 -0
  165. package/server/lib/vps/server-hardening.js +174 -0
  166. package/server/lib/vps/server-hardening.test.js +70 -0
  167. package/server/package-lock.json +19 -0
  168. package/server/package.json +1 -0
  169. package/server/templates/CLAUDE.md +37 -0
  170. package/server/templates/CODING-STANDARDS.md +408 -0
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Control Mapper Tests
3
+ */
4
+ import { describe, it, expect, vi } from 'vitest';
5
+ import { createControlMapper, mapControl, findOverlaps, generateCrossReference, importFramework } from './control-mapper.js';
6
+
7
+ describe('control-mapper', () => {
8
+ describe('createControlMapper', () => {
9
+ it('creates control mapper', () => {
10
+ const mapper = createControlMapper();
11
+ expect(mapper.map).toBeDefined();
12
+ expect(mapper.findOverlaps).toBeDefined();
13
+ });
14
+
15
+ it('loads default mappings', () => {
16
+ const mapper = createControlMapper({ loadDefaults: true });
17
+ const mappings = mapper.getMappings();
18
+ expect(mappings.length).toBeGreaterThan(0);
19
+ });
20
+ });
21
+
22
+ describe('mapControl', () => {
23
+ it('maps control between frameworks', () => {
24
+ const mapping = mapControl({
25
+ source: { framework: 'pci-dss', control: 'req-3.4' },
26
+ target: { framework: 'iso27001' }
27
+ });
28
+ expect(mapping.targetControls.length).toBeGreaterThan(0);
29
+ expect(mapping.targetControls.some(c => c.startsWith('A.8'))).toBe(true);
30
+ });
31
+
32
+ it('provides mapping confidence', () => {
33
+ const mapping = mapControl({
34
+ source: { framework: 'pci-dss', control: 'req-1.1' },
35
+ target: { framework: 'iso27001' }
36
+ });
37
+ expect(mapping.confidence).toBeDefined();
38
+ expect(mapping.confidence).toBeGreaterThan(0);
39
+ });
40
+
41
+ it('handles unmapped controls', () => {
42
+ const mapping = mapControl({
43
+ source: { framework: 'custom', control: 'unknown' },
44
+ target: { framework: 'iso27001' }
45
+ });
46
+ expect(mapping.targetControls).toHaveLength(0);
47
+ expect(mapping.unmapped).toBe(true);
48
+ });
49
+ });
50
+
51
+ describe('findOverlaps', () => {
52
+ it('finds overlapping controls', () => {
53
+ const overlaps = findOverlaps(['pci-dss', 'iso27001', 'hipaa']);
54
+ expect(overlaps.length).toBeGreaterThan(0);
55
+ });
56
+
57
+ it('identifies common control themes', () => {
58
+ const overlaps = findOverlaps(['pci-dss', 'hipaa']);
59
+ expect(overlaps.some(o => o.theme === 'encryption')).toBe(true);
60
+ expect(overlaps.some(o => o.theme === 'access-control')).toBe(true);
61
+ });
62
+
63
+ it('calculates overlap percentage', () => {
64
+ const overlaps = findOverlaps(['pci-dss', 'iso27001']);
65
+ expect(overlaps[0].overlapPercentage).toBeDefined();
66
+ });
67
+ });
68
+
69
+ describe('generateCrossReference', () => {
70
+ it('generates cross-reference matrix', () => {
71
+ const matrix = generateCrossReference(['pci-dss', 'iso27001', 'hipaa']);
72
+ expect(matrix['pci-dss']).toBeDefined();
73
+ expect(matrix['pci-dss']['iso27001']).toBeDefined();
74
+ });
75
+
76
+ it('outputs as markdown table', () => {
77
+ const markdown = generateCrossReference(['pci-dss', 'iso27001'], { format: 'markdown' });
78
+ expect(markdown).toContain('|');
79
+ expect(markdown).toContain('PCI DSS');
80
+ });
81
+
82
+ it('outputs as CSV', () => {
83
+ const csv = generateCrossReference(['pci-dss', 'iso27001'], { format: 'csv' });
84
+ expect(csv).toContain(',');
85
+ });
86
+ });
87
+
88
+ describe('importFramework', () => {
89
+ it('imports custom framework', () => {
90
+ const framework = {
91
+ id: 'custom-framework',
92
+ name: 'Custom Framework',
93
+ controls: [
94
+ { id: 'CF-1', name: 'Access Control', mappings: ['iso27001:A.5.15'] }
95
+ ]
96
+ };
97
+ const result = importFramework(framework);
98
+ expect(result.imported).toBe(true);
99
+ expect(result.controlCount).toBe(1);
100
+ });
101
+
102
+ it('validates framework structure', () => {
103
+ const invalidFramework = { controls: [] };
104
+ expect(() => importFramework(invalidFramework)).toThrow(/id.*required/i);
105
+ });
106
+
107
+ it('auto-maps to existing frameworks', () => {
108
+ const framework = {
109
+ id: 'custom',
110
+ name: 'Custom',
111
+ controls: [{ id: 'C-1', keywords: ['encryption', 'data protection'] }]
112
+ };
113
+ const result = importFramework(framework, { autoMap: true });
114
+ expect(result.mappingsCreated).toBeGreaterThan(0);
115
+ });
116
+ });
117
+ });
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Evidence Linker - Evidence linking to controls
3
+ */
4
+
5
+ import { randomUUID } from 'crypto';
6
+
7
+ // Supported evidence types
8
+ const SUPPORTED_TYPES = ['code', 'config', 'test', 'document', 'screenshot', 'log'];
9
+
10
+ // Compliance annotation patterns
11
+ const ANNOTATION_PATTERNS = [
12
+ { regex: /@compliance\s+([^\s]+)/i, group: 1 },
13
+ { regex: /@control\s+([^\s]+)/i, group: 1 },
14
+ { regex: /@evidence\s+(.+)/i, group: 1, isDescription: true }
15
+ ];
16
+
17
+ // Implicit evidence patterns
18
+ const IMPLICIT_PATTERNS = [
19
+ { pattern: /crypto\.create(?:Cipher|Decipher|Hash)/i, theme: 'encryption' },
20
+ { pattern: /aes-\d+-[a-z]+/i, theme: 'encryption' },
21
+ { pattern: /createCipheriv/i, theme: 'encryption' },
22
+ { pattern: /bcrypt|argon2|scrypt/i, theme: 'encryption' },
23
+ { pattern: /jwt|jsonwebtoken|oauth/i, theme: 'authentication' },
24
+ { pattern: /authenticate|login|session/i, theme: 'authentication' },
25
+ { pattern: /authorize|permission|role/i, theme: 'access-control' },
26
+ { pattern: /audit|log.*event/i, theme: 'logging' },
27
+ { pattern: /firewall|iptables|security.?group/i, theme: 'network-security' }
28
+ ];
29
+
30
+ // Evidence storage
31
+ const linkedEvidence = new Map();
32
+
33
+ /**
34
+ * Create an evidence linker instance
35
+ */
36
+ export function createEvidenceLinker() {
37
+ return {
38
+ link: (evidence) => linkEvidence(evidence),
39
+ scan: (options) => scanCodebase(options),
40
+ getSupportedTypes: () => [...SUPPORTED_TYPES],
41
+ getLinkedEvidence: () => [...linkedEvidence.values()]
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Link evidence to a control
47
+ */
48
+ export function linkEvidence(evidence) {
49
+ const result = {
50
+ linked: false,
51
+ evidenceId: null,
52
+ errors: []
53
+ };
54
+
55
+ // Validate required fields
56
+ if (!evidence.controlId) {
57
+ result.errors.push('controlId is required');
58
+ }
59
+
60
+ if (!evidence.type && !evidence.file) {
61
+ result.errors.push('type or file is required');
62
+ }
63
+
64
+ if (evidence.type && !SUPPORTED_TYPES.includes(evidence.type)) {
65
+ result.errors.push(`Invalid evidence type: ${evidence.type}. Supported: ${SUPPORTED_TYPES.join(', ')}`);
66
+ }
67
+
68
+ // If there are validation errors, return early
69
+ if (result.errors.length > 0) {
70
+ return result;
71
+ }
72
+
73
+ // Generate evidence ID and store
74
+ const evidenceId = randomUUID();
75
+ const evidenceRecord = {
76
+ id: evidenceId,
77
+ controlId: evidence.controlId,
78
+ type: evidence.type || 'code',
79
+ file: evidence.file,
80
+ lines: evidence.lines,
81
+ description: evidence.description,
82
+ linkedAt: new Date().toISOString()
83
+ };
84
+
85
+ linkedEvidence.set(evidenceId, evidenceRecord);
86
+
87
+ result.linked = true;
88
+ result.evidenceId = evidenceId;
89
+
90
+ return result;
91
+ }
92
+
93
+ /**
94
+ * Scan codebase for compliance evidence
95
+ */
96
+ export async function scanCodebase(options = {}) {
97
+ const { glob, readFile, detectPatterns = false } = options;
98
+ const results = [];
99
+
100
+ if (!glob || !readFile) {
101
+ return results;
102
+ }
103
+
104
+ try {
105
+ const files = await glob('**/*.{js,ts,jsx,tsx,py,go,java}');
106
+
107
+ for (const file of files) {
108
+ try {
109
+ const content = await readFile(file);
110
+ const lines = content.split('\n');
111
+
112
+ // Scan for explicit annotations
113
+ for (let i = 0; i < lines.length; i++) {
114
+ const line = lines[i];
115
+
116
+ for (const annotationPattern of ANNOTATION_PATTERNS) {
117
+ const match = line.match(annotationPattern.regex);
118
+ if (match) {
119
+ if (annotationPattern.isDescription) {
120
+ // @evidence annotation - attach to previous result
121
+ if (results.length > 0) {
122
+ results[results.length - 1].description = match[annotationPattern.group];
123
+ }
124
+ } else {
125
+ results.push({
126
+ file,
127
+ line: i + 1,
128
+ controlId: match[annotationPattern.group],
129
+ type: 'annotation'
130
+ });
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ // Scan for implicit patterns if requested
137
+ if (detectPatterns) {
138
+ for (const implicitPattern of IMPLICIT_PATTERNS) {
139
+ if (implicitPattern.pattern.test(content)) {
140
+ results.push({
141
+ file,
142
+ pattern: implicitPattern.theme,
143
+ type: 'implicit',
144
+ confidence: 0.7
145
+ });
146
+ }
147
+ }
148
+ }
149
+ } catch (err) {
150
+ // Skip unreadable files
151
+ }
152
+ }
153
+ } catch (err) {
154
+ // Return empty results on glob error
155
+ }
156
+
157
+ return results;
158
+ }
159
+
160
+ /**
161
+ * Generate an evidence report for a framework
162
+ */
163
+ export function generateEvidenceReport(evidence, options = {}) {
164
+ const { framework, showGaps = false } = options;
165
+
166
+ let report = `# Evidence Report\n\n`;
167
+
168
+ if (framework) {
169
+ const frameworkNames = {
170
+ 'pci-dss': 'PCI DSS',
171
+ 'iso27001': 'ISO 27001',
172
+ 'hipaa': 'HIPAA'
173
+ };
174
+ report += `**Framework:** ${frameworkNames[framework] || framework}\n\n`;
175
+ }
176
+
177
+ report += `## Linked Evidence\n\n`;
178
+
179
+ if (evidence.length === 0) {
180
+ report += `No evidence linked.\n\n`;
181
+ } else {
182
+ report += `| Control | Type | File | Description |\n`;
183
+ report += `|---------|------|------|-------------|\n`;
184
+
185
+ for (const e of evidence) {
186
+ report += `| ${e.controlId} | ${e.type || '-'} | ${e.file || '-'} | ${e.description || '-'} |\n`;
187
+ }
188
+ report += '\n';
189
+ }
190
+
191
+ if (showGaps) {
192
+ report += `## Missing Evidence\n\n`;
193
+ report += `The following controls are missing evidence documentation:\n\n`;
194
+
195
+ // Get framework controls that don't have evidence
196
+ const coveredControls = new Set(evidence.map(e => e.controlId));
197
+ const frameworkControls = getFrameworkControls(framework);
198
+ const missingControls = frameworkControls.filter(c => !coveredControls.has(c));
199
+
200
+ if (missingControls.length > 0) {
201
+ for (const control of missingControls) {
202
+ report += `- ${control}\n`;
203
+ }
204
+ } else {
205
+ report += `All controls have evidence linked.\n`;
206
+ }
207
+ report += '\n';
208
+ }
209
+
210
+ return report;
211
+ }
212
+
213
+ /**
214
+ * Get controls for a framework
215
+ */
216
+ function getFrameworkControls(framework) {
217
+ const controls = {
218
+ 'pci-dss': [
219
+ 'req-1.1', 'req-1.2', 'req-1.3',
220
+ 'req-2.1', 'req-2.2',
221
+ 'req-3.1', 'req-3.2', 'req-3.4',
222
+ 'req-4.1',
223
+ 'req-5.1', 'req-5.2',
224
+ 'req-6.1', 'req-6.2',
225
+ 'req-7.1', 'req-7.2',
226
+ 'req-8.1', 'req-8.2',
227
+ 'req-9.1',
228
+ 'req-10.1', 'req-10.2', 'req-10.3',
229
+ 'req-11.1', 'req-11.2',
230
+ 'req-12.1'
231
+ ],
232
+ 'iso27001': [
233
+ 'A.5.1', 'A.5.15', 'A.5.16', 'A.5.17',
234
+ 'A.6.1', 'A.6.2',
235
+ 'A.7.1', 'A.7.2',
236
+ 'A.8.1', 'A.8.15', 'A.8.24',
237
+ 'A.9.1',
238
+ 'A.10.1',
239
+ 'A.11.1',
240
+ 'A.12.1',
241
+ 'A.13.1', 'A.13.2'
242
+ ],
243
+ 'hipaa': [
244
+ '164.312(a)(1)', '164.312(a)(2)(iv)',
245
+ '164.312(b)',
246
+ '164.312(c)(1)',
247
+ '164.312(d)',
248
+ '164.312(e)(1)', '164.312(e)(2)(ii)'
249
+ ]
250
+ };
251
+
252
+ return controls[framework] || [];
253
+ }
254
+
255
+ /**
256
+ * Validate evidence
257
+ * Returns a Promise if checkExists is used with an async exists function,
258
+ * otherwise returns the result synchronously
259
+ */
260
+ export function validateEvidence(evidence, options = {}) {
261
+ const result = {
262
+ valid: true,
263
+ issues: []
264
+ };
265
+
266
+ // Check freshness
267
+ if (options.maxAge && evidence.lastModified) {
268
+ const age = (Date.now() - new Date(evidence.lastModified).getTime()) / (24 * 60 * 60 * 1000);
269
+ if (age > options.maxAge) {
270
+ result.valid = false;
271
+ result.issues.push('stale');
272
+ }
273
+ }
274
+
275
+ // Check file exists - this is async
276
+ if (options.checkExists && evidence.file) {
277
+ const existsCheck = options.exists ? options.exists(evidence.file) : Promise.resolve(true);
278
+ return Promise.resolve(existsCheck).then(exists => {
279
+ if (!exists) {
280
+ result.valid = false;
281
+ result.issues.push('file-not-found');
282
+ }
283
+ return result;
284
+ });
285
+ }
286
+
287
+ return result;
288
+ }
289
+
290
+ export default {
291
+ createEvidenceLinker,
292
+ linkEvidence,
293
+ scanCodebase,
294
+ generateEvidenceReport,
295
+ validateEvidence
296
+ };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Evidence Linker Tests
3
+ */
4
+ import { describe, it, expect, vi } from 'vitest';
5
+ import { createEvidenceLinker, linkEvidence, scanCodebase, generateEvidenceReport, validateEvidence } from './evidence-linker.js';
6
+
7
+ describe('evidence-linker', () => {
8
+ describe('createEvidenceLinker', () => {
9
+ it('creates evidence linker', () => {
10
+ const linker = createEvidenceLinker();
11
+ expect(linker.link).toBeDefined();
12
+ expect(linker.scan).toBeDefined();
13
+ });
14
+
15
+ it('supports multiple evidence types', () => {
16
+ const linker = createEvidenceLinker();
17
+ const types = linker.getSupportedTypes();
18
+ expect(types).toContain('code');
19
+ expect(types).toContain('config');
20
+ expect(types).toContain('test');
21
+ expect(types).toContain('document');
22
+ });
23
+ });
24
+
25
+ describe('linkEvidence', () => {
26
+ it('links code to control', () => {
27
+ const evidence = {
28
+ controlId: 'pci-dss:req-3.4',
29
+ type: 'code',
30
+ file: 'src/encryption.js',
31
+ lines: '10-25',
32
+ description: 'AES-256 encryption implementation'
33
+ };
34
+ const result = linkEvidence(evidence);
35
+ expect(result.linked).toBe(true);
36
+ expect(result.evidenceId).toBeDefined();
37
+ });
38
+
39
+ it('links test to control', () => {
40
+ const evidence = {
41
+ controlId: 'iso27001:A.8.24',
42
+ type: 'test',
43
+ file: 'tests/encryption.test.js',
44
+ description: 'Encryption tests'
45
+ };
46
+ const result = linkEvidence(evidence);
47
+ expect(result.linked).toBe(true);
48
+ });
49
+
50
+ it('validates evidence completeness', () => {
51
+ const evidence = { controlId: 'test' };
52
+ const result = linkEvidence(evidence);
53
+ expect(result.linked).toBe(false);
54
+ expect(result.errors.length).toBeGreaterThan(0);
55
+ });
56
+ });
57
+
58
+ describe('scanCodebase', () => {
59
+ it('scans codebase for evidence', async () => {
60
+ const mockGlob = vi.fn().mockResolvedValue(['src/auth.js', 'src/encryption.js']);
61
+ const mockRead = vi.fn().mockResolvedValue('// @compliance pci-dss:req-8.1\nfunction authenticate() {}');
62
+
63
+ const results = await scanCodebase({ glob: mockGlob, readFile: mockRead });
64
+ expect(results.length).toBeGreaterThan(0);
65
+ });
66
+
67
+ it('identifies compliance annotations', async () => {
68
+ const mockGlob = vi.fn().mockResolvedValue(['test.js']);
69
+ const mockRead = vi.fn().mockResolvedValue('// @control iso27001:A.5.15\n// @evidence access control implementation');
70
+
71
+ const results = await scanCodebase({ glob: mockGlob, readFile: mockRead });
72
+ expect(results[0].controlId).toBe('iso27001:A.5.15');
73
+ });
74
+
75
+ it('detects implicit evidence patterns', async () => {
76
+ const mockGlob = vi.fn().mockResolvedValue(['src/crypto.js']);
77
+ const mockRead = vi.fn().mockResolvedValue('crypto.createCipheriv("aes-256-gcm", key, iv)');
78
+
79
+ const results = await scanCodebase({ glob: mockGlob, readFile: mockRead, detectPatterns: true });
80
+ expect(results.some(r => r.pattern === 'encryption')).toBe(true);
81
+ });
82
+ });
83
+
84
+ describe('generateEvidenceReport', () => {
85
+ it('generates evidence report for framework', () => {
86
+ const evidence = [
87
+ { controlId: 'req-1.1', type: 'code', file: 'firewall.js' },
88
+ { controlId: 'req-3.4', type: 'test', file: 'encryption.test.js' }
89
+ ];
90
+ const report = generateEvidenceReport(evidence, { framework: 'pci-dss' });
91
+ expect(report).toContain('Evidence Report');
92
+ expect(report).toContain('req-1.1');
93
+ });
94
+
95
+ it('identifies controls without evidence', () => {
96
+ const evidence = [{ controlId: 'req-1.1', type: 'code', file: 'test.js' }];
97
+ const report = generateEvidenceReport(evidence, { framework: 'pci-dss', showGaps: true });
98
+ expect(report).toContain('Missing Evidence');
99
+ });
100
+ });
101
+
102
+ describe('validateEvidence', () => {
103
+ it('validates evidence freshness', () => {
104
+ const evidence = {
105
+ controlId: 'test',
106
+ file: 'test.js',
107
+ lastModified: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000)
108
+ };
109
+ const result = validateEvidence(evidence, { maxAge: 90 });
110
+ expect(result.valid).toBe(false);
111
+ expect(result.issues).toContain('stale');
112
+ });
113
+
114
+ it('validates evidence file exists', async () => {
115
+ const mockExists = vi.fn().mockResolvedValue(false);
116
+ const evidence = { controlId: 'test', file: 'deleted.js' };
117
+ const result = await validateEvidence(evidence, { checkExists: true, exists: mockExists });
118
+ expect(result.valid).toBe(false);
119
+ });
120
+ });
121
+ });