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,503 @@
1
+ /**
2
+ * Container Runtime Security Validator Tests
3
+ *
4
+ * Tests for docker-compose and container runtime security validation.
5
+ */
6
+
7
+ import { describe, it, expect } from 'vitest';
8
+ import {
9
+ validateCompose,
10
+ parseCompose,
11
+ validateService,
12
+ createRuntimeValidator,
13
+ SEVERITY,
14
+ } from './container-runtime.js';
15
+
16
+ describe('container-runtime', () => {
17
+ describe('parseCompose', () => {
18
+ it('parses basic docker-compose file', () => {
19
+ const compose = `
20
+ version: '3.8'
21
+ services:
22
+ app:
23
+ image: node:20-alpine
24
+ ports:
25
+ - "3000:3000"
26
+ db:
27
+ image: postgres:16
28
+ `.trim();
29
+
30
+ const parsed = parseCompose(compose);
31
+
32
+ expect(parsed.version).toBe('3.8');
33
+ expect(Object.keys(parsed.services)).toHaveLength(2);
34
+ expect(parsed.services.app.image).toBe('node:20-alpine');
35
+ });
36
+
37
+ it('parses service security options', () => {
38
+ const compose = `
39
+ services:
40
+ app:
41
+ image: node:20-alpine
42
+ cap_drop:
43
+ - ALL
44
+ cap_add:
45
+ - NET_BIND_SERVICE
46
+ security_opt:
47
+ - no-new-privileges:true
48
+ read_only: true
49
+ user: "1000:1000"
50
+ `.trim();
51
+
52
+ const parsed = parseCompose(compose);
53
+ const app = parsed.services.app;
54
+
55
+ expect(app.cap_drop).toContain('ALL');
56
+ expect(app.cap_add).toContain('NET_BIND_SERVICE');
57
+ expect(app.read_only).toBe(true);
58
+ expect(app.user).toBe('1000:1000');
59
+ });
60
+
61
+ it('parses network configuration', () => {
62
+ const compose = `
63
+ services:
64
+ app:
65
+ image: node:20-alpine
66
+ networks:
67
+ - frontend
68
+ - backend
69
+ networks:
70
+ frontend:
71
+ backend:
72
+ internal: true
73
+ `.trim();
74
+
75
+ const parsed = parseCompose(compose);
76
+
77
+ expect(parsed.networks).toBeDefined();
78
+ expect(parsed.networks.backend?.internal).toBe(true);
79
+ });
80
+ });
81
+
82
+ describe('validateService - privileged mode', () => {
83
+ it('detects privileged: true', () => {
84
+ const service = {
85
+ image: 'node:20',
86
+ privileged: true,
87
+ };
88
+
89
+ const result = validateService('app', service);
90
+
91
+ expect(result.findings.some(f => f.rule === 'no-privileged')).toBe(true);
92
+ });
93
+
94
+ it('passes without privileged flag', () => {
95
+ const service = {
96
+ image: 'node:20',
97
+ };
98
+
99
+ const result = validateService('app', service);
100
+
101
+ expect(result.findings.some(f => f.rule === 'no-privileged')).toBe(false);
102
+ });
103
+ });
104
+
105
+ describe('validateService - capabilities', () => {
106
+ it('detects missing cap_drop: ALL', () => {
107
+ const service = {
108
+ image: 'node:20',
109
+ };
110
+
111
+ const result = validateService('app', service);
112
+
113
+ expect(result.findings.some(f => f.rule === 'require-cap-drop-all')).toBe(true);
114
+ });
115
+
116
+ it('passes with cap_drop: ALL', () => {
117
+ const service = {
118
+ image: 'node:20',
119
+ cap_drop: ['ALL'],
120
+ };
121
+
122
+ const result = validateService('app', service);
123
+
124
+ expect(result.findings.some(f => f.rule === 'require-cap-drop-all')).toBe(false);
125
+ });
126
+
127
+ it('warns on dangerous capabilities', () => {
128
+ const service = {
129
+ image: 'node:20',
130
+ cap_drop: ['ALL'],
131
+ cap_add: ['SYS_ADMIN', 'NET_ADMIN'],
132
+ };
133
+
134
+ const result = validateService('app', service);
135
+
136
+ expect(result.findings.some(f => f.rule === 'dangerous-capabilities')).toBe(true);
137
+ });
138
+
139
+ it('allows safe capabilities', () => {
140
+ const service = {
141
+ image: 'node:20',
142
+ cap_drop: ['ALL'],
143
+ cap_add: ['NET_BIND_SERVICE', 'CHOWN'],
144
+ };
145
+
146
+ const result = validateService('app', service);
147
+
148
+ expect(result.findings.some(f => f.rule === 'dangerous-capabilities')).toBe(false);
149
+ });
150
+ });
151
+
152
+ describe('validateService - network mode', () => {
153
+ it('detects host network mode', () => {
154
+ const service = {
155
+ image: 'node:20',
156
+ network_mode: 'host',
157
+ };
158
+
159
+ const result = validateService('app', service);
160
+
161
+ expect(result.findings.some(f => f.rule === 'no-host-network')).toBe(true);
162
+ });
163
+
164
+ it('passes with bridge network', () => {
165
+ const service = {
166
+ image: 'node:20',
167
+ networks: ['app-network'],
168
+ };
169
+
170
+ const result = validateService('app', service);
171
+
172
+ expect(result.findings.some(f => f.rule === 'no-host-network')).toBe(false);
173
+ });
174
+ });
175
+
176
+ describe('validateService - read-only filesystem', () => {
177
+ it('recommends read_only for stateless services', () => {
178
+ const service = {
179
+ image: 'node:20',
180
+ cap_drop: ['ALL'],
181
+ };
182
+
183
+ const result = validateService('app', service);
184
+
185
+ expect(result.findings.some(f => f.rule === 'recommend-read-only')).toBe(true);
186
+ });
187
+
188
+ it('passes with read_only: true', () => {
189
+ const service = {
190
+ image: 'node:20',
191
+ cap_drop: ['ALL'],
192
+ read_only: true,
193
+ };
194
+
195
+ const result = validateService('app', service);
196
+
197
+ expect(result.findings.some(f => f.rule === 'recommend-read-only')).toBe(false);
198
+ });
199
+
200
+ it('skips read_only check for databases', () => {
201
+ const service = {
202
+ image: 'postgres:16',
203
+ cap_drop: ['ALL'],
204
+ };
205
+
206
+ const result = validateService('db', service);
207
+
208
+ // Should not recommend read_only for database
209
+ const finding = result.findings.find(f => f.rule === 'recommend-read-only');
210
+ expect(finding).toBeUndefined();
211
+ });
212
+ });
213
+
214
+ describe('validateService - user', () => {
215
+ it('detects missing user directive', () => {
216
+ const service = {
217
+ image: 'node:20',
218
+ cap_drop: ['ALL'],
219
+ };
220
+
221
+ const result = validateService('app', service);
222
+
223
+ expect(result.findings.some(f => f.rule === 'recommend-user')).toBe(true);
224
+ });
225
+
226
+ it('passes with user specified', () => {
227
+ const service = {
228
+ image: 'node:20',
229
+ cap_drop: ['ALL'],
230
+ user: '1000:1000',
231
+ };
232
+
233
+ const result = validateService('app', service);
234
+
235
+ expect(result.findings.some(f => f.rule === 'recommend-user')).toBe(false);
236
+ });
237
+
238
+ it('detects root user', () => {
239
+ const service = {
240
+ image: 'node:20',
241
+ user: 'root',
242
+ };
243
+
244
+ const result = validateService('app', service);
245
+
246
+ expect(result.findings.some(f => f.rule === 'no-root-user')).toBe(true);
247
+ });
248
+ });
249
+
250
+ describe('validateService - security_opt', () => {
251
+ it('recommends no-new-privileges', () => {
252
+ const service = {
253
+ image: 'node:20',
254
+ cap_drop: ['ALL'],
255
+ };
256
+
257
+ const result = validateService('app', service);
258
+
259
+ expect(result.findings.some(f => f.rule === 'recommend-no-new-privileges')).toBe(true);
260
+ });
261
+
262
+ it('passes with no-new-privileges set', () => {
263
+ const service = {
264
+ image: 'node:20',
265
+ cap_drop: ['ALL'],
266
+ security_opt: ['no-new-privileges:true'],
267
+ };
268
+
269
+ const result = validateService('app', service);
270
+
271
+ expect(result.findings.some(f => f.rule === 'recommend-no-new-privileges')).toBe(false);
272
+ });
273
+
274
+ it('recommends seccomp profile', () => {
275
+ const service = {
276
+ image: 'node:20',
277
+ cap_drop: ['ALL'],
278
+ };
279
+
280
+ const result = validateService('app', service);
281
+
282
+ expect(result.findings.some(f => f.rule === 'recommend-seccomp')).toBe(true);
283
+ });
284
+ });
285
+
286
+ describe('validateService - resource limits', () => {
287
+ it('recommends memory limits', () => {
288
+ const service = {
289
+ image: 'node:20',
290
+ cap_drop: ['ALL'],
291
+ };
292
+
293
+ const result = validateService('app', service);
294
+
295
+ expect(result.findings.some(f => f.rule === 'recommend-resource-limits')).toBe(true);
296
+ });
297
+
298
+ it('passes with deploy limits', () => {
299
+ const service = {
300
+ image: 'node:20',
301
+ cap_drop: ['ALL'],
302
+ deploy: {
303
+ resources: {
304
+ limits: {
305
+ memory: '512M',
306
+ cpus: '0.5',
307
+ },
308
+ },
309
+ },
310
+ };
311
+
312
+ const result = validateService('app', service);
313
+
314
+ expect(result.findings.some(f => f.rule === 'recommend-resource-limits')).toBe(false);
315
+ });
316
+
317
+ it('passes with mem_limit', () => {
318
+ const service = {
319
+ image: 'node:20',
320
+ cap_drop: ['ALL'],
321
+ mem_limit: '512m',
322
+ };
323
+
324
+ const result = validateService('app', service);
325
+
326
+ expect(result.findings.some(f => f.rule === 'recommend-resource-limits')).toBe(false);
327
+ });
328
+ });
329
+
330
+ describe('validateCompose - networks', () => {
331
+ it('warns on default bridge network', () => {
332
+ const compose = `
333
+ services:
334
+ app:
335
+ image: node:20
336
+ `.trim();
337
+
338
+ const result = validateCompose(compose);
339
+
340
+ expect(result.findings.some(f => f.rule === 'use-custom-networks')).toBe(true);
341
+ });
342
+
343
+ it('passes with custom networks', () => {
344
+ const compose = `
345
+ services:
346
+ app:
347
+ image: node:20
348
+ networks:
349
+ - app-network
350
+ networks:
351
+ app-network:
352
+ `.trim();
353
+
354
+ const result = validateCompose(compose);
355
+
356
+ expect(result.findings.some(f => f.rule === 'use-custom-networks')).toBe(false);
357
+ });
358
+
359
+ it('recommends internal networks for databases', () => {
360
+ const compose = `
361
+ services:
362
+ db:
363
+ image: postgres:16
364
+ networks:
365
+ - backend
366
+ networks:
367
+ backend:
368
+ `.trim();
369
+
370
+ const result = validateCompose(compose);
371
+
372
+ expect(result.findings.some(f => f.rule === 'database-internal-network')).toBe(true);
373
+ });
374
+
375
+ it('passes with internal network for database', () => {
376
+ const compose = `
377
+ services:
378
+ db:
379
+ image: postgres:16
380
+ networks:
381
+ - backend
382
+ networks:
383
+ backend:
384
+ internal: true
385
+ `.trim();
386
+
387
+ const result = validateCompose(compose);
388
+
389
+ expect(result.findings.some(f => f.rule === 'database-internal-network')).toBe(false);
390
+ });
391
+ });
392
+
393
+ describe('validateCompose - secrets', () => {
394
+ it('detects passwords in environment', () => {
395
+ const compose = `
396
+ services:
397
+ app:
398
+ image: node:20
399
+ environment:
400
+ - DB_PASSWORD=secret123
401
+ `.trim();
402
+
403
+ const result = validateCompose(compose);
404
+
405
+ expect(result.findings.some(f => f.rule === 'no-secrets-in-env')).toBe(true);
406
+ });
407
+
408
+ it('passes with variable reference', () => {
409
+ const compose = `
410
+ services:
411
+ app:
412
+ image: node:20
413
+ environment:
414
+ - DB_PASSWORD=\${DB_PASSWORD}
415
+ `.trim();
416
+
417
+ const result = validateCompose(compose);
418
+
419
+ expect(result.findings.some(f => f.rule === 'no-secrets-in-env')).toBe(false);
420
+ });
421
+
422
+ it('recommends Docker secrets for sensitive data', () => {
423
+ const compose = `
424
+ services:
425
+ app:
426
+ image: node:20
427
+ environment:
428
+ DB_PASSWORD: "\${DB_PASSWORD}"
429
+ `.trim();
430
+
431
+ const result = validateCompose(compose);
432
+
433
+ expect(result.findings.some(f => f.rule === 'recommend-docker-secrets')).toBe(true);
434
+ });
435
+ });
436
+
437
+ describe('createRuntimeValidator', () => {
438
+ it('creates validator with custom rules', () => {
439
+ const validator = createRuntimeValidator({
440
+ rules: {
441
+ 'recommend-read-only': 'off',
442
+ 'recommend-seccomp': 'off',
443
+ },
444
+ });
445
+
446
+ const compose = `
447
+ services:
448
+ app:
449
+ image: node:20
450
+ cap_drop:
451
+ - ALL
452
+ `.trim();
453
+
454
+ const result = validator.validate(compose);
455
+
456
+ expect(result.findings.some(f => f.rule === 'recommend-read-only')).toBe(false);
457
+ expect(result.findings.some(f => f.rule === 'recommend-seccomp')).toBe(false);
458
+ });
459
+
460
+ it('calculates security score', () => {
461
+ const validator = createRuntimeValidator();
462
+
463
+ const secureCompose = `
464
+ services:
465
+ app:
466
+ image: node:20-alpine
467
+ user: "1000:1000"
468
+ read_only: true
469
+ cap_drop:
470
+ - ALL
471
+ security_opt:
472
+ - no-new-privileges:true
473
+ - seccomp:default
474
+ deploy:
475
+ resources:
476
+ limits:
477
+ memory: 512M
478
+ networks:
479
+ - app-network
480
+ networks:
481
+ app-network:
482
+ `.trim();
483
+
484
+ const result = validator.validate(secureCompose);
485
+
486
+ expect(result.score).toBeGreaterThanOrEqual(80);
487
+ });
488
+
489
+ it('generates security recommendations', () => {
490
+ const compose = `
491
+ services:
492
+ app:
493
+ image: node:20
494
+ privileged: true
495
+ `.trim();
496
+
497
+ const result = validateCompose(compose);
498
+
499
+ expect(result.recommendations).toBeDefined();
500
+ expect(result.recommendations.length).toBeGreaterThan(0);
501
+ });
502
+ });
503
+ });