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
@@ -101,9 +101,12 @@ services:
101
101
  working_dir: /project
102
102
  command: >
103
103
  sh -c "
104
- npm install -g tlc-claude-code &&
105
- TLC_DIR=$$(npm root -g)/tlc-claude-code &&
106
- cd /project && node $$TLC_DIR/server/index.js --proxy-only
104
+ echo 'Installing TLC...' &&
105
+ npm install -g tlc-claude-code@latest &&
106
+ TLC_DIR=/usr/local/lib/node_modules/tlc-claude-code &&
107
+ echo 'TLC installed at:' $$TLC_DIR &&
108
+ ls -la $$TLC_DIR/server/ &&
109
+ cd /project && node $$TLC_DIR/server/index.js --proxy-only --skip-db
107
110
  "
108
111
  environment:
109
112
  - TLC_PORT=3147
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tlc-claude-code",
3
- "version": "1.4.7",
3
+ "version": "1.4.9",
4
4
  "description": "TLC - Test Led Coding for Claude Code",
5
5
  "bin": {
6
6
  "tlc": "./bin/tlc.js",
package/server/index.js CHANGED
@@ -19,6 +19,8 @@ const {
19
19
  verifyJWT,
20
20
  hashPassword,
21
21
  verifyPassword,
22
+ hasPermission,
23
+ USER_ROLES,
22
24
  } = require('./lib/auth-system');
23
25
 
24
26
  // Handle PGlite WASM crashes gracefully
@@ -66,39 +68,102 @@ const userStore = createUserStore();
66
68
  const JWT_SECRET = process.env.TLC_JWT_SECRET || 'tlc-dashboard-secret-change-in-production';
67
69
  const AUTH_ENABLED = process.env.TLC_AUTH !== 'false';
68
70
 
69
- // Initialize admin user from config or environment
71
+ // Initialize users from config or environment
70
72
  async function initializeAuth() {
71
73
  const tlcConfigPath = path.join(PROJECT_DIR, '.tlc.json');
72
- let adminEmail = process.env.TLC_ADMIN_EMAIL || 'admin@localhost';
73
- let adminPassword = process.env.TLC_ADMIN_PASSWORD;
74
+ let users = [];
74
75
 
75
76
  // Try to read from .tlc.json
76
77
  if (fs.existsSync(tlcConfigPath)) {
77
78
  try {
78
79
  const config = JSON.parse(fs.readFileSync(tlcConfigPath, 'utf-8'));
79
- if (config.auth?.adminEmail) adminEmail = config.auth.adminEmail;
80
- if (config.auth?.adminPassword) adminPassword = config.auth.adminPassword;
80
+
81
+ // Support new multi-user format: auth.users array
82
+ if (config.auth?.users && Array.isArray(config.auth.users)) {
83
+ users = config.auth.users;
84
+ }
85
+ // Also support legacy single admin format for backwards compatibility
86
+ else if (config.auth?.adminEmail && config.auth?.adminPassword) {
87
+ users.push({
88
+ email: config.auth.adminEmail,
89
+ password: config.auth.adminPassword,
90
+ name: config.auth.adminName || 'Admin',
91
+ role: 'admin',
92
+ });
93
+ }
81
94
  } catch (e) {
82
- // Ignore parse errors
95
+ console.error('[TLC] Failed to parse .tlc.json:', e.message);
83
96
  }
84
97
  }
85
98
 
86
- // Create admin user if password is set
87
- if (adminPassword) {
99
+ // Also check environment variables for admin
100
+ if (process.env.TLC_ADMIN_EMAIL && process.env.TLC_ADMIN_PASSWORD) {
101
+ const envAdmin = {
102
+ email: process.env.TLC_ADMIN_EMAIL,
103
+ password: process.env.TLC_ADMIN_PASSWORD,
104
+ name: process.env.TLC_ADMIN_NAME || 'Admin',
105
+ role: 'admin',
106
+ };
107
+ // Don't duplicate if already in config
108
+ if (!users.find(u => u.email === envAdmin.email)) {
109
+ users.push(envAdmin);
110
+ }
111
+ }
112
+
113
+ // Create all users
114
+ for (const userData of users) {
115
+ if (!userData.email || !userData.password) {
116
+ console.warn('[TLC] Skipping user without email or password');
117
+ continue;
118
+ }
119
+
88
120
  try {
89
121
  await userStore.createUser({
90
- email: adminEmail,
91
- password: adminPassword,
92
- name: 'Admin',
93
- role: 'admin',
122
+ email: userData.email,
123
+ password: userData.password,
124
+ name: userData.name || userData.email.split('@')[0],
125
+ role: userData.role || 'engineer',
94
126
  }, { skipValidation: true }); // Dev tool - allow simple passwords
95
- console.log(`[TLC] Admin user initialized: ${adminEmail}`);
127
+ console.log(`[TLC] User initialized: ${userData.email} (${userData.role || 'engineer'})`);
96
128
  } catch (e) {
97
129
  if (!e.message.includes('already registered')) {
98
- console.error('[TLC] Failed to create admin user:', e.message);
130
+ console.error(`[TLC] Failed to create user ${userData.email}:`, e.message);
99
131
  }
100
132
  }
101
133
  }
134
+
135
+ const userCount = await userStore.getUserCount();
136
+ if (userCount === 0) {
137
+ console.log('[TLC] No users configured. Add users to .tlc.json:');
138
+ console.log('[TLC] "auth": { "users": [{ "email": "...", "password": "...", "role": "admin" }] }');
139
+ }
140
+ }
141
+
142
+ // Role-based permission middleware
143
+ function requireRole(...allowedRoles) {
144
+ return (req, res, next) => {
145
+ if (!req.user) {
146
+ return res.status(401).json({ error: 'Authentication required' });
147
+ }
148
+ if (!allowedRoles.includes(req.user.role) && req.user.role !== 'admin') {
149
+ return res.status(403).json({ error: 'Insufficient permissions' });
150
+ }
151
+ next();
152
+ };
153
+ }
154
+
155
+ // Permission-based middleware
156
+ function requirePermission(permission) {
157
+ return async (req, res, next) => {
158
+ if (!req.user) {
159
+ return res.status(401).json({ error: 'Authentication required' });
160
+ }
161
+ const user = await userStore.findUserById(req.user.sub);
162
+ if (!user || !hasPermission(user, permission)) {
163
+ return res.status(403).json({ error: 'Permission denied' });
164
+ }
165
+ next();
166
+ };
102
167
  }
103
168
 
104
169
  // Auth middleware for protected routes
@@ -198,6 +263,156 @@ app.get('/api/auth/me', (req, res) => {
198
263
  res.json({ user: req.user });
199
264
  });
200
265
 
266
+ // ============================================
267
+ // User Management API (Admin only)
268
+ // ============================================
269
+
270
+ // List all users
271
+ app.get('/api/users', requireRole('admin'), async (req, res) => {
272
+ try {
273
+ const users = await userStore.listUsers();
274
+ res.json({ users });
275
+ } catch (e) {
276
+ res.status(500).json({ error: e.message });
277
+ }
278
+ });
279
+
280
+ // Get single user
281
+ app.get('/api/users/:id', requireRole('admin'), async (req, res) => {
282
+ try {
283
+ const user = await userStore.findUserById(req.params.id);
284
+ if (!user) {
285
+ return res.status(404).json({ error: 'User not found' });
286
+ }
287
+ // Don't return password hash
288
+ const { passwordHash, passwordSalt, ...safeUser } = user;
289
+ res.json({ user: safeUser });
290
+ } catch (e) {
291
+ res.status(500).json({ error: e.message });
292
+ }
293
+ });
294
+
295
+ // Create new user
296
+ app.post('/api/users', requireRole('admin'), async (req, res) => {
297
+ const { email, password, name, role } = req.body;
298
+
299
+ if (!email || !password) {
300
+ return res.status(400).json({ error: 'Email and password required' });
301
+ }
302
+
303
+ // Validate role
304
+ const validRoles = ['admin', 'engineer', 'qa', 'po'];
305
+ if (role && !validRoles.includes(role)) {
306
+ return res.status(400).json({ error: `Invalid role. Valid roles: ${validRoles.join(', ')}` });
307
+ }
308
+
309
+ try {
310
+ const user = await userStore.createUser({
311
+ email,
312
+ password,
313
+ name: name || email.split('@')[0],
314
+ role: role || 'engineer',
315
+ }, { skipValidation: true }); // Allow simple passwords for dev
316
+
317
+ res.status(201).json({ user });
318
+ } catch (e) {
319
+ if (e.message.includes('already registered')) {
320
+ return res.status(409).json({ error: 'Email already registered' });
321
+ }
322
+ res.status(500).json({ error: e.message });
323
+ }
324
+ });
325
+
326
+ // Update user
327
+ app.put('/api/users/:id', requireRole('admin'), async (req, res) => {
328
+ const { name, role, active } = req.body;
329
+
330
+ // Validate role if provided
331
+ const validRoles = ['admin', 'engineer', 'qa', 'po'];
332
+ if (role && !validRoles.includes(role)) {
333
+ return res.status(400).json({ error: `Invalid role. Valid roles: ${validRoles.join(', ')}` });
334
+ }
335
+
336
+ try {
337
+ const updates = {};
338
+ if (name !== undefined) updates.name = name;
339
+ if (role !== undefined) updates.role = role;
340
+ if (active !== undefined) updates.active = active;
341
+
342
+ const user = await userStore.updateUser(req.params.id, updates);
343
+ if (!user) {
344
+ return res.status(404).json({ error: 'User not found' });
345
+ }
346
+
347
+ res.json({ user });
348
+ } catch (e) {
349
+ res.status(500).json({ error: e.message });
350
+ }
351
+ });
352
+
353
+ // Delete user
354
+ app.delete('/api/users/:id', requireRole('admin'), async (req, res) => {
355
+ // Prevent self-deletion
356
+ if (req.user.sub === req.params.id) {
357
+ return res.status(400).json({ error: 'Cannot delete your own account' });
358
+ }
359
+
360
+ try {
361
+ const deleted = await userStore.deleteUser(req.params.id);
362
+ if (!deleted) {
363
+ return res.status(404).json({ error: 'User not found' });
364
+ }
365
+
366
+ // Also invalidate their sessions
367
+ await userStore.invalidateUserSessions(req.params.id);
368
+
369
+ res.json({ success: true });
370
+ } catch (e) {
371
+ res.status(500).json({ error: e.message });
372
+ }
373
+ });
374
+
375
+ // Reset user password (admin only)
376
+ app.post('/api/users/:id/reset-password', requireRole('admin'), async (req, res) => {
377
+ const { newPassword } = req.body;
378
+
379
+ if (!newPassword) {
380
+ return res.status(400).json({ error: 'New password required' });
381
+ }
382
+
383
+ try {
384
+ const user = await userStore.findUserById(req.params.id);
385
+ if (!user) {
386
+ return res.status(404).json({ error: 'User not found' });
387
+ }
388
+
389
+ // Use the internal hash function
390
+ const { hash, salt } = hashPassword(newPassword);
391
+ user.passwordHash = hash;
392
+ user.passwordSalt = salt;
393
+ user.updatedAt = new Date().toISOString();
394
+
395
+ // Invalidate all sessions for security
396
+ await userStore.invalidateUserSessions(req.params.id);
397
+
398
+ res.json({ success: true, message: 'Password reset successfully' });
399
+ } catch (e) {
400
+ res.status(500).json({ error: e.message });
401
+ }
402
+ });
403
+
404
+ // Get available roles
405
+ app.get('/api/roles', (req, res) => {
406
+ res.json({
407
+ roles: [
408
+ { id: 'admin', name: 'Admin', description: 'Full access to all features' },
409
+ { id: 'engineer', name: 'Engineer', description: 'Can read, write, deploy, and claim tasks' },
410
+ { id: 'qa', name: 'QA', description: 'Can read, verify, report bugs, and run tests' },
411
+ { id: 'po', name: 'Product Owner', description: 'Can read, plan, verify, and approve' },
412
+ ],
413
+ });
414
+ });
415
+
201
416
  // Serve static files (after auth middleware)
202
417
  app.use(express.static(path.join(__dirname, 'dashboard')));
203
418
 
@@ -0,0 +1,401 @@
1
+ /**
2
+ * Control Mapper - Cross-framework control mapping
3
+ */
4
+
5
+ // Default control mappings between frameworks
6
+ const DEFAULT_MAPPINGS = [
7
+ {
8
+ source: { framework: 'pci-dss', control: 'req-1.1' },
9
+ target: { framework: 'iso27001', control: 'A.13.1' },
10
+ theme: 'network-security',
11
+ confidence: 0.85
12
+ },
13
+ {
14
+ source: { framework: 'pci-dss', control: 'req-3.4' },
15
+ target: { framework: 'iso27001', control: 'A.8.24' },
16
+ theme: 'encryption',
17
+ confidence: 0.95
18
+ },
19
+ {
20
+ source: { framework: 'pci-dss', control: 'req-3.4' },
21
+ target: { framework: 'hipaa', control: '164.312(a)(2)(iv)' },
22
+ theme: 'encryption',
23
+ confidence: 0.90
24
+ },
25
+ {
26
+ source: { framework: 'pci-dss', control: 'req-8.1' },
27
+ target: { framework: 'iso27001', control: 'A.5.15' },
28
+ theme: 'access-control',
29
+ confidence: 0.90
30
+ },
31
+ {
32
+ source: { framework: 'pci-dss', control: 'req-8.1' },
33
+ target: { framework: 'hipaa', control: '164.312(d)' },
34
+ theme: 'access-control',
35
+ confidence: 0.85
36
+ },
37
+ {
38
+ source: { framework: 'hipaa', control: '164.312(a)(1)' },
39
+ target: { framework: 'iso27001', control: 'A.5.15' },
40
+ theme: 'access-control',
41
+ confidence: 0.85
42
+ },
43
+ {
44
+ source: { framework: 'hipaa', control: '164.312(e)(1)' },
45
+ target: { framework: 'iso27001', control: 'A.8.24' },
46
+ theme: 'encryption',
47
+ confidence: 0.90
48
+ },
49
+ {
50
+ source: { framework: 'hipaa', control: '164.312(e)(1)' },
51
+ target: { framework: 'pci-dss', control: 'req-3.4' },
52
+ theme: 'encryption',
53
+ confidence: 0.85
54
+ }
55
+ ];
56
+
57
+ // Framework-specific control themes and keywords
58
+ const FRAMEWORK_THEMES = {
59
+ 'pci-dss': {
60
+ encryption: ['req-3.4', 'req-3.5', 'req-4.1'],
61
+ 'access-control': ['req-7.1', 'req-7.2', 'req-8.1', 'req-8.2'],
62
+ 'network-security': ['req-1.1', 'req-1.2', 'req-1.3'],
63
+ logging: ['req-10.1', 'req-10.2', 'req-10.3']
64
+ },
65
+ 'iso27001': {
66
+ encryption: ['A.8.24', 'A.10.1'],
67
+ 'access-control': ['A.5.15', 'A.5.16', 'A.5.17', 'A.5.18'],
68
+ 'network-security': ['A.13.1', 'A.13.2'],
69
+ logging: ['A.8.15', 'A.8.16']
70
+ },
71
+ 'hipaa': {
72
+ encryption: ['164.312(a)(2)(iv)', '164.312(e)(1)', '164.312(e)(2)(ii)'],
73
+ 'access-control': ['164.312(a)(1)', '164.312(d)'],
74
+ 'network-security': ['164.312(e)(1)'],
75
+ logging: ['164.312(b)']
76
+ }
77
+ };
78
+
79
+ // Unique requirements per framework
80
+ const UNIQUE_REQUIREMENTS = {
81
+ 'pci-dss': [
82
+ 'Payment card industry specific requirements',
83
+ 'PAN masking and truncation',
84
+ 'Quarterly vulnerability scans'
85
+ ],
86
+ 'hipaa': [
87
+ 'Protected Health Information (PHI) handling',
88
+ 'Business Associate Agreements',
89
+ 'Patient rights documentation'
90
+ ],
91
+ 'iso27001': [
92
+ 'Information Security Management System (ISMS)',
93
+ 'Risk assessment methodology',
94
+ 'Continuous improvement process'
95
+ ]
96
+ };
97
+
98
+ // Custom frameworks storage
99
+ const customFrameworks = new Map();
100
+
101
+ /**
102
+ * Create a control mapper instance
103
+ */
104
+ export function createControlMapper(options = {}) {
105
+ const mappings = options.loadDefaults ? [...DEFAULT_MAPPINGS] : [];
106
+
107
+ return {
108
+ map: (source, target) => mapControl({ source, target }),
109
+ findOverlaps: (frameworks) => findOverlaps(frameworks),
110
+ getMappings: () => mappings,
111
+ addMapping: (mapping) => mappings.push(mapping)
112
+ };
113
+ }
114
+
115
+ /**
116
+ * Map a control from one framework to another
117
+ */
118
+ export function mapControl({ source, target }) {
119
+ const result = {
120
+ sourceFramework: source.framework,
121
+ sourceControl: source.control,
122
+ targetFramework: target.framework,
123
+ targetControls: [],
124
+ confidence: 0,
125
+ unmapped: false
126
+ };
127
+
128
+ // Find direct mappings
129
+ const directMappings = DEFAULT_MAPPINGS.filter(m =>
130
+ m.source.framework === source.framework &&
131
+ m.source.control === source.control &&
132
+ m.target.framework === target.framework
133
+ );
134
+
135
+ if (directMappings.length > 0) {
136
+ result.targetControls = directMappings.map(m => m.target.control);
137
+ result.confidence = Math.max(...directMappings.map(m => m.confidence));
138
+ return result;
139
+ }
140
+
141
+ // Try reverse mappings
142
+ const reverseMappings = DEFAULT_MAPPINGS.filter(m =>
143
+ m.target.framework === source.framework &&
144
+ m.target.control === source.control &&
145
+ m.source.framework === target.framework
146
+ );
147
+
148
+ if (reverseMappings.length > 0) {
149
+ result.targetControls = reverseMappings.map(m => m.source.control);
150
+ result.confidence = Math.max(...reverseMappings.map(m => m.confidence)) * 0.9; // Slightly lower confidence for reverse
151
+ return result;
152
+ }
153
+
154
+ // Try theme-based mapping
155
+ const sourceThemes = FRAMEWORK_THEMES[source.framework] || {};
156
+ let sourceTheme = null;
157
+
158
+ for (const [theme, controls] of Object.entries(sourceThemes)) {
159
+ if (controls.includes(source.control)) {
160
+ sourceTheme = theme;
161
+ break;
162
+ }
163
+ }
164
+
165
+ if (sourceTheme && FRAMEWORK_THEMES[target.framework]) {
166
+ const targetControls = FRAMEWORK_THEMES[target.framework][sourceTheme];
167
+ if (targetControls && targetControls.length > 0) {
168
+ result.targetControls = targetControls;
169
+ result.confidence = 0.6; // Lower confidence for theme-based
170
+ return result;
171
+ }
172
+ }
173
+
174
+ // No mapping found
175
+ result.unmapped = true;
176
+ return result;
177
+ }
178
+
179
+ /**
180
+ * Find overlapping controls between frameworks
181
+ */
182
+ export function findOverlaps(frameworks) {
183
+ const overlaps = [];
184
+ const themes = ['encryption', 'access-control', 'network-security', 'logging'];
185
+
186
+ for (const theme of themes) {
187
+ const frameworkControls = {};
188
+ let count = 0;
189
+
190
+ for (const framework of frameworks) {
191
+ const controls = FRAMEWORK_THEMES[framework]?.[theme];
192
+ if (controls && controls.length > 0) {
193
+ frameworkControls[framework] = controls;
194
+ count++;
195
+ }
196
+ }
197
+
198
+ if (count >= 2) {
199
+ const totalControls = Object.values(frameworkControls).reduce((sum, c) => sum + c.length, 0);
200
+ const avgControlsPerFramework = totalControls / count;
201
+
202
+ overlaps.push({
203
+ theme,
204
+ frameworks: Object.keys(frameworkControls),
205
+ controls: frameworkControls,
206
+ overlapPercentage: Math.round((count / frameworks.length) * 100)
207
+ });
208
+ }
209
+ }
210
+
211
+ return overlaps;
212
+ }
213
+
214
+ /**
215
+ * Generate a cross-reference matrix between frameworks
216
+ */
217
+ export function generateCrossReference(frameworks, options = {}) {
218
+ if (options.format === 'markdown') {
219
+ return generateMarkdownMatrix(frameworks);
220
+ }
221
+
222
+ if (options.format === 'csv') {
223
+ return generateCsvMatrix(frameworks);
224
+ }
225
+
226
+ // Return object matrix by default
227
+ const matrix = {};
228
+
229
+ for (const source of frameworks) {
230
+ matrix[source] = {};
231
+ for (const target of frameworks) {
232
+ if (source !== target) {
233
+ const mappings = DEFAULT_MAPPINGS.filter(m =>
234
+ (m.source.framework === source && m.target.framework === target) ||
235
+ (m.target.framework === source && m.source.framework === target)
236
+ );
237
+ matrix[source][target] = mappings.length;
238
+ }
239
+ }
240
+ }
241
+
242
+ return matrix;
243
+ }
244
+
245
+ /**
246
+ * Generate markdown cross-reference matrix
247
+ */
248
+ function generateMarkdownMatrix(frameworks) {
249
+ const frameworkNames = {
250
+ 'pci-dss': 'PCI DSS',
251
+ 'iso27001': 'ISO 27001',
252
+ 'hipaa': 'HIPAA'
253
+ };
254
+
255
+ let md = '| Framework |';
256
+ for (const f of frameworks) {
257
+ md += ` ${frameworkNames[f] || f} |`;
258
+ }
259
+ md += '\n|---|';
260
+ for (let i = 0; i < frameworks.length; i++) {
261
+ md += '---|';
262
+ }
263
+ md += '\n';
264
+
265
+ for (const source of frameworks) {
266
+ md += `| ${frameworkNames[source] || source} |`;
267
+ for (const target of frameworks) {
268
+ if (source === target) {
269
+ md += ' - |';
270
+ } else {
271
+ const count = DEFAULT_MAPPINGS.filter(m =>
272
+ (m.source.framework === source && m.target.framework === target) ||
273
+ (m.target.framework === source && m.source.framework === target)
274
+ ).length;
275
+ md += ` ${count} |`;
276
+ }
277
+ }
278
+ md += '\n';
279
+ }
280
+
281
+ return md;
282
+ }
283
+
284
+ /**
285
+ * Generate CSV cross-reference matrix
286
+ */
287
+ function generateCsvMatrix(frameworks) {
288
+ const frameworkNames = {
289
+ 'pci-dss': 'PCI DSS',
290
+ 'iso27001': 'ISO 27001',
291
+ 'hipaa': 'HIPAA'
292
+ };
293
+
294
+ let csv = 'Framework,' + frameworks.map(f => frameworkNames[f] || f).join(',') + '\n';
295
+
296
+ for (const source of frameworks) {
297
+ csv += frameworkNames[source] || source;
298
+ for (const target of frameworks) {
299
+ if (source === target) {
300
+ csv += ',-';
301
+ } else {
302
+ const count = DEFAULT_MAPPINGS.filter(m =>
303
+ (m.source.framework === source && m.target.framework === target) ||
304
+ (m.target.framework === source && m.source.framework === target)
305
+ ).length;
306
+ csv += `,${count}`;
307
+ }
308
+ }
309
+ csv += '\n';
310
+ }
311
+
312
+ return csv;
313
+ }
314
+
315
+ /**
316
+ * Import a custom framework
317
+ */
318
+ export function importFramework(framework, options = {}) {
319
+ // Validate required fields
320
+ if (!framework.id) {
321
+ throw new Error('Framework id is required');
322
+ }
323
+
324
+ if (!framework.name) {
325
+ throw new Error('Framework name is required');
326
+ }
327
+
328
+ const result = {
329
+ imported: true,
330
+ frameworkId: framework.id,
331
+ controlCount: framework.controls?.length || 0,
332
+ mappingsCreated: 0
333
+ };
334
+
335
+ // Store the framework
336
+ customFrameworks.set(framework.id, framework);
337
+
338
+ // Auto-map to existing frameworks if requested
339
+ if (options.autoMap && framework.controls) {
340
+ for (const control of framework.controls) {
341
+ // Check for explicit mappings
342
+ if (control.mappings) {
343
+ for (const mapping of control.mappings) {
344
+ const [targetFramework, targetControl] = mapping.split(':');
345
+ DEFAULT_MAPPINGS.push({
346
+ source: { framework: framework.id, control: control.id },
347
+ target: { framework: targetFramework, control: targetControl },
348
+ theme: 'custom',
349
+ confidence: 0.9
350
+ });
351
+ result.mappingsCreated++;
352
+ }
353
+ }
354
+
355
+ // Check for keyword-based mappings
356
+ if (control.keywords) {
357
+ for (const keyword of control.keywords) {
358
+ const keywordLower = keyword.toLowerCase();
359
+ // Map to encryption controls
360
+ if (keywordLower.includes('encrypt') || keywordLower.includes('data protection')) {
361
+ for (const [fw, themes] of Object.entries(FRAMEWORK_THEMES)) {
362
+ if (themes.encryption) {
363
+ DEFAULT_MAPPINGS.push({
364
+ source: { framework: framework.id, control: control.id },
365
+ target: { framework: fw, control: themes.encryption[0] },
366
+ theme: 'encryption',
367
+ confidence: 0.7
368
+ });
369
+ result.mappingsCreated++;
370
+ }
371
+ }
372
+ }
373
+ // Map to access control
374
+ if (keywordLower.includes('access')) {
375
+ for (const [fw, themes] of Object.entries(FRAMEWORK_THEMES)) {
376
+ if (themes['access-control']) {
377
+ DEFAULT_MAPPINGS.push({
378
+ source: { framework: framework.id, control: control.id },
379
+ target: { framework: fw, control: themes['access-control'][0] },
380
+ theme: 'access-control',
381
+ confidence: 0.7
382
+ });
383
+ result.mappingsCreated++;
384
+ }
385
+ }
386
+ }
387
+ }
388
+ }
389
+ }
390
+ }
391
+
392
+ return result;
393
+ }
394
+
395
+ export default {
396
+ createControlMapper,
397
+ mapControl,
398
+ findOverlaps,
399
+ generateCrossReference,
400
+ importFramework
401
+ };