tlc-claude-code 1.4.8 → 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 (169) hide show
  1. package/package.json +1 -1
  2. package/server/index.js +229 -14
  3. package/server/lib/compliance/control-mapper.js +401 -0
  4. package/server/lib/compliance/control-mapper.test.js +117 -0
  5. package/server/lib/compliance/evidence-linker.js +296 -0
  6. package/server/lib/compliance/evidence-linker.test.js +121 -0
  7. package/server/lib/compliance/gdpr-checklist.js +416 -0
  8. package/server/lib/compliance/gdpr-checklist.test.js +131 -0
  9. package/server/lib/compliance/hipaa-checklist.js +277 -0
  10. package/server/lib/compliance/hipaa-checklist.test.js +101 -0
  11. package/server/lib/compliance/iso27001-checklist.js +287 -0
  12. package/server/lib/compliance/iso27001-checklist.test.js +99 -0
  13. package/server/lib/compliance/multi-framework-reporter.js +284 -0
  14. package/server/lib/compliance/multi-framework-reporter.test.js +127 -0
  15. package/server/lib/compliance/pci-dss-checklist.js +214 -0
  16. package/server/lib/compliance/pci-dss-checklist.test.js +95 -0
  17. package/server/lib/compliance/trust-centre.js +187 -0
  18. package/server/lib/compliance/trust-centre.test.js +93 -0
  19. package/server/lib/dashboard/api-server.js +155 -0
  20. package/server/lib/dashboard/api-server.test.js +155 -0
  21. package/server/lib/dashboard/health-api.js +199 -0
  22. package/server/lib/dashboard/health-api.test.js +122 -0
  23. package/server/lib/dashboard/notes-api.js +234 -0
  24. package/server/lib/dashboard/notes-api.test.js +134 -0
  25. package/server/lib/dashboard/router-api.js +176 -0
  26. package/server/lib/dashboard/router-api.test.js +132 -0
  27. package/server/lib/dashboard/tasks-api.js +289 -0
  28. package/server/lib/dashboard/tasks-api.test.js +161 -0
  29. package/server/lib/dashboard/tlc-introspection.js +197 -0
  30. package/server/lib/dashboard/tlc-introspection.test.js +138 -0
  31. package/server/lib/dashboard/version-api.js +222 -0
  32. package/server/lib/dashboard/version-api.test.js +112 -0
  33. package/server/lib/dashboard/websocket-server.js +104 -0
  34. package/server/lib/dashboard/websocket-server.test.js +118 -0
  35. package/server/lib/deploy/branch-classifier.js +163 -0
  36. package/server/lib/deploy/branch-classifier.test.js +164 -0
  37. package/server/lib/deploy/deployment-approval.js +299 -0
  38. package/server/lib/deploy/deployment-approval.test.js +296 -0
  39. package/server/lib/deploy/deployment-audit.js +374 -0
  40. package/server/lib/deploy/deployment-audit.test.js +307 -0
  41. package/server/lib/deploy/deployment-executor.js +335 -0
  42. package/server/lib/deploy/deployment-executor.test.js +329 -0
  43. package/server/lib/deploy/deployment-rules.js +163 -0
  44. package/server/lib/deploy/deployment-rules.test.js +188 -0
  45. package/server/lib/deploy/rollback-manager.js +379 -0
  46. package/server/lib/deploy/rollback-manager.test.js +321 -0
  47. package/server/lib/deploy/security-gates.js +236 -0
  48. package/server/lib/deploy/security-gates.test.js +222 -0
  49. package/server/lib/k8s/gitops-config.js +188 -0
  50. package/server/lib/k8s/gitops-config.test.js +59 -0
  51. package/server/lib/k8s/helm-generator.js +196 -0
  52. package/server/lib/k8s/helm-generator.test.js +59 -0
  53. package/server/lib/k8s/kustomize-generator.js +176 -0
  54. package/server/lib/k8s/kustomize-generator.test.js +58 -0
  55. package/server/lib/k8s/network-policy.js +114 -0
  56. package/server/lib/k8s/network-policy.test.js +53 -0
  57. package/server/lib/k8s/pod-security.js +114 -0
  58. package/server/lib/k8s/pod-security.test.js +55 -0
  59. package/server/lib/k8s/rbac-generator.js +132 -0
  60. package/server/lib/k8s/rbac-generator.test.js +57 -0
  61. package/server/lib/k8s/resource-manager.js +172 -0
  62. package/server/lib/k8s/resource-manager.test.js +60 -0
  63. package/server/lib/k8s/secrets-encryption.js +168 -0
  64. package/server/lib/k8s/secrets-encryption.test.js +49 -0
  65. package/server/lib/monitoring/alert-manager.js +238 -0
  66. package/server/lib/monitoring/alert-manager.test.js +106 -0
  67. package/server/lib/monitoring/health-check.js +226 -0
  68. package/server/lib/monitoring/health-check.test.js +176 -0
  69. package/server/lib/monitoring/incident-manager.js +230 -0
  70. package/server/lib/monitoring/incident-manager.test.js +98 -0
  71. package/server/lib/monitoring/log-aggregator.js +147 -0
  72. package/server/lib/monitoring/log-aggregator.test.js +89 -0
  73. package/server/lib/monitoring/metrics-collector.js +337 -0
  74. package/server/lib/monitoring/metrics-collector.test.js +172 -0
  75. package/server/lib/monitoring/status-page.js +214 -0
  76. package/server/lib/monitoring/status-page.test.js +105 -0
  77. package/server/lib/monitoring/uptime-monitor.js +194 -0
  78. package/server/lib/monitoring/uptime-monitor.test.js +109 -0
  79. package/server/lib/network/fail2ban-config.js +294 -0
  80. package/server/lib/network/fail2ban-config.test.js +275 -0
  81. package/server/lib/network/firewall-manager.js +252 -0
  82. package/server/lib/network/firewall-manager.test.js +254 -0
  83. package/server/lib/network/geoip-filter.js +282 -0
  84. package/server/lib/network/geoip-filter.test.js +264 -0
  85. package/server/lib/network/rate-limiter.js +229 -0
  86. package/server/lib/network/rate-limiter.test.js +293 -0
  87. package/server/lib/network/request-validator.js +351 -0
  88. package/server/lib/network/request-validator.test.js +345 -0
  89. package/server/lib/network/security-headers.js +251 -0
  90. package/server/lib/network/security-headers.test.js +283 -0
  91. package/server/lib/network/tls-config.js +210 -0
  92. package/server/lib/network/tls-config.test.js +248 -0
  93. package/server/lib/security/auth-security.js +369 -0
  94. package/server/lib/security/auth-security.test.js +448 -0
  95. package/server/lib/security/cis-benchmark.js +152 -0
  96. package/server/lib/security/cis-benchmark.test.js +137 -0
  97. package/server/lib/security/compose-templates.js +312 -0
  98. package/server/lib/security/compose-templates.test.js +229 -0
  99. package/server/lib/security/container-runtime.js +456 -0
  100. package/server/lib/security/container-runtime.test.js +503 -0
  101. package/server/lib/security/cors-validator.js +278 -0
  102. package/server/lib/security/cors-validator.test.js +310 -0
  103. package/server/lib/security/crypto-utils.js +253 -0
  104. package/server/lib/security/crypto-utils.test.js +409 -0
  105. package/server/lib/security/dockerfile-linter.js +459 -0
  106. package/server/lib/security/dockerfile-linter.test.js +483 -0
  107. package/server/lib/security/dockerfile-templates.js +278 -0
  108. package/server/lib/security/dockerfile-templates.test.js +164 -0
  109. package/server/lib/security/error-sanitizer.js +426 -0
  110. package/server/lib/security/error-sanitizer.test.js +331 -0
  111. package/server/lib/security/headers-generator.js +368 -0
  112. package/server/lib/security/headers-generator.test.js +398 -0
  113. package/server/lib/security/image-scanner.js +83 -0
  114. package/server/lib/security/image-scanner.test.js +106 -0
  115. package/server/lib/security/input-validator.js +352 -0
  116. package/server/lib/security/input-validator.test.js +330 -0
  117. package/server/lib/security/network-policy.js +174 -0
  118. package/server/lib/security/network-policy.test.js +164 -0
  119. package/server/lib/security/output-encoder.js +237 -0
  120. package/server/lib/security/output-encoder.test.js +276 -0
  121. package/server/lib/security/path-validator.js +359 -0
  122. package/server/lib/security/path-validator.test.js +293 -0
  123. package/server/lib/security/query-builder.js +421 -0
  124. package/server/lib/security/query-builder.test.js +318 -0
  125. package/server/lib/security/secret-detector.js +290 -0
  126. package/server/lib/security/secret-detector.test.js +354 -0
  127. package/server/lib/security/secrets-validator.js +137 -0
  128. package/server/lib/security/secrets-validator.test.js +120 -0
  129. package/server/lib/security-testing/dast-runner.js +154 -0
  130. package/server/lib/security-testing/dast-runner.test.js +62 -0
  131. package/server/lib/security-testing/dependency-scanner.js +172 -0
  132. package/server/lib/security-testing/dependency-scanner.test.js +64 -0
  133. package/server/lib/security-testing/pentest-runner.js +230 -0
  134. package/server/lib/security-testing/pentest-runner.test.js +60 -0
  135. package/server/lib/security-testing/sast-runner.js +136 -0
  136. package/server/lib/security-testing/sast-runner.test.js +62 -0
  137. package/server/lib/security-testing/secret-scanner.js +153 -0
  138. package/server/lib/security-testing/secret-scanner.test.js +66 -0
  139. package/server/lib/security-testing/security-gate.js +216 -0
  140. package/server/lib/security-testing/security-gate.test.js +115 -0
  141. package/server/lib/security-testing/security-reporter.js +303 -0
  142. package/server/lib/security-testing/security-reporter.test.js +114 -0
  143. package/server/lib/standards/audit-checker.js +546 -0
  144. package/server/lib/standards/audit-checker.test.js +415 -0
  145. package/server/lib/standards/cleanup-executor.js +452 -0
  146. package/server/lib/standards/cleanup-executor.test.js +293 -0
  147. package/server/lib/standards/refactor-stepper.js +425 -0
  148. package/server/lib/standards/refactor-stepper.test.js +298 -0
  149. package/server/lib/standards/standards-injector.js +167 -0
  150. package/server/lib/standards/standards-injector.test.js +232 -0
  151. package/server/lib/user-management.test.js +284 -0
  152. package/server/lib/vps/backup-manager.js +157 -0
  153. package/server/lib/vps/backup-manager.test.js +59 -0
  154. package/server/lib/vps/caddy-config.js +159 -0
  155. package/server/lib/vps/caddy-config.test.js +48 -0
  156. package/server/lib/vps/compose-orchestrator.js +219 -0
  157. package/server/lib/vps/compose-orchestrator.test.js +50 -0
  158. package/server/lib/vps/database-config.js +208 -0
  159. package/server/lib/vps/database-config.test.js +47 -0
  160. package/server/lib/vps/deploy-script.js +211 -0
  161. package/server/lib/vps/deploy-script.test.js +53 -0
  162. package/server/lib/vps/secrets-manager.js +148 -0
  163. package/server/lib/vps/secrets-manager.test.js +58 -0
  164. package/server/lib/vps/server-hardening.js +174 -0
  165. package/server/lib/vps/server-hardening.test.js +70 -0
  166. package/server/package-lock.json +19 -0
  167. package/server/package.json +1 -0
  168. package/server/templates/CLAUDE.md +37 -0
  169. package/server/templates/CODING-STANDARDS.md +408 -0
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Container Runtime Security Validator Module
3
+ *
4
+ * Validates docker-compose and container runtime configurations.
5
+ * Based on CIS Docker Benchmark and OWASP Docker Security guidelines.
6
+ */
7
+
8
+ import yaml from 'js-yaml';
9
+
10
+ /**
11
+ * Severity levels for findings
12
+ */
13
+ export const SEVERITY = {
14
+ CRITICAL: 'critical',
15
+ HIGH: 'high',
16
+ MEDIUM: 'medium',
17
+ LOW: 'low',
18
+ INFO: 'info',
19
+ };
20
+
21
+ /**
22
+ * Dangerous Linux capabilities
23
+ */
24
+ const DANGEROUS_CAPABILITIES = [
25
+ 'SYS_ADMIN',
26
+ 'NET_ADMIN',
27
+ 'SYS_PTRACE',
28
+ 'SYS_MODULE',
29
+ 'DAC_READ_SEARCH',
30
+ 'SYS_RAWIO',
31
+ 'SYS_BOOT',
32
+ 'SYS_TIME',
33
+ 'MKNOD',
34
+ ];
35
+
36
+ /**
37
+ * Safe capabilities that can be added
38
+ */
39
+ const SAFE_CAPABILITIES = [
40
+ 'NET_BIND_SERVICE',
41
+ 'CHOWN',
42
+ 'SETUID',
43
+ 'SETGID',
44
+ 'FOWNER',
45
+ 'DAC_OVERRIDE',
46
+ ];
47
+
48
+ /**
49
+ * Database image patterns
50
+ */
51
+ const DATABASE_IMAGES = [
52
+ /postgres/i,
53
+ /mysql/i,
54
+ /mariadb/i,
55
+ /mongo/i,
56
+ /redis/i,
57
+ /elasticsearch/i,
58
+ /memcached/i,
59
+ ];
60
+
61
+ /**
62
+ * Patterns for secrets in environment values
63
+ */
64
+ const SECRET_PATTERNS = [
65
+ /password\s*[=:]\s*[^$\s{][^\s]*/i,
66
+ /secret\s*[=:]\s*[^$\s{][^\s]*/i,
67
+ /api[_-]?key\s*[=:]\s*[^$\s{][^\s]*/i,
68
+ /token\s*[=:]\s*[^$\s{][^\s]*/i,
69
+ ];
70
+
71
+ /**
72
+ * Check if a service is a database
73
+ * @param {string} name - Service name
74
+ * @param {Object} service - Service config
75
+ * @returns {boolean} True if database
76
+ */
77
+ function isDatabase(name, service) {
78
+ if (DATABASE_IMAGES.some(p => p.test(service.image || ''))) return true;
79
+ if (/db|database|postgres|mysql|mongo|redis/i.test(name)) return true;
80
+ return false;
81
+ }
82
+
83
+ /**
84
+ * Parse docker-compose YAML content
85
+ * @param {string} content - YAML content
86
+ * @returns {Object} Parsed compose config
87
+ */
88
+ export function parseCompose(content) {
89
+ try {
90
+ const parsed = yaml.load(content);
91
+ return {
92
+ version: parsed.version || null,
93
+ services: parsed.services || {},
94
+ networks: parsed.networks || {},
95
+ volumes: parsed.volumes || {},
96
+ secrets: parsed.secrets || {},
97
+ };
98
+ } catch (e) {
99
+ throw new Error(`Failed to parse docker-compose: ${e.message}`);
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Validate a single service configuration
105
+ * @param {string} name - Service name
106
+ * @param {Object} service - Service configuration
107
+ * @param {Object} options - Validation options
108
+ * @returns {Object} Validation result
109
+ */
110
+ export function validateService(name, service, options = {}) {
111
+ const findings = [];
112
+ const isDb = isDatabase(name, service);
113
+
114
+ // Check privileged mode
115
+ if (service.privileged === true) {
116
+ findings.push({
117
+ rule: 'no-privileged',
118
+ severity: SEVERITY.CRITICAL,
119
+ service: name,
120
+ message: `Service '${name}' uses privileged mode. This grants full root access.`,
121
+ fix: 'Remove privileged: true. Use specific capabilities instead.',
122
+ });
123
+ }
124
+
125
+ // Check capabilities
126
+ const capDrop = service.cap_drop || [];
127
+ const capAdd = service.cap_add || [];
128
+
129
+ if (!capDrop.includes('ALL')) {
130
+ findings.push({
131
+ rule: 'require-cap-drop-all',
132
+ severity: SEVERITY.HIGH,
133
+ service: name,
134
+ message: `Service '${name}' should drop all capabilities and add only required ones.`,
135
+ fix: "Add 'cap_drop: [ALL]' to the service.",
136
+ });
137
+ }
138
+
139
+ // Check for dangerous capabilities
140
+ const dangerousCaps = capAdd.filter(cap => DANGEROUS_CAPABILITIES.includes(cap));
141
+ if (dangerousCaps.length > 0) {
142
+ findings.push({
143
+ rule: 'dangerous-capabilities',
144
+ severity: SEVERITY.HIGH,
145
+ service: name,
146
+ message: `Service '${name}' adds dangerous capabilities: ${dangerousCaps.join(', ')}`,
147
+ fix: 'Remove dangerous capabilities or document why they are required.',
148
+ });
149
+ }
150
+
151
+ // Check network mode
152
+ if (service.network_mode === 'host') {
153
+ findings.push({
154
+ rule: 'no-host-network',
155
+ severity: SEVERITY.HIGH,
156
+ service: name,
157
+ message: `Service '${name}' uses host network mode. This bypasses network isolation.`,
158
+ fix: 'Use custom bridge networks instead of host network.',
159
+ });
160
+ }
161
+
162
+ // Check read-only filesystem
163
+ if (!isDb && service.read_only !== true) {
164
+ findings.push({
165
+ rule: 'recommend-read-only',
166
+ severity: SEVERITY.MEDIUM,
167
+ service: name,
168
+ message: `Service '${name}' should use read-only root filesystem.`,
169
+ fix: "Add 'read_only: true' and mount writable volumes for needed paths.",
170
+ });
171
+ }
172
+
173
+ // Check user
174
+ if (!service.user) {
175
+ findings.push({
176
+ rule: 'recommend-user',
177
+ severity: SEVERITY.MEDIUM,
178
+ service: name,
179
+ message: `Service '${name}' should specify a non-root user.`,
180
+ fix: "Add 'user: \"1000:1000\"' or similar non-root user.",
181
+ });
182
+ } else if (service.user === 'root' || service.user === '0' || service.user === '0:0') {
183
+ findings.push({
184
+ rule: 'no-root-user',
185
+ severity: SEVERITY.HIGH,
186
+ service: name,
187
+ message: `Service '${name}' runs as root user.`,
188
+ fix: 'Change to a non-root user.',
189
+ });
190
+ }
191
+
192
+ // Check security_opt
193
+ const securityOpt = service.security_opt || [];
194
+ const hasNoNewPrivileges = securityOpt.some(opt =>
195
+ opt.includes('no-new-privileges')
196
+ );
197
+ const hasSeccomp = securityOpt.some(opt =>
198
+ opt.includes('seccomp')
199
+ );
200
+
201
+ if (!hasNoNewPrivileges) {
202
+ findings.push({
203
+ rule: 'recommend-no-new-privileges',
204
+ severity: SEVERITY.MEDIUM,
205
+ service: name,
206
+ message: `Service '${name}' should prevent privilege escalation.`,
207
+ fix: "Add 'security_opt: [no-new-privileges:true]'.",
208
+ });
209
+ }
210
+
211
+ if (!hasSeccomp) {
212
+ findings.push({
213
+ rule: 'recommend-seccomp',
214
+ severity: SEVERITY.LOW,
215
+ service: name,
216
+ message: `Service '${name}' should use seccomp profile.`,
217
+ fix: "Add 'security_opt: [seccomp:default]' or custom profile.",
218
+ });
219
+ }
220
+
221
+ // Check resource limits
222
+ const hasDeployLimits = service.deploy?.resources?.limits;
223
+ const hasMemLimit = service.mem_limit || service.memory;
224
+ if (!hasDeployLimits && !hasMemLimit) {
225
+ findings.push({
226
+ rule: 'recommend-resource-limits',
227
+ severity: SEVERITY.MEDIUM,
228
+ service: name,
229
+ message: `Service '${name}' should have resource limits.`,
230
+ fix: 'Add deploy.resources.limits or mem_limit to prevent resource exhaustion.',
231
+ });
232
+ }
233
+
234
+ return { findings };
235
+ }
236
+
237
+ /**
238
+ * Validate a full docker-compose configuration
239
+ * @param {string} content - YAML content
240
+ * @param {Object} options - Validation options
241
+ * @returns {Object} Validation result
242
+ */
243
+ export function validateCompose(content, options = {}) {
244
+ const parsed = parseCompose(content);
245
+ const findings = [];
246
+ const recommendations = [];
247
+
248
+ // Validate each service
249
+ for (const [name, service] of Object.entries(parsed.services)) {
250
+ const serviceResult = validateService(name, service, options);
251
+ findings.push(...serviceResult.findings);
252
+ }
253
+
254
+ // Check for custom networks
255
+ const hasCustomNetworks = Object.keys(parsed.networks).length > 0;
256
+ const servicesUseNetworks = Object.values(parsed.services).some(s => s.networks);
257
+
258
+ if (!hasCustomNetworks && !servicesUseNetworks) {
259
+ findings.push({
260
+ rule: 'use-custom-networks',
261
+ severity: SEVERITY.MEDIUM,
262
+ message: 'No custom networks defined. Services will use default bridge network.',
263
+ fix: 'Define custom networks for network segmentation.',
264
+ });
265
+ }
266
+
267
+ // Check database network isolation
268
+ for (const [name, service] of Object.entries(parsed.services)) {
269
+ if (isDatabase(name, service)) {
270
+ const serviceNetworks = service.networks || [];
271
+ let hasInternalNetwork = false;
272
+
273
+ for (const netName of serviceNetworks) {
274
+ const netConfig = parsed.networks[netName];
275
+ if (netConfig?.internal === true) {
276
+ hasInternalNetwork = true;
277
+ break;
278
+ }
279
+ }
280
+
281
+ if (serviceNetworks.length > 0 && !hasInternalNetwork) {
282
+ findings.push({
283
+ rule: 'database-internal-network',
284
+ severity: SEVERITY.MEDIUM,
285
+ service: name,
286
+ message: `Database '${name}' should use internal network (not externally accessible).`,
287
+ fix: 'Add "internal: true" to database network configuration.',
288
+ });
289
+ }
290
+ }
291
+ }
292
+
293
+ // Check for secrets in environment
294
+ for (const [name, service] of Object.entries(parsed.services)) {
295
+ const env = service.environment || {};
296
+ const envList = Array.isArray(env) ? env : Object.entries(env).map(([k, v]) => `${k}=${v}`);
297
+
298
+ for (const envVar of envList) {
299
+ // Check for hardcoded secrets
300
+ if (SECRET_PATTERNS.some(p => p.test(envVar))) {
301
+ findings.push({
302
+ rule: 'no-secrets-in-env',
303
+ severity: SEVERITY.CRITICAL,
304
+ service: name,
305
+ message: `Service '${name}' has possible hardcoded secret in environment.`,
306
+ fix: 'Use Docker secrets or external secret management.',
307
+ });
308
+ }
309
+ }
310
+
311
+ // Recommend Docker secrets for password-like vars
312
+ const hasPasswordEnv = envList.some(e =>
313
+ /password|secret|key|token/i.test(e)
314
+ );
315
+ const hasSecretsDefined = Object.keys(parsed.secrets || {}).length > 0;
316
+ if (hasPasswordEnv && !hasSecretsDefined) {
317
+ const existingRec = recommendations.find(r => r.rule === 'recommend-docker-secrets');
318
+ if (!existingRec) {
319
+ findings.push({
320
+ rule: 'recommend-docker-secrets',
321
+ severity: SEVERITY.LOW,
322
+ message: 'Sensitive environment variables detected. Consider using Docker secrets.',
323
+ fix: 'Define secrets in docker-compose and mount them in services.',
324
+ });
325
+ recommendations.push({ rule: 'recommend-docker-secrets' });
326
+ }
327
+ }
328
+ }
329
+
330
+ // Calculate score
331
+ const score = calculateScore(findings);
332
+
333
+ // Generate recommendations
334
+ const allRecommendations = generateRecommendations(findings);
335
+
336
+ return {
337
+ findings,
338
+ parsed,
339
+ score,
340
+ recommendations: allRecommendations,
341
+ summary: {
342
+ total: findings.length,
343
+ critical: findings.filter(f => f.severity === SEVERITY.CRITICAL).length,
344
+ high: findings.filter(f => f.severity === SEVERITY.HIGH).length,
345
+ medium: findings.filter(f => f.severity === SEVERITY.MEDIUM).length,
346
+ low: findings.filter(f => f.severity === SEVERITY.LOW).length,
347
+ },
348
+ };
349
+ }
350
+
351
+ /**
352
+ * Calculate security score (0-100)
353
+ * @param {Array} findings - Validation findings
354
+ * @returns {number} Security score
355
+ */
356
+ function calculateScore(findings) {
357
+ let score = 100;
358
+
359
+ for (const finding of findings) {
360
+ switch (finding.severity) {
361
+ case SEVERITY.CRITICAL:
362
+ score -= 25;
363
+ break;
364
+ case SEVERITY.HIGH:
365
+ score -= 15;
366
+ break;
367
+ case SEVERITY.MEDIUM:
368
+ score -= 10;
369
+ break;
370
+ case SEVERITY.LOW:
371
+ score -= 5;
372
+ break;
373
+ }
374
+ }
375
+
376
+ return Math.max(0, score);
377
+ }
378
+
379
+ /**
380
+ * Generate recommendations from findings
381
+ * @param {Array} findings - Validation findings
382
+ * @returns {Array} Recommendations
383
+ */
384
+ function generateRecommendations(findings) {
385
+ const recommendations = [];
386
+ const seenRules = new Set();
387
+
388
+ for (const finding of findings) {
389
+ if (!seenRules.has(finding.rule) && finding.fix) {
390
+ recommendations.push({
391
+ rule: finding.rule,
392
+ severity: finding.severity,
393
+ message: finding.message,
394
+ fix: finding.fix,
395
+ });
396
+ seenRules.add(finding.rule);
397
+ }
398
+ }
399
+
400
+ return recommendations.sort((a, b) => {
401
+ const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
402
+ return severityOrder[a.severity] - severityOrder[b.severity];
403
+ });
404
+ }
405
+
406
+ /**
407
+ * Create a configurable runtime validator
408
+ * @param {Object} config - Validator configuration
409
+ * @returns {Object} Validator instance
410
+ */
411
+ export function createRuntimeValidator(config = {}) {
412
+ const { rules = {} } = config;
413
+
414
+ return {
415
+ /**
416
+ * Validate docker-compose content
417
+ * @param {string} content - YAML content
418
+ * @returns {Object} Validation result
419
+ */
420
+ validate(content) {
421
+ const result = validateCompose(content);
422
+
423
+ // Filter findings based on rule configuration
424
+ result.findings = result.findings.filter(finding => {
425
+ const ruleConfig = rules[finding.rule];
426
+ if (ruleConfig === 'off' || ruleConfig === false) {
427
+ return false;
428
+ }
429
+ return true;
430
+ });
431
+
432
+ // Recalculate score
433
+ result.score = calculateScore(result.findings);
434
+ result.summary = {
435
+ total: result.findings.length,
436
+ critical: result.findings.filter(f => f.severity === SEVERITY.CRITICAL).length,
437
+ high: result.findings.filter(f => f.severity === SEVERITY.HIGH).length,
438
+ medium: result.findings.filter(f => f.severity === SEVERITY.MEDIUM).length,
439
+ low: result.findings.filter(f => f.severity === SEVERITY.LOW).length,
440
+ };
441
+
442
+ return result;
443
+ },
444
+
445
+ /**
446
+ * Validate from file path
447
+ * @param {string} filePath - Path to docker-compose.yml
448
+ * @param {Function} readFile - File reader function
449
+ * @returns {Promise<Object>} Validation result
450
+ */
451
+ async validateFile(filePath, readFile) {
452
+ const content = await readFile(filePath, 'utf8');
453
+ return this.validate(content);
454
+ },
455
+ };
456
+ }