CVE-2026-70608: Verified Reproduction
CVE-2026-70608: Hermes Agent Electron preview webview sandbox escape via CVE-2026-70608
CVE-2026-70608 is verified against hermes-agent · github. Affected versions: electron <39.8.10, >=40.0.0-alpha.1 <41.10.3, >=42.0.0-alpha.1 <42.0.1. Fixed in 41.10.3. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00326.
What Is CVE-2026-70608?
CVE-2026-70608 is a high-severity vulnerability affecting hermes-agent electron <39.8.10, >=40.0.0-alpha.1 <41.10.3, >=42.0.0-alpha.1 <42.0.1. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00326).
CVE-2026-70608 Severity
CVE-2026-70608 is rated high severity.
High — serious impact or readily exploitable. Prioritize remediation.
Affected hermes-agent Versions
hermes-agent · github versions electron <39.8.10, >=40.0.0-alpha.1 <41.10.3, >=42.0.0-alpha.1 <42.0.1 are affected.
How to Reproduce CVE-2026-70608
pruva-verify REPRO-2026-00326 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00326/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-70608
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
content of a page loaded in hermes's right-rail URL preview webview (attacker-selected URL), incl. a sandboxed iframe without allow-popups dispatching a synthetic ctrl+meta click with no user gesture
- chat open_preview tool / preview restore
- preview-pane.tsx <webview partition=persist:hermes-preview> guest
- CVE-2026-70608 OpenURL bypass
- guest did-create-window
- real BrowserWindow (no setWindowOpenHandler on the guest)
reproduction_steps.sh How the agent worked
Root Cause and Exploit Chain for CVE-2026-70608
hermes-agent's right-rail URL preview (apps/desktop/src/app/chat/right-rail/preview-pane.tsx:557-561)
creates a <webview partition="persist:hermes-preview" webpreferences="contextIsolation=yes,nodeIntegration=no,sandbox=yes"> whose guest webContents
carries no setWindowOpenHandler (verified: no web-contents-created / did-attach /
new-window wiring exists anywhere in apps/desktop/electron/main.ts). In Electron, no handler
means default ALLOW. On Electron 40.10.2 (pinned in apps/desktop/package.json), the
CVE-2026-70608 OpenURL bypass — a synthetic ctrl/meta-click dispatched from a sandboxed iframe
without allow-popups — fires inside that guest and, because no handler exists to deny it,
spawns a real application BrowserWindow loading attacker content. On Electron 41.10.3 the
identical iframe trigger is blocked (fix confirmed). This escalates the R2 finding (where the main
window's setWindowOpenHandler capped the effect at an allowlisted external-URL open) to outcome
(a) of the ticket: a real window spawns.
- Product: hermes-agent desktop (
apps/desktop), repo commite3fab0437ee50ebe511cec57b9ac36f0c2803268 - Affected runtime: Electron 40.10.2 (pinned in the repo); fixed in Electron 41.10.3
- Any page rendered in the right-rail URL preview (attacker-selected URL, e.g. a link the agent
was asked to preview) can, without any user gesture, spawn real Electron
BrowserWindows loading attacker-controlled content — from the top-level guest document AND, on the vulnerable Electron, from a sandboxed iframe (sandbox="allow-scripts", noallow-popups) inside it. - Consequences: attacker-controlled application windows inside the Hermes desktop (phishing chrome, permission prompts, further drive-by surface) — beyond R2's browser-external-open cap.
Impact Parity
- Disclosed/claimed maximum impact: sandbox escape (guest content escapes the preview confinement into a real application window); the ticket asked to determine outcome (a) real window spawn / (b) external path / (c) blocked.
- Reproduced impact: outcome (a) — real
BrowserWindowcreation from the CVE bypass inside the preview webview guest (main-processbrowser-window-created+ guestdid-create-window+did-finish-loadof the attacker marker URL), twice on 40.10.2; blocked twice on 41.10.3. - Parity: full for the claimed escalation question. (No renderer code execution, Node integration, or permission escalation inside the spawned window was attempted or claimed here; the spawned windows use Electron default webPreferences.)
Root Cause
preview-pane.tsxcreates the preview webview for URL targets with a persistent partition and sandboxed renderer prefs, but nothing in the main process ever attaches asetWindowOpenHandler(orweb-contents-createdpolicy) to that guest. Electron's default in this case is to honor window-open requests by creating new windows.- Electron ≤ 40.10.2 (CVE-2026-70608): the OpenURLFromTab path omits the initiating-frame
popup-sandbox check, so a synthetic modifier-click on an anchor inside an iframe sandboxed
without
allow-popupsis still routed as aforeground-tabopen (the R2 primitive, revalidated here inside the webview guest). - Combined: the iframe sandbox bypass reaches the guest's window-open path and the guest has no handler to deny it → a real window is created. On 41.10.3 the popup-sandbox check is restored, so the iframe trigger is blocked before any window is created.
Fix upstream: Electron 41.10.3 (CVE-2026-70608 fix). Product-side defense in depth: attach a
setWindowOpenHandler (deny or route through openExternalUrl) to every webview guest via
app.on('web-contents-created') for type === 'webview'.
Reproduction Steps
bundle/repro/reproduction_steps.sh(self-contained; idempotent).- What it does:
- Seeds the work repo from the prepared project cache (or clones) and checks out
e3fab0437ee50ebe511cec57b9ac36f0c2803268. - Installs node deps with pnpm (workspace file +
pnpm importof the root package-lock; npm itself OOMs in this container's 1.5GB cgroup) and the Python backend venv the desktop spawns (venv/bin/python -m hermes_cli.main serve). - Applies observation-only instrumentation to
electron/main.ts(bundle/repro/patch_instrumentation.py, gated onHERMES_REPRO_INSTRUMENT_LOG): recordsbrowser-window-created,web-contents-created, guestdid-create-window, guest console,openExternalUrl,shell.openExternal(record-only stub — never executes a real OS open), and the main window'ssetWindowOpenHandler. Guest behavior is NOT modified. - Bundles the electron main process with the product's own
scripts/bundle-electron-main.mjs --dev, and bundles the SAME renderer sources with esbuild (bundle/repro/bundle_renderer.mjs; vite build/dev OOMs in the cgroup), served statically athttp://127.0.0.1:5174and loaded via the product's ownHERMES_DESKTOP_DEV_SERVERdev-mode entry. - Runs the Playwright driver (
bundle/repro/repro.spec.ts) under Xvfb, twice on Electron 40.10.2 and twice on 41.10.3. Each attempt: launches the real app with a real backend and a mock OpenAI provider; sends a chat message asking to previewhttp://127.0.0.1:<port>/x; the mock returns a realopen_previewtool call which the backend executes; the driver clicks the product's "Open Preview" affordance and, if needed, falls back to the product's own localStorage restore path (hermes.desktop.previewTabs.v2+ reload) to mount the pane. The preview-pane then creates the webview pointed at the attacker page, which fireswindow.openplus synthetic ctrl+meta-clicks from the top level and from a sandboxed iframe (sandbox="allow-scripts", noallow-popups).
- Seeds the work repo from the prepared project cache (or clones) and checks out
- Expected evidence: per-attempt
guest.did-create-window/browser-window-created/did-finish-loadevents for…/marker?src=frame-click(CVE bypass) on 40.10.2, and their absence (with the iframe beacon still dispatched) on 41.10.3.
Evidence
Per-attempt artifacts (two runs of the script, both confirmed=true):
bundle/logs/vulnerable_attempt_{1,2}.{json,main.jsonl,log,diag.log,requests.json}— Electron 40.10.2bundle/logs/fixed_attempt_{1,2}.{json,main.jsonl,log,diag.log,requests.json}— Electron 41.10.3bundle/repro/runtime_manifest.json— entrypoint, target identity, artifact hashesbundle/logs/reproduction_steps.log— full script log
Key excerpts (run 2, vulnerable attempt 1, Electron 40.10.2):
web-contents-created {type:'webview'} # preview guest mounted
guest-console: PRUVA src=page-loaded # attacker page ran in the guest
guest-console: PRUVA src=windowopen-result&value=null # gesture-less window.open blocked
guest.did-create-window url=…/marker?src=top-click disposition=foreground-tab
web-contents-created {type:'window'} + win.did-finish-load …/marker?src=top-click
guest-console: PRUVA-FRAME src=frame-windowopen-result&value=null # iframe window.open blocked
guest.did-create-window url=…/marker?src=frame-click disposition=foreground-tab # CVE bypass fires
web-contents-created {type:'window'} + win.did-finish-load …/marker?src=frame-click # REAL window
Fixed attempt 1 (Electron 41.10.3): the iframe still dispatches
(PRUVA-FRAME src=frame-click-dispatched beacon) but no did-create-window /
window for src=frame-click ever occurs; only the generic top-level clicks spawn windows
(unchanged pre-existing default-allow behavior for a handler-less guest, present on both versions).
Outcome matrix (both full script runs):
| attempt | Electron | outcome |
|---|---|---|
| vulnerable #1 | 40.10.2 | A_REAL_WINDOW_SPAWNED_CVE_BYPASS (cveWindows=1, genericWindows=2) |
| vulnerable #2 | 40.10.2 | A_REAL_WINDOW_SPAWNED_CVE_BYPASS (cveWindows=1, genericWindows=2) |
| fixed #1 | 41.10.3 | A_REAL_WINDOW_SPAWNED_GENERIC_ONLY (cveWindows=0, genericWindows=2) |
| fixed #2 | 41.10.3 | A_REAL_WINDOW_SPAWNED_GENERIC_ONLY (cveWindows=0, genericWindows=2) |
Environment: Ubuntu 26.04 container, x86_64, Xvfb, UID 1000, all traffic to 127.0.0.1,
shell.openExternal record-only. Chromium OS sandbox disabled (--no-sandbox) because the
container blocks all namespace creation (EPERM even for root); this matches the product's own e2e
fixtures and does not affect the Blink iframe popup-sandbox under test.
Recommendations / Next Steps
- Upgrade the desktop app to Electron ≥ 41.10.3 (upstream CVE-2026-70608 fix).
- Defense in depth (works regardless of Electron version): in the main process, attach
setWindowOpenHandlerto webview guests (app.on('web-contents-created'),type==='webview') that denies or routes through the allowlistedopenExternalUrl, mirroring the main window. - Consider a restrictive
session.setPermissionRequestHandler/ CSP forpersist:hermes-preview. - Test: an e2e spec asserting that a sandboxed iframe in a preview webview cannot create windows.
Additional Notes
- Idempotency: the script was run twice consecutively end-to-end; both runs printed
confirmed=truewith the same 2×2 outcome matrix. All setup steps skip when outputs exist. - The chat → tool-call → gateway path executed for real (mock provider issued a genuine
open_previewtool call the backend ran); the pane mount additionally used the product's own persisted-store restore path when the in-test click did not land (a test-harness timing issue, not a product defect). - The renderer is bundled by esbuild instead of vite purely because vite/rolldown exceeds the container's 1.5GB memory cgroup; identical sources and product code paths are exercised.
- Separately observed (both Electron versions, pre-existing, not the CVE): the top-level guest page's synthetic ctrl+click also spawns real windows (default allow, no handler). Worth its own product hardening note.
CVE-2026-70608 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
ls /pruva/project-cache/repo-mirrors/ /workspace/bundle/.pruva/ /workspace/bundle/repro/ /workspace/bundle/logs/ 2>&1; cat /pruva/project-cache/.pruva/cache_manifest.jsonEXPLOIT-KNOWLEDGE PREFLIGHT REVIEW REQUIRED: `run_shell` is blocked until this agent calls `review_exploit_knowledge_preflight` with snapshot_sha256 `26c785772498c34b82f80fbd40e20b16ac9643b5629915661dd4572dbe0f6b41` and exactly one disposition for each candidate ID [0de42017-cb45-40d5-bb37-e860181bc9f2, 8eb87bb6-23a3-4b2b-8c2f-6ff9e6755968, acbcc6e9-f172-4c35-b731-927485e84dc1]. Historical records remain reference-only; review their typed mechanisms and preconditions before continuing.
Unknown error
ls -la /pruva/project-cache/repo-mirrors/ /workspace/bundle/repro/ /workspace/bundle/logs/ /workspace/bundle/logs/repro/ 2>&1; echo ---; cat /pruva/project-cache/.pruva/cache_manifest.json; echo ---; cat /workspace/bundle/artifact_promotion_manifest.json /workspace/bundle/artifact_promotion_report.json 2>/dev/null{"cwd":"/workspace","duration_ms":31,"exit_code":0,"stderr":"","stdout":"/pruva/project-cache/repo-mirrors/:\ntotal 0\ndrwxr-xr-x 2 vscode vscode 40 Aug 17 15:10 .\ndrwxr-xr-x 4 vscode vscode 80 Aug 17 15:10 ..\n\n/workspace/bundle/logs/:\ntotal 0\ndrwxr-xr-x 5 vscode vscode 100 Aug 17 15:12 .\ndrwxr-xr-x 8 vscode vscode 260 Aug 17 15:12 ..\ndrwxr-xr-x 2 vscode vscode 40 Aug 17 15:12 claim_contract\ndrwxr-xr-x 2 vscode vscode 40 Aug 17 15:12 repro\ndrwxr-xr-x 2 vscode vscode 40 Aug 17 15:11 support\n\n/workspace/bundle/logs/repro/:\ntotal 0\ndrwxr-xr-x 2 vscode vscode 40 Aug 17 15:12 .\ndrwxr-xr-x 5 vscode vscode 100 Aug 17 15:12 ..\n\n/workspace/bundle/repro/:\ntotal 0\ndrwxr-xr-x 2 vscode vscode 40 Aug 17 15:12 .\ndrwxr-xr-x 8 vscode vscode 260 Aug 17 15:12 ..\n---\n{\n \"schema_version\": 1,\n \"entries\": [\n {\n \"path\": \"repo-mirrors\",\n \"reuse_class\": \"repo\",\n \"provenance\": \"run_generated\"\n }\n ]\n}---\n{\n \"schema_version\": 1,\n \"run_id\": \"61cb8db7-c1c6-43ee-8a76-d4ebdc9eb6fb\",\n \"updated_at\": \"2026-08-17T15:12:04.206964409Z\",\n \"entries\": [\n {\n \"logical_path\": \"claim_contract/report.json\",\n \"sta… [truncated]node --version; npm --version; which xvfb-run Xvfb; python3 --version; id; cat /etc/os-release | head -3; df -h /pruva/project-cache | tail -1v24.18.0 11.16.0 Python 3.14.4 uid=1000(vscode) gid=1000(vscode) groups=1000(vscode) PRETTY_NAME="Ubuntu 26.04 LTS" NAME="Ubuntu" VERSION_ID="26.04" tmpfs 1.5G 4.0K 1.5G 1% /pruva/project-cache
which Xvfb xvfb-run; ls /usr/bin | grep -i xvfb; dpkg -l | grep -i xvfb | headArtifacts and Evidence for CVE-2026-70608
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-70608
Upgrade hermes-agent · github to 41.10.3 or later.
FAQ: CVE-2026-70608
Is CVE-2026-70608 exploitable?
How severe is CVE-2026-70608?
What type of vulnerability is CVE-2026-70608?
Which versions of hermes-agent are affected by CVE-2026-70608?
Is there a fix for CVE-2026-70608?
How can I reproduce CVE-2026-70608?
Is the CVE-2026-70608 reproduction verified?
References for CVE-2026-70608
Authoritative sources for CVE-2026-70608 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.