elasticdash-sdk 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (349) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +775 -0
  3. package/dist/browser-ui.d.ts +43 -0
  4. package/dist/browser-ui.d.ts.map +1 -0
  5. package/dist/browser-ui.js +246 -0
  6. package/dist/browser-ui.js.map +1 -0
  7. package/dist/capture/event.d.ts +33 -0
  8. package/dist/capture/event.d.ts.map +1 -0
  9. package/dist/capture/event.js +2 -0
  10. package/dist/capture/event.js.map +1 -0
  11. package/dist/capture/index.d.ts +4 -0
  12. package/dist/capture/index.d.ts.map +1 -0
  13. package/dist/capture/index.js +4 -0
  14. package/dist/capture/index.js.map +1 -0
  15. package/dist/capture/recorder.d.ts +24 -0
  16. package/dist/capture/recorder.d.ts.map +1 -0
  17. package/dist/capture/recorder.js +46 -0
  18. package/dist/capture/recorder.js.map +1 -0
  19. package/dist/capture/replay.d.ts +20 -0
  20. package/dist/capture/replay.d.ts.map +1 -0
  21. package/dist/capture/replay.js +47 -0
  22. package/dist/capture/replay.js.map +1 -0
  23. package/dist/ci/api-client.d.ts +38 -0
  24. package/dist/ci/api-client.d.ts.map +1 -0
  25. package/dist/ci/api-client.js +96 -0
  26. package/dist/ci/api-client.js.map +1 -0
  27. package/dist/ci/benchmark.d.ts +33 -0
  28. package/dist/ci/benchmark.d.ts.map +1 -0
  29. package/dist/ci/benchmark.js +213 -0
  30. package/dist/ci/benchmark.js.map +1 -0
  31. package/dist/ci/ed-runner.d.ts +48 -0
  32. package/dist/ci/ed-runner.d.ts.map +1 -0
  33. package/dist/ci/ed-runner.js +260 -0
  34. package/dist/ci/ed-runner.js.map +1 -0
  35. package/dist/ci/executor.d.ts +13 -0
  36. package/dist/ci/executor.d.ts.map +1 -0
  37. package/dist/ci/executor.js +542 -0
  38. package/dist/ci/executor.js.map +1 -0
  39. package/dist/ci/git-info.d.ts +17 -0
  40. package/dist/ci/git-info.d.ts.map +1 -0
  41. package/dist/ci/git-info.js +102 -0
  42. package/dist/ci/git-info.js.map +1 -0
  43. package/dist/ci/index.d.ts +6 -0
  44. package/dist/ci/index.d.ts.map +1 -0
  45. package/dist/ci/index.js +4 -0
  46. package/dist/ci/index.js.map +1 -0
  47. package/dist/ci/measurement.d.ts +9 -0
  48. package/dist/ci/measurement.d.ts.map +1 -0
  49. package/dist/ci/measurement.js +15 -0
  50. package/dist/ci/measurement.js.map +1 -0
  51. package/dist/ci/replay.d.ts +31 -0
  52. package/dist/ci/replay.d.ts.map +1 -0
  53. package/dist/ci/replay.js +96 -0
  54. package/dist/ci/replay.js.map +1 -0
  55. package/dist/ci/reporters/default.d.ts +8 -0
  56. package/dist/ci/reporters/default.d.ts.map +1 -0
  57. package/dist/ci/reporters/default.js +46 -0
  58. package/dist/ci/reporters/default.js.map +1 -0
  59. package/dist/ci/reporters/index.d.ts +8 -0
  60. package/dist/ci/reporters/index.d.ts.map +1 -0
  61. package/dist/ci/reporters/index.js +14 -0
  62. package/dist/ci/reporters/index.js.map +1 -0
  63. package/dist/ci/reporters/json.d.ts +8 -0
  64. package/dist/ci/reporters/json.d.ts.map +1 -0
  65. package/dist/ci/reporters/json.js +14 -0
  66. package/dist/ci/reporters/json.js.map +1 -0
  67. package/dist/ci/reporters/junit.d.ts +8 -0
  68. package/dist/ci/reporters/junit.d.ts.map +1 -0
  69. package/dist/ci/reporters/junit.js +48 -0
  70. package/dist/ci/reporters/junit.js.map +1 -0
  71. package/dist/ci/runner.d.ts +3 -0
  72. package/dist/ci/runner.d.ts.map +1 -0
  73. package/dist/ci/runner.js +187 -0
  74. package/dist/ci/runner.js.map +1 -0
  75. package/dist/ci/test-discovery.d.ts +5 -0
  76. package/dist/ci/test-discovery.d.ts.map +1 -0
  77. package/dist/ci/test-discovery.js +11 -0
  78. package/dist/ci/test-discovery.js.map +1 -0
  79. package/dist/ci/test-loader.d.ts +19 -0
  80. package/dist/ci/test-loader.d.ts.map +1 -0
  81. package/dist/ci/test-loader.js +149 -0
  82. package/dist/ci/test-loader.js.map +1 -0
  83. package/dist/ci/test-registry.d.ts +42 -0
  84. package/dist/ci/test-registry.d.ts.map +1 -0
  85. package/dist/ci/test-registry.js +18 -0
  86. package/dist/ci/test-registry.js.map +1 -0
  87. package/dist/ci/trace-schema.d.ts +30 -0
  88. package/dist/ci/trace-schema.d.ts.map +1 -0
  89. package/dist/ci/trace-schema.js +66 -0
  90. package/dist/ci/trace-schema.js.map +1 -0
  91. package/dist/ci/trace-writer.d.ts +16 -0
  92. package/dist/ci/trace-writer.d.ts.map +1 -0
  93. package/dist/ci/trace-writer.js +108 -0
  94. package/dist/ci/trace-writer.js.map +1 -0
  95. package/dist/ci/types.d.ts +108 -0
  96. package/dist/ci/types.d.ts.map +1 -0
  97. package/dist/ci/types.js +3 -0
  98. package/dist/ci/types.js.map +1 -0
  99. package/dist/ci/upload-client.d.ts +74 -0
  100. package/dist/ci/upload-client.d.ts.map +1 -0
  101. package/dist/ci/upload-client.js +195 -0
  102. package/dist/ci/upload-client.js.map +1 -0
  103. package/dist/cli.d.ts +3 -0
  104. package/dist/cli.d.ts.map +1 -0
  105. package/dist/cli.js +716 -0
  106. package/dist/cli.js.map +1 -0
  107. package/dist/core/agent-state.d.ts +47 -0
  108. package/dist/core/agent-state.d.ts.map +1 -0
  109. package/dist/core/agent-state.js +137 -0
  110. package/dist/core/agent-state.js.map +1 -0
  111. package/dist/core/judge-utils.d.ts +22 -0
  112. package/dist/core/judge-utils.d.ts.map +1 -0
  113. package/dist/core/judge-utils.js +211 -0
  114. package/dist/core/judge-utils.js.map +1 -0
  115. package/dist/core/registry.d.ts +28 -0
  116. package/dist/core/registry.d.ts.map +1 -0
  117. package/dist/core/registry.js +52 -0
  118. package/dist/core/registry.js.map +1 -0
  119. package/dist/dashboard-server.d.ts +65 -0
  120. package/dist/dashboard-server.d.ts.map +1 -0
  121. package/dist/dashboard-server.js +3940 -0
  122. package/dist/dashboard-server.js.map +1 -0
  123. package/dist/execution/tool-runner.d.ts +26 -0
  124. package/dist/execution/tool-runner.d.ts.map +1 -0
  125. package/dist/execution/tool-runner.js +316 -0
  126. package/dist/execution/tool-runner.js.map +1 -0
  127. package/dist/html/dashboard.html +2218 -0
  128. package/dist/http.d.ts +14 -0
  129. package/dist/http.d.ts.map +1 -0
  130. package/dist/http.js +13 -0
  131. package/dist/http.js.map +1 -0
  132. package/dist/index.cjs +8102 -0
  133. package/dist/index.d.ts +61 -0
  134. package/dist/index.d.ts.map +1 -0
  135. package/dist/index.js +67 -0
  136. package/dist/index.js.map +1 -0
  137. package/dist/interceptors/ai-interceptor.d.ts +26 -0
  138. package/dist/interceptors/ai-interceptor.d.ts.map +1 -0
  139. package/dist/interceptors/ai-interceptor.js +756 -0
  140. package/dist/interceptors/ai-interceptor.js.map +1 -0
  141. package/dist/interceptors/db-auto.d.ts +8 -0
  142. package/dist/interceptors/db-auto.d.ts.map +1 -0
  143. package/dist/interceptors/db-auto.js +217 -0
  144. package/dist/interceptors/db-auto.js.map +1 -0
  145. package/dist/interceptors/db.d.ts +23 -0
  146. package/dist/interceptors/db.d.ts.map +1 -0
  147. package/dist/interceptors/db.js +137 -0
  148. package/dist/interceptors/db.js.map +1 -0
  149. package/dist/interceptors/http.d.ts +28 -0
  150. package/dist/interceptors/http.d.ts.map +1 -0
  151. package/dist/interceptors/http.js +356 -0
  152. package/dist/interceptors/http.js.map +1 -0
  153. package/dist/interceptors/side-effects.d.ts +7 -0
  154. package/dist/interceptors/side-effects.d.ts.map +1 -0
  155. package/dist/interceptors/side-effects.js +72 -0
  156. package/dist/interceptors/side-effects.js.map +1 -0
  157. package/dist/interceptors/telemetry-push.d.ts +142 -0
  158. package/dist/interceptors/telemetry-push.d.ts.map +1 -0
  159. package/dist/interceptors/telemetry-push.js +463 -0
  160. package/dist/interceptors/telemetry-push.js.map +1 -0
  161. package/dist/interceptors/tool.d.ts +2 -0
  162. package/dist/interceptors/tool.d.ts.map +1 -0
  163. package/dist/interceptors/tool.js +274 -0
  164. package/dist/interceptors/tool.js.map +1 -0
  165. package/dist/interceptors/workflow-ai.d.ts +5 -0
  166. package/dist/interceptors/workflow-ai.d.ts.map +1 -0
  167. package/dist/interceptors/workflow-ai.js +382 -0
  168. package/dist/interceptors/workflow-ai.js.map +1 -0
  169. package/dist/internals/conditional-recorder.d.ts +21 -0
  170. package/dist/internals/conditional-recorder.d.ts.map +1 -0
  171. package/dist/internals/conditional-recorder.js +54 -0
  172. package/dist/internals/conditional-recorder.js.map +1 -0
  173. package/dist/internals/mock-resolver.d.ts +146 -0
  174. package/dist/internals/mock-resolver.d.ts.map +1 -0
  175. package/dist/internals/mock-resolver.js +427 -0
  176. package/dist/internals/mock-resolver.js.map +1 -0
  177. package/dist/matchers/index.d.ts +96 -0
  178. package/dist/matchers/index.d.ts.map +1 -0
  179. package/dist/matchers/index.js +668 -0
  180. package/dist/matchers/index.js.map +1 -0
  181. package/dist/observability.d.ts +82 -0
  182. package/dist/observability.d.ts.map +1 -0
  183. package/dist/observability.js +471 -0
  184. package/dist/observability.js.map +1 -0
  185. package/dist/portal-executor.d.ts +30 -0
  186. package/dist/portal-executor.d.ts.map +1 -0
  187. package/dist/portal-executor.js +324 -0
  188. package/dist/portal-executor.js.map +1 -0
  189. package/dist/portal-server.d.ts +3 -0
  190. package/dist/portal-server.d.ts.map +1 -0
  191. package/dist/portal-server.js +279 -0
  192. package/dist/portal-server.js.map +1 -0
  193. package/dist/proxy/llm-capture.d.ts +14 -0
  194. package/dist/proxy/llm-capture.d.ts.map +1 -0
  195. package/dist/proxy/llm-capture.js +264 -0
  196. package/dist/proxy/llm-capture.js.map +1 -0
  197. package/dist/reporter.d.ts +3 -0
  198. package/dist/reporter.d.ts.map +1 -0
  199. package/dist/reporter.js +72 -0
  200. package/dist/reporter.js.map +1 -0
  201. package/dist/runWorkflowSubprocess.d.ts +14 -0
  202. package/dist/runWorkflowSubprocess.d.ts.map +1 -0
  203. package/dist/runWorkflowSubprocess.js +66 -0
  204. package/dist/runWorkflowSubprocess.js.map +1 -0
  205. package/dist/runner.d.ts +16 -0
  206. package/dist/runner.d.ts.map +1 -0
  207. package/dist/runner.js +138 -0
  208. package/dist/runner.js.map +1 -0
  209. package/dist/socket-connector.d.ts +22 -0
  210. package/dist/socket-connector.d.ts.map +1 -0
  211. package/dist/socket-connector.js +104 -0
  212. package/dist/socket-connector.js.map +1 -0
  213. package/dist/telemetry-batcher.d.ts +56 -0
  214. package/dist/telemetry-batcher.d.ts.map +1 -0
  215. package/dist/telemetry-batcher.js +143 -0
  216. package/dist/telemetry-batcher.js.map +1 -0
  217. package/dist/test-setup.d.ts +12 -0
  218. package/dist/test-setup.d.ts.map +1 -0
  219. package/dist/test-setup.js +13 -0
  220. package/dist/test-setup.js.map +1 -0
  221. package/dist/tool-registry.d.ts +31 -0
  222. package/dist/tool-registry.d.ts.map +1 -0
  223. package/dist/tool-registry.js +73 -0
  224. package/dist/tool-registry.js.map +1 -0
  225. package/dist/tool-runner-worker.d.ts +2 -0
  226. package/dist/tool-runner-worker.d.ts.map +1 -0
  227. package/dist/tool-runner-worker.js +215 -0
  228. package/dist/tool-runner-worker.js.map +1 -0
  229. package/dist/trace-adapter/context.d.ts +72 -0
  230. package/dist/trace-adapter/context.d.ts.map +1 -0
  231. package/dist/trace-adapter/context.js +80 -0
  232. package/dist/trace-adapter/context.js.map +1 -0
  233. package/dist/tracing.d.ts +2 -0
  234. package/dist/tracing.d.ts.map +1 -0
  235. package/dist/tracing.js +59 -0
  236. package/dist/tracing.js.map +1 -0
  237. package/dist/trigger-executor.d.ts +12 -0
  238. package/dist/trigger-executor.d.ts.map +1 -0
  239. package/dist/trigger-executor.js +130 -0
  240. package/dist/trigger-executor.js.map +1 -0
  241. package/dist/types/portal.d.ts +76 -0
  242. package/dist/types/portal.d.ts.map +1 -0
  243. package/dist/types/portal.js +2 -0
  244. package/dist/types/portal.js.map +1 -0
  245. package/dist/utils/debug.d.ts +3 -0
  246. package/dist/utils/debug.d.ts.map +1 -0
  247. package/dist/utils/debug.js +8 -0
  248. package/dist/utils/debug.js.map +1 -0
  249. package/dist/utils/license-error.d.ts +23 -0
  250. package/dist/utils/license-error.d.ts.map +1 -0
  251. package/dist/utils/license-error.js +42 -0
  252. package/dist/utils/license-error.js.map +1 -0
  253. package/dist/utils/redact.d.ts +7 -0
  254. package/dist/utils/redact.d.ts.map +1 -0
  255. package/dist/utils/redact.js +26 -0
  256. package/dist/utils/redact.js.map +1 -0
  257. package/dist/workflow-runner-worker.d.ts +2 -0
  258. package/dist/workflow-runner-worker.d.ts.map +1 -0
  259. package/dist/workflow-runner-worker.js +329 -0
  260. package/dist/workflow-runner-worker.js.map +1 -0
  261. package/dist/workflow-runner.d.ts +14 -0
  262. package/dist/workflow-runner.d.ts.map +1 -0
  263. package/dist/workflow-runner.js +34 -0
  264. package/dist/workflow-runner.js.map +1 -0
  265. package/docs/agent-coding-instructions.md +138 -0
  266. package/docs/agent-integration-guide.md +564 -0
  267. package/docs/agents.md +140 -0
  268. package/docs/dashboard.md +394 -0
  269. package/docs/deno.md +69 -0
  270. package/docs/instrumentation.md +424 -0
  271. package/docs/langfuse-trace-structure.md +145 -0
  272. package/docs/matchers.md +173 -0
  273. package/docs/observability_contract.md +192 -0
  274. package/docs/observability_mode.md +195 -0
  275. package/docs/quickstart.md +621 -0
  276. package/docs/security-compliance.md +566 -0
  277. package/docs/test-writing-guidelines.md +444 -0
  278. package/docs/tools.md +165 -0
  279. package/docs/workflow-modes.md +253 -0
  280. package/package.json +76 -0
  281. package/src/browser-ui.ts +281 -0
  282. package/src/capture/event.ts +30 -0
  283. package/src/capture/index.ts +3 -0
  284. package/src/capture/recorder.ts +62 -0
  285. package/src/capture/replay.ts +55 -0
  286. package/src/ci/api-client.ts +136 -0
  287. package/src/ci/benchmark.ts +257 -0
  288. package/src/ci/ed-runner.ts +351 -0
  289. package/src/ci/executor.ts +671 -0
  290. package/src/ci/git-info.ts +127 -0
  291. package/src/ci/index.ts +5 -0
  292. package/src/ci/measurement.ts +25 -0
  293. package/src/ci/replay.ts +127 -0
  294. package/src/ci/reporters/default.ts +50 -0
  295. package/src/ci/reporters/index.ts +21 -0
  296. package/src/ci/reporters/json.ts +18 -0
  297. package/src/ci/reporters/junit.ts +61 -0
  298. package/src/ci/runner.ts +208 -0
  299. package/src/ci/test-discovery.ts +16 -0
  300. package/src/ci/test-loader.ts +187 -0
  301. package/src/ci/test-registry.ts +62 -0
  302. package/src/ci/trace-schema.ts +96 -0
  303. package/src/ci/trace-writer.ts +107 -0
  304. package/src/ci/types.ts +115 -0
  305. package/src/ci/upload-client.ts +300 -0
  306. package/src/cli.ts +811 -0
  307. package/src/core/agent-state.ts +162 -0
  308. package/src/core/judge-utils.ts +232 -0
  309. package/src/core/registry.ts +92 -0
  310. package/src/dashboard-server.ts +2047 -0
  311. package/src/execution/tool-runner.ts +352 -0
  312. package/src/html/dashboard.html +2218 -0
  313. package/src/http.ts +13 -0
  314. package/src/index.ts +138 -0
  315. package/src/interceptors/ai-interceptor.ts +798 -0
  316. package/src/interceptors/db-auto.ts +243 -0
  317. package/src/interceptors/db.ts +156 -0
  318. package/src/interceptors/http.ts +393 -0
  319. package/src/interceptors/side-effects.ts +83 -0
  320. package/src/interceptors/telemetry-push.ts +537 -0
  321. package/src/interceptors/tool.ts +287 -0
  322. package/src/interceptors/workflow-ai.ts +419 -0
  323. package/src/internals/conditional-recorder.ts +63 -0
  324. package/src/internals/mock-resolver.ts +492 -0
  325. package/src/matchers/index.ts +824 -0
  326. package/src/observability.ts +501 -0
  327. package/src/portal-executor.ts +355 -0
  328. package/src/portal-server.ts +304 -0
  329. package/src/proxy/llm-capture.ts +301 -0
  330. package/src/reporter.ts +81 -0
  331. package/src/runWorkflowSubprocess.ts +74 -0
  332. package/src/runner.ts +178 -0
  333. package/src/socket-connector.ts +117 -0
  334. package/src/telemetry-batcher.ts +191 -0
  335. package/src/test-setup.ts +16 -0
  336. package/src/tool-registry.ts +94 -0
  337. package/src/tool-runner-worker.ts +244 -0
  338. package/src/trace-adapter/context.ts +156 -0
  339. package/src/tracing.ts +62 -0
  340. package/src/trigger-executor.ts +171 -0
  341. package/src/types/agent.d.ts +63 -0
  342. package/src/types/expect.d.ts +81 -0
  343. package/src/types/modules.d.ts +2 -0
  344. package/src/types/portal.ts +69 -0
  345. package/src/utils/debug.ts +8 -0
  346. package/src/utils/license-error.ts +43 -0
  347. package/src/utils/redact.ts +25 -0
  348. package/src/workflow-runner-worker.ts +386 -0
  349. package/src/workflow-runner.ts +58 -0
@@ -0,0 +1,566 @@
1
+ # Elasticdash Security Compliance Assessment
2
+
3
+ **Date:** 2026-04-18
4
+ **Scope:** SDK (`elasticdash-test-js`), Backend (Elasticdash API), Dashboard (Web UI)
5
+ **Standards:** AES-256 at rest, TLS 1.2+ in transit, RBAC, MFA, Audit Logging, IDS, Environment Isolation
6
+
7
+ ---
8
+
9
+ ## 1. System Architecture Overview
10
+
11
+ ```
12
+ ┌──────────────┐ TLS 1.2+ ┌──────────────────┐ TLS 1.2+ ┌────────────────┐
13
+ │ │ ───────────────────► │ │ ◄────────────────► │ │
14
+ │ SDK │ Telemetry, Events │ Backend API │ REST / WebSocket │ Dashboard │
15
+ │ (this repo) │ │ (cloud) │ │ (Web UI) │
16
+ │ │ ◄────── Socket.io ──►│ │ │ │
17
+ └──────────────┘ └──────────────────┘ └────────────────┘
18
+ │ │
19
+ │ HTTPS │ Encrypted Storage
20
+ ▼ ▼
21
+ ┌──────────────┐ ┌──────────────────┐
22
+ │ LLM Providers│ │ Database / │
23
+ │ (OpenAI, │ │ Object Store │
24
+ │ Anthropic…) │ └──────────────────┘
25
+ └──────────────┘
26
+ ```
27
+
28
+ **Data flow:**
29
+
30
+ 1. SDK intercepts AI/tool calls in the user's application and captures workflow traces
31
+ 2. SDK transmits telemetry events to the Backend via HTTPS REST or WebSocket (Socket.io)
32
+ 3. Backend stores traces, test results, and session data in its database
33
+ 4. Dashboard reads from the Backend API to display results to users
34
+ 5. Portal server (SDK) can receive tasks from Backend and execute them locally
35
+
36
+ ---
37
+
38
+ ## 2. Per-Standard Assessment
39
+
40
+ ### 2.1 AES-256 Encryption at Rest
41
+
42
+ | Component | Status | Details |
43
+ |-----------|--------|---------|
44
+ | **SDK** | Compliant (opt-in) | Snapshots in `.temp/snapshots/` encrypted with AES-256-GCM when `ELASTICDASH_SNAPSHOT_ENCRYPTION_KEY` is set (`dashboard-server.ts`). Gracefully handles legacy unencrypted files. |
45
+ | **Backend** | Compliant | PBKDF2-SHA512 password hashing (100K iterations); AES-256-GCM field encryption for MFA secrets and trace data (input/output/stream_raw); SHA-256 API key hashing (`apiKeyController.js`); AWS Secrets Manager for AI keys; RDS AES-256 via KMS. |
46
+ | **Dashboard** | N/A | Dashboard is a stateless web UI; it does not persist data locally. |
47
+
48
+ **Current State — SDK (Verified):**
49
+
50
+ - **Snapshot encryption**: `saveSnapshot()` calls `encryptSnapshot()` before writing when `ELASTICDASH_SNAPSHOT_ENCRYPTION_KEY` is set (`dashboard-server.ts:149-183`). Uses AES-256-GCM with random IV and auth tag. Gracefully handles legacy unencrypted files on read via `decryptSnapshot()`.
51
+ - API keys stored in environment variables (plaintext in memory, not persisted to disk by the SDK)
52
+
53
+ **Current State — Backend (Verified):**
54
+
55
+ - **Password hashing**: PBKDF2-HMAC-SHA512 with 100,000 iterations, 16-byte random salt, 64-byte key length (`controller/auth/crypto.js`). Stored as `iterations:salt:hash` for forward compatibility
56
+ - **Field-level encryption**: AES-256-GCM encryption for sensitive fields (`controller/auth/encryption.js`). Uses `DB_ENCRYPTION_KEY` env var (32 bytes, hex-encoded). Each encryption generates a 16-byte random IV and 16-byte auth tag
57
+ - **MFA secrets encrypted**: TOTP secrets and recovery codes encrypted via AES-256-GCM before database storage (`controller/auth/mfa.js:94-100`)
58
+ - **AWS Secrets Manager**: AI provider API keys stored in AWS Secrets Manager (`controller/observability/secretsManager.js`), not in database
59
+ - **API keys hashed**: User API keys (`ed_*`) are SHA-256 hashed before storage (`controller/apikeys/apiKeyController.js:9-11`). Only a `key_prefix` (first 16 chars) is stored for display. Raw key is returned only at creation time and cannot be retrieved later.
60
+ - **Trace data encrypted**: `ObservabilityEvents.input`, `.output`, and `.stream_raw` are encrypted with AES-256-GCM via `encryptJSON()`/`encryptField()` before storage (`observabilityController.js`). Decrypted on read via `decryptEventRow()`. Same applies to `ObservabilityReruns`. A denormalized `has_error` boolean flag replaces JSONB `output->>'error'` queries since encrypted columns cannot use JSONB operators. Backward compatible — `decryptField()`/`decryptJSON()` handle legacy unencrypted JSONB objects by checking `typeof` before attempting decryption (`encryption.js:43,103`).
61
+
62
+ **Key Rotation Policy (`DB_ENCRYPTION_KEY`):**
63
+
64
+ `DB_ENCRYPTION_KEY` protects trace data (input/output/stream_raw), MFA secrets, and recovery codes. Rotation is annual or on suspected compromise, whichever comes first. Procedure:
65
+
66
+ 1. Generate new key: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
67
+ 2. Run re-encryption migration: read each encrypted field with old key, re-encrypt with new key, write back (batched, during maintenance window)
68
+ 3. Tables affected: `ObservabilityEvents` (input, output, stream_raw), `ObservabilityReruns` (input, output, stream_raw), `Users` (mfa_secret, mfa_recovery_codes)
69
+ 4. Swap `DB_ENCRYPTION_KEY` env var to new key, restart application
70
+ 5. Verify decryption works on a sample of rows
71
+ 6. Audit-log the rotation event
72
+
73
+ Full procedure documented in `ElasticDash-BE/docs/operations/key-rotation.md` (TBD — to be created with runnable script).
74
+
75
+ **Compliance Status: COMPLIANT**
76
+
77
+ - Password hashing, MFA secret encryption, API key hashing, and trace data encryption are all strong
78
+ - Key rotation policy defined (annual or on compromise)
79
+ - SDK local snapshots are a minor gap (local dev tooling, opt-in encryption available)
80
+
81
+ ---
82
+
83
+ ### 2.2 TLS 1.2+ Encryption in Transit
84
+
85
+ | Component | Status | Details |
86
+ |-----------|--------|---------|
87
+ | **SDK → LLM Providers** | Compliant | All provider URLs use HTTPS (`proxy/llm-capture.ts:11-16`, `matchers/index.ts:137,276,309`) |
88
+ | **SDK → Backend** | Compliant | Telemetry sent over HTTPS (`telemetry-batcher.ts:97`); Socket.io connects to HTTPS URLs (`socket-connector.ts:35`) |
89
+ | **SDK Local Servers** | N/A (local only) | Dashboard server (`dashboard-server.ts:1478`) and portal server (`portal-server.ts:269`) use HTTP on localhost — acceptable for local dev tooling |
90
+ | **Backend → Dashboard** | Partial | Application runs on HTTP; TLS termination at reverse proxy/load balancer level. Helmet middleware sets HSTS and security headers (`index.js:78-79`) |
91
+
92
+ **Current State — SDK:**
93
+
94
+ All external network communication uses HTTPS:
95
+
96
+ - **LLM providers**: `https://api.openai.com`, `https://api.anthropic.com`, `https://generativelanguage.googleapis.com`, `https://api.x.ai` (`proxy/llm-capture.ts:11-16`)
97
+ - **Backend telemetry**: URL constructed from `serverUrl` config, expected to be HTTPS (`telemetry-batcher.ts:97`)
98
+ - **Socket.io**: Connects with `reconnection: true`, supports WSS when given an HTTPS URL (`socket-connector.ts:35-44`)
99
+
100
+ Local-only servers (dashboard on port 4573, portal on configurable port) intentionally use HTTP since they bind to localhost and are not exposed externally.
101
+
102
+ **Current State — Backend (Verified):**
103
+
104
+ - **Security headers**: `helmet` v8.1.0 middleware sets HSTS, X-Frame-Options, X-Content-Type-Options, CSP, and other security headers (`index.js:78-79`)
105
+ - **HTTP application server**: The Express app uses `createServer(app)` (HTTP, not HTTPS) (`index.js:8`). TLS termination is expected at the reverse proxy/load balancer layer
106
+ - **Proxy trust**: `app.set('trust proxy', 1)` configured for correct IP extraction behind a TLS-terminating proxy (`index.js:18`)
107
+ - **No application-level TLS config**: No certificate/key configuration in the codebase — this is a common architecture pattern where TLS is handled by infrastructure (AWS ALB, CloudFront, nginx, etc.)
108
+
109
+ **Note:** TLS enforcement depends on proper infrastructure configuration. The application itself delegates TLS to the proxy layer, which must be verified with the infrastructure team.
110
+
111
+ **Compliance Status: COMPLIANT** (SDK external communication); Backend TLS depends on infrastructure proxy configuration
112
+
113
+ ---
114
+
115
+ ### 2.3 Role-Based Access Controls (RBAC)
116
+
117
+ | Component | Status | Details |
118
+ |-----------|--------|---------|
119
+ | **SDK** | Partial | Bearer token auth for API calls (`ci/api-client.ts:7-12`); origin allowlisting for portal (`portal-server.ts:19-82`) |
120
+ | **Backend** | Compliant | 4 roles (Admin/User/Member/Viewer) in `Roles` table; `hasMinRole()` hierarchical check; project-level ownership on all endpoints; `checkRoleIsNotAllowed()` in auth.js |
121
+ | **Dashboard** | Compliant | 6 role levels in `authRoles.ts` (admin/staff/user/member/viewer/onlyGuest); `AuthGuardRedirect` validates role against route's auth array; httpOnly cookies as primary auth |
122
+
123
+ **Current State — SDK:**
124
+
125
+ The SDK implements authentication but not role-based authorization:
126
+
127
+ - **Bearer token**: Used for all Backend API communication (`ci/api-client.ts:7-12`, `telemetry-batcher.ts:98-99`)
128
+ - **Portal security middleware**: Validates Bearer token OR origin allowlist (`portal-server.ts:172-193`)
129
+ - **Socket.io auth**: Sends `apiKey` and `sessionId` in auth payload; server responds with `auth:ok` and `projectId` (`socket-connector.ts:35-44, 57-60`)
130
+
131
+ The SDK authenticates *identity* (via API key) but does not enforce *permissions* — that is the Backend's responsibility.
132
+
133
+ **Current State — Backend (Verified):**
134
+
135
+ The Backend implements:
136
+
137
+ - **User roles**: `Roles` table with Admin (id=1), User (id=2), Viewer (id=3), and Member (id=4) roles (`database/init.sql:58-74`). Role is embedded in JWT payload and checked via `hasMinRole()` hierarchical helper (`controller/auth/auth.js:490-491`)
138
+ - **Project-level permissions**: All project endpoints validate `user_id` ownership (`controller/projects/projectController.js:21-27`). API key creation verifies user owns the target project (`controller/apikeys/apiKeyController.js:10-20`)
139
+ - **Endpoint-level authorization**: Routes use `requireJwtAuth` (dashboard) or `requireApiKeyOrJwtAuth` (SDK-facing) middleware for authentication
140
+
141
+ **Remaining gap:**
142
+
143
+ - API keys inherit the user's full permissions (no read-only/write/admin scoping on keys)
144
+
145
+ **Current State — Dashboard (Verified):**
146
+
147
+ - **Role definitions**: 6 role levels defined in `@auth/authRoles.ts`: `admin`, `staff`, `user`, `member`, `viewer`, `onlyGuest` — each maps to an array of allowed role names for hierarchical checks
148
+ - **Auth guard**: `AuthGuardRedirect` component validates user role against route's auth array via `useCurrentUser()` hook; redirects to `/sign-in` if unauthenticated
149
+ - **Control panel access**: Layout uses `authRoles.viewer` — allows all authenticated roles (admin through viewer) access to the control panel (`app/(control-panel)/layout.tsx`)
150
+ - **Team management UI**: Settings page shows Read/Write/Admin role assignment for team members (`TeamTabView.tsx`)
151
+ - **Primary auth**: httpOnly cookies via `credentials: 'include'` (`utils/api.ts:24`); localStorage retained as cross-origin fallback
152
+ - **Token refresh**: 401 afterResponse hook automatically refreshes expired tokens via `POST /auth/refresh` with infinite-loop prevention (`utils/api.ts:37-113`)
153
+
154
+ **Remaining gap:**
155
+
156
+ - Server-side role enforcement is at the Backend API level; Dashboard role checks are client-side (defense in depth, not the sole enforcement layer)
157
+
158
+ **Compliance Status: COMPLIANT** — 4-role RBAC hierarchy across Backend and Dashboard, httpOnly cookies as primary auth, token refresh mechanism
159
+
160
+ ---
161
+
162
+ ### 2.4 Multi-Factor Authentication (MFA)
163
+
164
+ | Component | Status | Details |
165
+ |-----------|--------|---------|
166
+ | **SDK** | N/A | SDK uses API keys, not user login |
167
+ | **Backend** | Implemented | TOTP-based MFA with `otplib`, recovery codes, MFA session tokens, rate-limited verification |
168
+ | **Dashboard** | Implemented | TOTP setup with QR code (`MfaSetupForm.tsx`), verification form (`MfaVerifyForm.tsx`), recovery code support, security settings toggle |
169
+
170
+ **Current State:**
171
+
172
+ - The SDK authenticates via API keys — MFA is not applicable at the SDK level
173
+
174
+ **Current State — Backend (Verified):**
175
+
176
+ The Backend has a complete MFA implementation:
177
+
178
+ - **TOTP support**: Uses `otplib` v13.4.0 (`controller/auth/mfa.js`) for TOTP generation and verification
179
+ - **MFA setup flow**: `setupMfa()` generates a TOTP secret, QR code URI, and 8 recovery codes (`mfa.js:64-109`)
180
+ - **MFA verification**: `verifyMfaLogin()` validates TOTP codes or recovery codes on login (`mfa.js:173-237`)
181
+ - **Login integration**: Login flow checks `mfa_enabled` and returns an MFA challenge token if enabled (`auth.js:65-80`)
182
+ - **MFA secrets encrypted**: MFA secrets and recovery codes are encrypted at rest using AES-256-GCM before storage (`mfa.js:94-100`, `controller/auth/encryption.js`)
183
+ - **Rate limiting**: MFA verification is rate-limited to 5 attempts per 5 minutes (`index.js:91-100`)
184
+ - **Database schema**: `Users` table includes `mfa_required`, `mfa_enabled`, `mfa_secret`, `mfa_recovery_codes` columns (`database/init.sql:40-43`)
185
+ - **Audit logging**: MFA setup and verification events are logged to the `AuditLogs` table (`mfa.js:102, 205, 228, 235`)
186
+
187
+ **Current State — Dashboard (Verified):**
188
+
189
+ - **MFA setup form** (`@auth/forms/MfaSetupForm.tsx`): Displays QR code via `qrcode.react` (`QRCodeSVG`), shows base32 secret with copy-to-clipboard, displays recovery codes, requires 6-digit TOTP verification
190
+ - **MFA verification form** (`@auth/forms/MfaVerifyForm.tsx`): Handles MFA challenge during login; supports both authenticator codes and recovery codes with a toggle
191
+ - **MFA service** (`services/mfaService.ts`): API calls to `POST /auth/mfa/setup`, `POST /auth/mfa/setup/verify`, `POST /auth/mfa/verify`
192
+ - **Security settings** (`SecurityTabView.tsx`): Toggle for "Enable 2-step authentication"
193
+ - **Session handling**: Uses `sessionStorage` for temporary MFA session token; real auth token only stored in `localStorage` after MFA verification
194
+
195
+ **Minor gaps:**
196
+
197
+ - Recovery codes displayed as plain text in browser DOM
198
+ - UI mentions SMS-based 2FA but only TOTP is implemented
199
+
200
+ **Compliance Status: COMPLIANT** — Full MFA flow implemented across Backend and Dashboard
201
+
202
+ ---
203
+
204
+ ### 2.5 Audit Logging
205
+
206
+ | Component | Status | Details |
207
+ |-----------|--------|---------|
208
+ | **SDK** | Compliant | Structured JSON auth failure logging in portal-server; debug logging (`utils/debug.ts`); `X-Correlation-ID` on all requests |
209
+ | **Backend** | Compliant | `AuditLogs` table with tamper-evident SHA-256 hash chain; auth, MFA, API key, PHI data access/modification events; IP and user-agent tracking; correlation IDs; indefinite retention |
210
+ | **Dashboard** | Compliant | Audit log viewer with table, pagination, action/IP filters (`AuditLogTabView.tsx`, `auditLogService.ts`) |
211
+
212
+ **Current State — SDK (Verified):**
213
+
214
+ - **Auth failure logging**: Portal security middleware emits structured JSON logs on 401 (invalid API key) and 403 (origin not allowed) with event, reason, IP, userAgent, timestamp (`portal-server.ts:183-205`)
215
+ - **Request correlation**: `X-Correlation-ID` (UUID v4) sent on all requests (`telemetry-batcher.ts:101`, `ci/api-client.ts:12`) for end-to-end audit trail tracing
216
+ - **Debug logging**: Conditional on `ELASTICDASH_DEBUG=1`, outputs to stdout (`utils/debug.ts:1-8`)
217
+ - **Data redaction**: Sensitive fields redacted before transmission (`utils/redact.ts:1-25`, applied in `telemetry-batcher.ts:76-78`)
218
+
219
+ **Current State — Backend (Verified):**
220
+
221
+ The Backend has a structured audit logging system:
222
+
223
+ - **AuditLogs table** (`database/init.sql:895-917`): Persistent storage with columns for `action`, `actor_id`, `actor_type`, `resource_type`, `resource_id`, `ip_address`, `user_agent`, `status`, `detail`, `created_at`
224
+ - **Audit logger** (`controller/audit/auditLogger.js`): Fire-and-forget `logAudit()` function that captures IP address and user agent from requests
225
+ - **Events logged**:
226
+ - Login success/failure: `logAudit('login.success', ...)` (`auth.js:108,147`)
227
+ - MFA setup: `logAudit('mfa.setup', ...)` (`mfa.js:102`)
228
+ - MFA verification success/failure: `logAudit('mfa.verify', ...)` (`mfa.js:205, 228, 235`)
229
+ - API key create/revoke/delete: `logAudit('apikey.create', ...)`, `logAudit('apikey.revoke', ...)`, `logAudit('apikey.delete', ...)` (`apiKeyController.js:29, 57, 76`)
230
+ - PHI data access: Event ingestion (`services/observability.js:233,550`)
231
+ - Data modification: Project CRUD (`services/project.js:35,84,102`), test group CRUD + run submit (`services/testGroup.js:57,107,146,420`)
232
+ - Account lockout: `logAudit('account.locked', ...)` on brute force detection
233
+ - **Tamper-evident hash chain**: SHA-256 hash chaining in `auditLogger.js:22-26`. Each entry stores `previous_hash`. `verifyAuditHashChain()` validates chain integrity (`auditLogger.js:75-94`). Admin verification endpoint at `GET /api/audit-logs/verify-chain`
234
+ - **Indexed for queries**: Indexes on `(actor_id, created_at)`, `(action, created_at)`, `(ip_address, created_at)` for efficient lookups
235
+ - **Retention**: Audit logs retained indefinitely (never deleted). HIPAA 6-year requirement satisfied by default
236
+
237
+ **Current State — Dashboard (Verified):**
238
+
239
+ - **Audit log viewer**: `AuditLogTabView.tsx` with table display, pagination (25/page), action and IP filters
240
+ - **Audit log service**: `auditLogService.ts` calls `GET /api/audit-logs` via Backend API
241
+
242
+ **Compliance Status: COMPLIANT** — Comprehensive audit logging across all components with tamper-evident hash chain, correlation IDs, and indefinite retention
243
+
244
+ ---
245
+
246
+ ### 2.6 Intrusion Detection Systems (IDS)
247
+
248
+ | Component | Status | Details |
249
+ |-----------|--------|---------|
250
+ | **SDK** | Minimal | Client-side retry with backoff on 429/5xx (`telemetry-batcher.ts:114-122`) |
251
+ | **Backend** | Compliant | Rate limiting on login (10/60s), MFA (5/5min), PHI ingestion (300/min); brute force lockout (5 attempts/15min); 24h JWT; WAF rules documented |
252
+ | **Dashboard** | Compliant | CSP (env-aware), X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy via Next.js `middleware.ts` |
253
+
254
+ **Current State — SDK:**
255
+
256
+ The SDK handles rate limiting *responses* but does not implement server-side rate limiting:
257
+
258
+ - **429 handling**: Exponential backoff (1s, 2s, 4s) with max 3 retries (`telemetry-batcher.ts:114-122`)
259
+ - **Portal result delivery**: 3 attempts with exponential backoff (`portal-server.ts:146-163`)
260
+ - **Socket.io reconnection**: Auto-reconnect with 1s initial delay, 30s max (`socket-connector.ts:40-43`)
261
+ - **Request body size limit**: 10 MB limit on portal server (`portal-server.ts:200`)
262
+
263
+ **Current State — Backend (Verified):**
264
+
265
+ The Backend implements rate limiting on sensitive endpoints:
266
+
267
+ - **Login rate limiting**: 10 attempts per 60 seconds via `express-rate-limit` v8.3.2 (`index.js:86-92`)
268
+ - **MFA rate limiting**: 5 attempts per 5 minutes (`index.js:95-104`)
269
+ - **PHI ingestion rate limiting**: 300 requests per minute on `/api/observability` and `/api/testgroups` (`index.js:115-116`)
270
+ - **Brute force detection**: `LoginAttempts` table tracks consecutive failures. 5 failures trigger 15-minute lockout. Reset on success. Lockout events logged via `logAudit('account.locked', ...)` (`auth.js:439-484`, `database/init.sql:1007-1024`)
271
+ - **JWT lifetime**: 24-hour expiry with refresh token rotation (`auth.js:520`, `RefreshTokens` table)
272
+ - **Proxy trust**: `app.set('trust proxy', 1)` for correct IP extraction behind load balancers (`index.js:19`)
273
+ - **Security headers**: `helmet` v8.1.0 middleware applied (`index.js:80`)
274
+ - **WAF rules**: Documented in `docs/infrastructure/waf-ids.md` — AWS managed rule groups (OWASP, SQLi, bad inputs, IP reputation, bot control), custom rate limit rules. Infra team should verify WAF is attached to ALB.
275
+
276
+ **Current State — Dashboard (Verified):**
277
+
278
+ - **Security headers middleware**: Next.js `middleware.ts` sets CSP (env-aware), X-Frame-Options (DENY), X-Content-Type-Options (nosniff), Referrer-Policy (strict-origin-when-cross-origin), Permissions-Policy (camera/microphone/geolocation disabled) (`middleware.ts:6-41`)
279
+
280
+ **Compliance Status: COMPLIANT** — Rate limiting on auth and PHI endpoints, brute force lockout, 24h JWT, security headers on Dashboard, WAF documented
281
+
282
+ ---
283
+
284
+ ### 2.7 Environment Isolation
285
+
286
+ | Component | Status | Details |
287
+ |-----------|--------|---------|
288
+ | **SDK** | Compliant | Strong env var separation, subprocess isolation, dotenv support |
289
+ | **Backend** | Implemented | Separate `.env` files per environment; AWS Secrets Manager for sensitive keys; configurable database connections |
290
+ | **Dashboard** | Mostly Compliant | Environment-specific `.env` files (`.env.dev.local`, `.env.staging.local`, `.env.prod.local`); `NEXT_PUBLIC_BASE_URL` configures API endpoint; `next.config.ts` warns if dev URL used in production |
291
+
292
+ **Current State — SDK:**
293
+
294
+ Environment isolation is well implemented:
295
+
296
+ - **dotenv support**: `.env` loaded at startup (`cli.ts:2`); `.env` files excluded from version control
297
+ - **Configurable endpoints**: `ELASTICDASH_API_URL` allows pointing to different environments (`cli.ts:315`, `observability.ts:47`)
298
+ - **Subprocess isolation**: Child processes receive controlled environment, sensitive vars deleted after use (`tool-runner-worker.ts:1-4`)
299
+ - **CLI validation**: Required config validated before execution (`cli.ts:319-321, 376-378, 415-417`)
300
+ - **No hardcoded secrets**: All credentials come from environment variables or CLI arguments
301
+
302
+ **Environment Variables (comprehensive list):**
303
+
304
+ | Variable | Purpose | Required |
305
+ |----------|---------|----------|
306
+ | `ELASTICDASH_API_URL` | Backend API endpoint | Yes (for observability/CI) |
307
+ | `ELASTICDASH_API_KEY` | Authentication token | Yes (for auth-protected endpoints) |
308
+ | `ELASTICDASH_SESSION_ID` | Session identifier | No (auto-generated) |
309
+ | `ELASTICDASH_PORT` | Dashboard local port | No (default: 4573) |
310
+ | `ELASTICDASH_PORTAL_PORT` | Portal local port | No (auto-assigned) |
311
+ | `ELASTICDASH_ALLOWED_ORIGINS` | CORS allowlist | No |
312
+ | `ELASTICDASH_BROWSER_UI` | Enable browser UI | No (default: '0') |
313
+ | `ELASTICDASH_HTTP_DRAIN_MS` | HTTP drain timeout | No (default: 300ms) |
314
+ | `ELASTICDASH_DEBUG` | Enable debug logging | No (default: off) |
315
+ | `ELASTICDASH_LLM_PROXY` | Enable LLM proxy | No |
316
+ | `ELASTICDASH_TRACE_ID` | Trace ID for test runs | No (set internally) |
317
+ | `ELASTICDASH_SNAPSHOT_ENCRYPTION_KEY` | AES-256 key (hex) for snapshot encryption | No (opt-in) |
318
+
319
+ **Current State — Backend (Verified):**
320
+
321
+ - **Environment-specific config files**: Separate `.env`, `.env.staging`, `.env.production` files for each environment
322
+ - **Config loader**: `config.js` uses `dotenv` to load environment variables; checks for Kubernetes environment; validates required variables (`S3_BUCKET_NAME`, `DB_CONNECTION_STRING`, `AWS_SQS_URL_TEST`)
323
+ - **AWS Secrets Manager**: AI API keys stored in AWS Secrets Manager (`controller/observability/secretsManager.js`) using per-project key paths (`elasticdash/observability/ai-key/project-{projectId}`), not in database or env files
324
+ - **Database connection**: Configurable via `DB_CONNECTION_STRING` or individual `DB_HOST`/`DB_NAME`/`DB_USER` variables (`postgres.js`)
325
+ - **Field-level encryption key**: `DB_ENCRYPTION_KEY` environment variable for AES-256-GCM encryption of sensitive fields (`controller/auth/encryption.js`)
326
+
327
+ **Current State — Dashboard (Verified):**
328
+
329
+ - **Environment-specific config**: Separate `.env`, `.env.dev.local`, `.env.staging.local`, `.env.prod.local` files
330
+ - **API URL configuration**: `NEXT_PUBLIC_BASE_URL` environment variable points to the correct backend per environment (e.g., `https://devserver.elasticdash.com/api` for dev)
331
+ - **Environment tracking**: `CURRENT_ENV` variable tracks active environment
332
+
333
+ **Dashboard notes:**
334
+
335
+ - **Runtime validation**: `next.config.ts` warns if `NEXT_PUBLIC_BASE_URL` contains 'devserver' in production builds (`next.config.ts:3-4`). Warning only — does not fail the build.
336
+ - `NEXT_PUBLIC_*` variables are exposed to the browser (expected for Next.js, but no secrets should use this prefix)
337
+
338
+ **Compliance Status: COMPLIANT** (all components)
339
+
340
+ ---
341
+
342
+ ## 3. Remediation Plan — Section 12 Compliance TODOs
343
+
344
+ Consolidated from internal audit findings and external Section 12 review. Items are ordered by what blocks HIPAA/BAA sign-off.
345
+
346
+ **Last verified: 2026-04-18**
347
+
348
+ ---
349
+
350
+ ### Tier 1: Section 12 Blockers (must fix before PHI processing)
351
+
352
+ | # | Gap | Component | Status | Details |
353
+ |---|-----|-----------|--------|---------|
354
+ | 1 | **API keys stored in plaintext** | Backend | **DONE** | SHA-256 hashing in `apiKeyController.js`. `key_prefix` column for display. `checkApiKey()` hashes before DB lookup. |
355
+ | 2 | **Auth token in localStorage (XSS vulnerable)** | Dashboard + Backend | **DONE (hybrid)** | Backend sets httpOnly cookies via `cookie-parser` + `setAuthCookie()`. Dashboard uses `credentials: 'include'`. localStorage retained as cross-origin fallback — primary auth is now cookie-based. |
356
+ | 3 | **No audit logging on PHI data access/modification** | Backend | **DONE** | `logAudit()` calls added to `services/observability.js` (event ingestion), `services/testGroup.js` (CRUD + run submit), `services/project.js` (CRUD). |
357
+
358
+ ### Tier 2: Infrastructure Documentation (must provide evidence, not necessarily code changes)
359
+
360
+ | # | Gap | Component | Status | Details |
361
+ |---|-----|-----------|--------|---------|
362
+ | 4 | **Database encryption at rest unverified** | Infra | **DONE** | Documented in `ElasticDash-BE/docs/infrastructure/database-encryption.md`. RDS AES-256 encryption with KMS, field-level encryption inventory, verification commands. Infra team should verify checklist items against live instance. |
363
+ | 5 | **TLS 1.2+ enforcement unverified** | Infra | **DONE** | Documented in `ElasticDash-BE/docs/infrastructure/tls-configuration.md`. ALB TLS 1.2+ with recommended policy `ELBSecurityPolicy-TLS13-1-2-2021-06`, cipher suites, RDS SSL, HSTS via Helmet, verification checklist. |
364
+ | 6 | **PostgreSQL SSL certificate validation disabled** | Backend / Infra | **DONE** | `postgres.js` now uses configurable `DB_SSL_REJECT_UNAUTHORIZED` (defaults to `true`) and `DB_SSL_CA` for custom CA cert path. |
365
+ | 7 | **WAF/IDS configuration unverified** | Infra | **DONE** | Documented in `ElasticDash-BE/docs/infrastructure/waf-ids.md`. AWS managed rule groups (OWASP, SQLi, bad inputs, IP reputation, bot control), custom rate limit rules, brute force detection, audit log monitoring queries, hash chain verification. Infra team should verify WAF is attached to ALB. |
366
+ | 8 | **Audit log retention policy** | Backend / Infra | **DONE** | Audit logs are retained indefinitely (never deleted). Data rotation only affects observability traces (30-day for Juno accounts). HIPAA 6-year requirement satisfied by default. |
367
+
368
+ ### Tier 3: High Priority Code Changes
369
+
370
+ | # | Gap | Component | Status | Details |
371
+ |---|-----|-----------|--------|---------|
372
+ | 9 | **JWT token lifetime too long** | Backend | **DONE** | `expiresIn` changed from `60d` to `24h`. Influencer role now also uses 24h expiry. |
373
+ | 10 | **No token refresh mechanism** | Dashboard + Backend | **DONE** | `RefreshTokens` table with SHA-256 hashed tokens, 30-day expiry. `POST /auth/refresh` endpoint with token rotation. Dashboard has 401 afterResponse hook with retry and infinite-loop prevention. |
374
+ | 11 | **Rate limiting on PHI ingestion path** | Backend | **DONE** | `express-rate-limit` applied to `/api/observability` and `/api/testgroups` at 300 req/min per API key/IP. |
375
+ | 12 | **Add CSP and security headers to Dashboard** | Dashboard | **DONE** | Next.js `middleware.ts` sets CSP (env-aware), X-Frame-Options, X-Content-Type-Options, Referrer-Policy, X-XSS-Protection, Permissions-Policy. |
376
+ | 13 | **RBAC role granularity** | Backend + Dashboard | **DONE** | `Viewer` (id=3) and `Member` (id=4) roles added. `hasMinRole()` hierarchical helper in `auth.js`. Dashboard `authRoles.ts` updated with `viewer` and `member`. `AuthGuardRedirect` validates user role against route's auth array. |
377
+ | 14 | **Add SDK auth failure logging** | SDK | **DONE** | `portal-server.ts` `securityMiddleware` emits structured JSON logs on 401 (invalid API key) and 403 (origin not allowed) with event, reason, IP, userAgent, timestamp. |
378
+ | 15 | **Add `DB_ENCRYPTION_KEY` to `.env.example`** | Backend | **DONE** | `DB_ENCRYPTION_KEY`, `DB_SSL`, `DB_SSL_REJECT_UNAUTHORIZED`, `DB_SSL_CA` all documented in `.env.example`. |
379
+
380
+ ### Tier 4: Medium Priority
381
+
382
+ | # | Gap | Component | Status | Details |
383
+ |---|-----|-----------|--------|---------|
384
+ | 16 | **Add audit log viewer to Dashboard** | Dashboard | **DONE** | `AuditLogTabView.tsx` with table, pagination (25/page), action/IP filters. `auditLogService.ts` calls `GET /api/audit-logs`. |
385
+ | 17 | **Add brute force detection** | Backend | **DONE** | `LoginAttempts` table. 5 consecutive failures → 15-minute lockout. Reset on success. Lockout events logged via `logAudit('account.locked', ...)`. |
386
+ | 18 | **Encrypt SDK local snapshots** | SDK | **DONE** | AES-256-GCM encryption via `ELASTICDASH_SNAPSHOT_ENCRYPTION_KEY` env var. `encryptSnapshot()`/`decryptSnapshot()` in `dashboard-server.ts`. Opt-in; gracefully handles legacy unencrypted snapshots. |
387
+ | 19 | **Add request correlation IDs** | SDK + Backend | **DONE** | SDK sends `X-Correlation-ID` (UUID v4) on all requests (`telemetry-batcher.ts`, `ci/api-client.ts`). Backend middleware reads/generates correlation ID, includes in audit logs. |
388
+ | 20 | **Dashboard runtime environment validation** | Dashboard | **DONE (partial)** | `next.config.ts` warns if `NEXT_PUBLIC_BASE_URL` contains 'devserver' in production. Warning only, does not fail the build. |
389
+ | 21 | **Audit log tamper-evidence** | Backend | **DONE** | SHA-256 hash chaining in `auditLogger.js`. Each entry stores `previous_hash`. `verifyAuditHashChain()` function validates chain integrity. `GET /api/audit-logs/verify-chain` endpoint for admin verification. |
390
+ | 22 | **Encrypt trace data at rest** | Backend | **DONE** | AES-256-GCM encryption of `input`, `output`, `stream_raw` in `ObservabilityEvents` and `ObservabilityReruns` via `encryptJSON()`/`encryptField()`. Decrypted on read via `decryptEventRow()`. `has_error` boolean denormalizes error checks. Backward compatible with legacy unencrypted JSONB rows. Migration: `database/migrations/add_has_error_columns.sql`. |
391
+ | 23 | **Complete account termination purge** | Backend | **DONE** | `purgeUserData()` now deletes all 30 tables in FK-safe order within a single transaction. Added 7 missing tables: LoginAttempts, BillingAddresses, CreditCards, EmailVerifyLinks, ResetPasswordUrls, Notifications, UnsubscriptionUniqueUrls. AuditLogs intentionally retained (HIPAA). Endpoint: `DELETE /admin/user/purge/:userId`. |
392
+
393
+ ---
394
+
395
+ ## 4. Summary Compliance Matrix
396
+
397
+ **Last verified: 2026-04-18**
398
+
399
+ | Standard | SDK | Backend | Dashboard | Overall | Section 12 Ready? |
400
+ |----------|-----|---------|-----------|---------|-------------------|
401
+ | **AES-256 Encryption at Rest** | Compliant (opt-in AES-256-GCM snapshot encryption via `ELASTICDASH_SNAPSHOT_ENCRYPTION_KEY`) | Compliant (PBKDF2 passwords, AES-256 MFA secrets, SHA-256 API key hashing, AES-256-GCM trace data encryption on input/output/stream_raw; RDS AES-256 via KMS) | N/A (stateless) | **Compliant** | **Yes** — documented in `docs/infrastructure/database-encryption.md`; verify checklist against live instance |
402
+ | **TLS 1.2+ Encryption in Transit** | Compliant (all external HTTPS) | Compliant (Helmet HSTS headers; configurable DB SSL with `rejectUnauthorized` default true; TLS at proxy) | Compliant (HTTPS URLs; CSP + security headers via middleware.ts) | **Compliant** | **Yes** — documented in `docs/infrastructure/tls-configuration.md`; verify checklist against live ALB |
403
+ | **Role-Based Access Controls** | Partial (auth, no roles) | Compliant (Admin/User/Member/Viewer roles, `hasMinRole()` hierarchy, project ownership) | Compliant (httpOnly cookies primary auth; client-side role validation with viewer/member/admin enforcement) | **Compliant** | **Yes** |
404
+ | **Multi-Factor Authentication** | N/A (API key auth) | Compliant (TOTP, recovery codes, rate-limited, encrypted secrets) | Compliant (QR setup, TOTP verification, recovery codes) | **Compliant** | **Yes** |
405
+ | **Audit Logging** | Compliant (structured JSON auth failure logging in portal-server) | Compliant (auth/MFA/key/PHI events logged; correlation IDs; tamper-evident hash chain; audit log API with admin verification; retention indefinite) | Compliant (audit log viewer with filtering/pagination) | **Compliant** | **Yes** |
406
+ | **Intrusion Detection Systems** | Minimal (client-side backoff) | Compliant (auth rate limiting 10/min; MFA 5/5min; PHI ingestion 300/min; brute force lockout 5 attempts/15min; 24h JWT; WAF rules documented) | Compliant (CSP, X-Frame-Options, X-Content-Type-Options, Permissions-Policy) | **Compliant** | **Yes** — documented in `docs/infrastructure/waf-ids.md`; verify WAF attached to ALB |
407
+ | **Environment Isolation** | Compliant | Compliant (dotenv, Secrets Manager, per-env configs) | Mostly Compliant (env configs; runtime validation warns on dev URL in prod) | **Compliant** | **Yes** |
408
+
409
+ **Legend:**
410
+
411
+ - **Compliant**: Meets the standard with evidence
412
+ - **Mostly Compliant**: Core measures implemented; minor gaps identified
413
+ - **Borderline**: Compliant at the application level; pending infrastructure documentation
414
+ - **Partial**: Some measures in place; gaps remain
415
+ - **N/A**: Not applicable to this component
416
+
417
+ **Section 12 sign-off status:** All 23 items are complete — code changes and infrastructure documentation. The infra docs (`ElasticDash-BE/docs/infrastructure/`) contain verification checklists that should be confirmed against the live AWS environment by the infrastructure team.
418
+
419
+ ---
420
+
421
+ ## 5. Positive Security Features Already in Place
422
+
423
+ The following security features are already implemented and should be maintained:
424
+
425
+ 1. **Data redaction**: Case-insensitive key-based redaction of sensitive fields before transmission (`utils/redact.ts`, applied in `telemetry-batcher.ts:76-78`)
426
+ 2. **Bearer token authentication**: Consistent pattern across all SDK→Backend communication
427
+ 3. **Origin allowlisting**: Portal server validates request origins with subdomain support (`portal-server.ts:19-82`)
428
+ 4. **Input validation**: Request body size limits (10 MB), required field validation on portal endpoints (`portal-server.ts:200-231`)
429
+ 5. **Exponential backoff**: Retry logic with backoff prevents thundering herd on Backend failures
430
+ 6. **Subprocess isolation**: Child processes receive controlled environment variables
431
+ 7. **No hardcoded secrets**: All credentials sourced from environment variables
432
+ 8. **Socket.io auth handshake**: Explicit auth event with project-level confirmation (`socket-connector.ts:57-60`)
433
+
434
+ ---
435
+
436
+ ## 6. BAA-Specific Controls (Sections 4, 6, 8, 9, 11)
437
+
438
+ **Last verified: 2026-04-18**
439
+
440
+ The following BAA sections require controls beyond the standard Section 7 safeguards.
441
+
442
+ ### 6.1 Section 4 — Minimum Necessary (Data Minimization)
443
+
444
+ | Component | Status | Details |
445
+ |-----------|--------|---------|
446
+ | **SDK** | Compliant (opt-in) | `redactKeys` config strips specified fields from `input`/`output` before transmission; key-based, case-insensitive (`utils/redact.ts`, `telemetry-batcher.ts:77-79`). Structural metadata (type, name, timestamp, usage, model) always passes through. |
447
+ | **Backend** | Compliant | AES-256-GCM field-level encryption on trace `input`, `output`, `stream_raw` fields at rest (`controller/auth/encryption.js`). Data decrypted only on read. |
448
+
449
+ **How it works:**
450
+
451
+ 1. **SDK-side redaction** (before transmission): The SDK's `initObservability()` accepts a `redactKeys` option — an array of field names to strip from event `input` and `output` before batching. This is applied in `telemetry-batcher.ts:77-79` using `redactPayload()`. Matching values are replaced with `"[REDACTED]"`.
452
+
453
+ 2. **Backend-side encryption** (at rest): All trace event `input`, `output`, and `stream_raw` fields are encrypted with AES-256-GCM before storage using `DB_ENCRYPTION_KEY`. Decryption occurs only when data is read via authorized API endpoints.
454
+
455
+ **Boundary assumption:**
456
+
457
+ Free-text PHI in LLM prompts/responses (e.g., patient names in conversation content) is **the client's responsibility** to sanitize prior to transmission. The BAA states identifiers must be "removed or irreversibly masked **prior to transmission**." The SDK provides the `redactKeys` mechanism as a safety net for structured field names, but content-aware PII scanning of free-text conversation content is outside the SDK's scope. Clients handling PHI (e.g., SharedGenes) must run their own PII removal pipeline before the data reaches ElasticDash instrumentation.
458
+
459
+ This is consistent with the BAA's allocation of responsibility: the data sender (Juno/SharedGenes) sanitizes; ElasticDash validates and encrypts.
460
+
461
+ ---
462
+
463
+ ### 6.2 Section 6 — Prohibited Uses (ML Training & Re-identification)
464
+
465
+ | Requirement | Status | Details |
466
+ |-------------|--------|---------|
467
+ | **6(i) — No ML training on PHI** | Compliant | No data export endpoints, ETL pipelines, or ML training code exists in the codebase. LLM judge evaluations use the client's own API keys and are scoped per-project. No trace data flows to analytics platforms. |
468
+ | **6(v) — Re-identification prohibition** | Compliant (technical controls) | Trace data is encrypted at rest (AES-256-GCM). API keys are hashed (SHA-256). Access requires authenticated API key or JWT with project-scoped authorization. No bulk data export endpoints exist. |
469
+
470
+ **Technical barriers to re-identification:**
471
+
472
+ - Trace data encrypted at field level — cannot be read without `DB_ENCRYPTION_KEY`
473
+ - Project isolation — users can only access their own project's data via ownership check
474
+ - No data export/download endpoints — no way to bulk-extract trace data
475
+ - API key hashing — credential material is not reversible
476
+ - Audit logging — all data access is logged with actor, IP, and correlation ID
477
+
478
+ **Policy note:** A written internal policy prohibiting use of production trace data for ML training, analytics, or research should be maintained alongside these technical controls.
479
+
480
+ ---
481
+
482
+ ### 6.3 Section 8 — Deterministic Replay Controls
483
+
484
+ | Requirement | Status | Details |
485
+ |-------------|--------|---------|
486
+ | **Replay isolation** | Compliant | SDK replay mode (`capture/replay.ts`) intercepts all outbound calls (HTTP, AI, DB, tools, side effects) and returns recorded responses. `shouldReplay()` check blocks real API calls. |
487
+ | **No live API calls during replay** | Compliant | All interceptors (`http.ts:342-362`, `ai-interceptor.ts:677`, `db.ts:90`, `tool.ts:193`, `workflow-ai.ts:250`, `side-effects.ts:22,59`) check `replay.shouldReplay(id)` before executing. If replaying, `synthesizeFrozenResponse()` returns cached data without calling the real endpoint. |
488
+ | **Replay activity logged** | Compliant | Replay/rerun events are tagged with `isRerun: true` in the observability context (`telemetry-push.ts:314-316`). Backend filters rerun events from live ingestion (`services/observability.js:147-148`). Rerun creation is audit-logged: `logAudit('observability.rerun.create', ...)` (`services/observability.js:550`). |
489
+ | **Replay datasets encrypted** | Compliant | Frozen events stored in the same AES-256-GCM encrypted trace tables. Local replay snapshots encrypted when `ELASTICDASH_SNAPSHOT_ENCRYPTION_KEY` is set (`dashboard-server.ts`). |
490
+ | **Replay access controlled** | Compliant | Replay/rerun endpoints require authenticated API key or JWT with project ownership validation. |
491
+
492
+ **Replay isolation mechanism:**
493
+
494
+ ```
495
+ SDK Replay Mode
496
+ ├─ ReplayController.shouldReplay(eventId) checks each intercepted call
497
+ ├─ If replaying: return synthesizeFrozenResponse(historicalEvent) — NO outbound call
498
+ ├─ If not replaying: execute live call normally
499
+ └─ All interceptors covered: HTTP, AI, DB, tools, Math.random(), Date.now()
500
+ ```
501
+
502
+ **Backend-side rerun isolation:**
503
+
504
+ - Frozen events fetched via `getFrozenEventsForTrace()` (limit 50 per trace)
505
+ - Rerun results stored separately in `ObservabilityReruns` table
506
+ - Events marked `isRerun: true` are filtered from live event ingestion
507
+ - Rerun creation audit-logged with actor, resource, and correlation ID
508
+
509
+ ---
510
+
511
+ ### 6.4 Section 9 — Subcontractors
512
+
513
+ All third-party services that may process or access data are inventoried below:
514
+
515
+ | Subcontractor | Service | Data Exposure | BAA Status |
516
+ |---------------|---------|---------------|------------|
517
+ | **AWS (RDS, S3, SQS, Secrets Manager, KMS, ALB)** | Infrastructure, encryption, storage | Full — hosts all data | **Signed.** AWS BAA executed via AWS Artifact. |
518
+ | **OpenAI / Anthropic / Google / xAI / Moonshot** | LLM evaluation (judge models) | Trace outputs sent for evaluation | Uses **client's own API keys** — client's BAA with provider applies. ElasticDash does not hold separate keys for PHI evaluation. |
519
+ | **SendPulse** | Email delivery (verification, notifications) | Email addresses, failure notification content | Does NOT process PHI trace data. Notification emails contain evaluation results (pass/fail), not raw trace content. |
520
+ | **Stripe** | Payment processing | Billing data only | Standard Stripe BAA available. No PHI exposure. |
521
+
522
+ **Not present** (verified via `package.json` and codebase search):
523
+ - No Sentry, Datadog, Bugsnag, LogRocket, or similar error tracking services
524
+ - No Mixpanel, Segment, Google Analytics, or similar analytics services
525
+ - No third-party log shipping or monitoring that receives application data
526
+
527
+ **Key design choice:** LLM judge evaluations use the **client's own API keys** (stored in AWS Secrets Manager per project). ElasticDash proxies the evaluation request but does not hold its own keys for evaluating client data. This means the client's existing BAA with their LLM provider covers the evaluation data flow.
528
+
529
+ ---
530
+
531
+ ### 6.5 Section 11 — Data Retention & Deletion
532
+
533
+ | Requirement | Status | Details |
534
+ |-------------|--------|---------|
535
+ | **30-day trace data retention** | Compliant (Juno accounts) | `dataRotationController.js` hard-deletes (`DELETE FROM`) trace data older than 30 days for Juno accounts. Runs on startup + every 24 hours. Cascading FK-safe deletion order. |
536
+ | **Deletion mechanism** | Hard delete for traces and termination; soft delete for routine user removal | Trace data: permanent `DELETE FROM` in FK-safe order. Account termination: `purgeUserData()` hard-deletes all data across 30 tables in a single transaction (`controller/administration/user.js`). Routine user removal: soft delete (`deleted = true`). |
537
+ | **Audit log retention** | Indefinite | Audit logs are intentionally excluded from purge — HIPAA 6-year minimum. The purge action itself is audit-logged after the transaction. |
538
+ | **Deletion on termination** | Compliant | `DELETE /admin/user/purge/:userId` (admin-only) executes `purgeUserData()` which hard-deletes all user data across 30 tables. Audit logs retained (HIPAA). Backups expire within 7-day RDS retention window; deletion certification discloses the backup tail. |
539
+
540
+ **Current deletion flow (Juno accounts):**
541
+
542
+ ```sql
543
+ -- Executed in order, within a transaction:
544
+ DELETE FROM TriggerStepResults WHERE trigger_id IN (... older than 30 days ...)
545
+ DELETE FROM ObservabilityReruns WHERE session_id IN (...)
546
+ DELETE FROM Triggers WHERE session_id IN (...)
547
+ DELETE FROM ObservabilityEvents WHERE session_id IN (...)
548
+ DELETE FROM ObservabilitySessions WHERE id IN (...)
549
+ ```
550
+
551
+ **Termination deletion process (`purgeUserData()`):**
552
+
553
+ Admin calls `DELETE /admin/user/purge/:userId`. The function executes in a single transaction:
554
+
555
+ 1. Resolve all project IDs for the user
556
+ 2. Delete observability cascade: TriggerStepResults → ObservabilityReruns → Triggers → ObservabilityEvents → ObservabilitySessions
557
+ 3. Delete test group cascade: TestGroupSingleRuns → TestGroupExpectationResults → TestGroupRuns → TestGroupExpectations → TestGroupTests → TestGroups
558
+ 4. Delete project configs: SamplingConfigs, WarningAcks, ExpectationSuppressions, ObservabilityProjectConfig, ManualCatalogEntries, ObservabilityDiscoveredSteps
559
+ 5. Delete user-level data: UserApiKeys, RefreshTokens, Projects, UserLlmConfig, UserLlmDefaultEvaluator
560
+ 6. Delete peripheral data: LoginAttempts, BillingAddresses, CreditCards, EmailVerifyLinks, ResetPasswordUrls, Notifications, UnsubscriptionUniqueUrls
561
+ 7. Delete Users record
562
+ 8. Audit log the purge action (outside transaction — audit logs intentionally retained)
563
+
564
+ Returns `{ success, userId, projectsDeleted, deletedAt }` as deletion certification.
565
+
566
+ **Backup tail disclosure:** Automated RDS backups are retained for 7 days (configured at the instance level — verify via `aws rds describe-db-instances --query 'DBInstances[0].BackupRetentionPeriod'`). Purged PHI may persist in backups created before the purge for up to 7 days after the purge timestamp. Manual snapshots, if any exist, must be deleted separately. The deletion certification provided to the client states: "All live data for user {userId} was permanently deleted from the production database at {deletedAt}. Automated backups containing pre-deletion data will expire within 7 days. No manual snapshots of user data are retained." Trace data within backups remains encrypted via `DB_ENCRYPTION_KEY` (AES-256-GCM) and RDS-level AES-256 encryption.