Skip to content

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.

REPRO-2026-00326 hermes-agent · github Aug 23, 2026 CVE entry .txt
Severity
HIGH
Confidence
HIGH
Reproduced in
271m 8s
Tool calls
541
Spend
$31.18
01 · Overview

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).

02 · Severity & CVSS

CVE-2026-70608 Severity

CVE-2026-70608 is rated high severity.

HIGH threat level
Weakness CWE-693

High — serious impact or readily exploitable. Prioritize remediation.

03 · Affected Versions

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
or 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
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-70608

sandbox escape — reproduced
  • 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
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

Attack chain
  1. chat open_preview tool / preview restore
  2. preview-pane.tsx <webview partition=persist:hermes-preview> guest
  3. CVE-2026-70608 OpenURL bypass
  4. guest did-create-window
  5. real BrowserWindow (no setWindowOpenHandler on the guest)
Runnable proof: reproduction_steps.sh
Captured evidence: fixed attempt 1fixed attempt 2vulnerable attempt 1vulnerable attempt 2
How the agent worked 1,291 events · 541 tool calls · 4h 31m
4h 31mDuration
541Tool calls
237Reasoning steps
1,291Events
18Dead-ends
Agent activity over 4h 31m
Policy
1
Support
8
Repro
1,230
Judge
47
Verify
1
0:00270:58

Root Cause and Exploit Chain for CVE-2026-70608

Versions: runtime: Electron 40.10.2 (pinned in the repo); fixed in Electron 41.10.3

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 commit e3fab0437ee50ebe511cec57b9ac36f0c2803268
  • 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", no allow-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 BrowserWindow creation from the CVE bypass inside the preview webview guest (main-process browser-window-created + guest did-create-window + did-finish-load of 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

  1. preview-pane.tsx creates the preview webview for URL targets with a persistent partition and sandboxed renderer prefs, but nothing in the main process ever attaches a setWindowOpenHandler (or web-contents-created policy) to that guest. Electron's default in this case is to honor window-open requests by creating new windows.
  2. 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-popups is still routed as a foreground-tab open (the R2 primitive, revalidated here inside the webview guest).
  3. 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

  1. bundle/repro/reproduction_steps.sh (self-contained; idempotent).
  2. 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 import of 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 on HERMES_REPRO_INSTRUMENT_LOG): records browser-window-created, web-contents-created, guest did-create-window, guest console, openExternalUrl, shell.openExternal (record-only stub — never executes a real OS open), and the main window's setWindowOpenHandler. 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 at http://127.0.0.1:5174 and loaded via the product's own HERMES_DESKTOP_DEV_SERVER dev-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 preview http://127.0.0.1:<port>/x; the mock returns a real open_preview tool 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 fires window.open plus synthetic ctrl+meta-clicks from the top level and from a sandboxed iframe (sandbox="allow-scripts", no allow-popups).
  3. Expected evidence: per-attempt guest.did-create-window / browser-window-created / did-finish-load events 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.2
  • bundle/logs/fixed_attempt_{1,2}.{json,main.jsonl,log,diag.log,requests.json} — Electron 41.10.3
  • bundle/repro/runtime_manifest.json — entrypoint, target identity, artifact hashes
  • bundle/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 setWindowOpenHandler to webview guests (app.on('web-contents-created'), type==='webview') that denies or routes through the allowlisted openExternalUrl, mirroring the main window.
  • Consider a restrictive session.setPermissionRequestHandler / CSP for persist: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=true with 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_preview tool 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.

Event 1/40
0:001:57
0:00
session startedaccounts/fireworks/models/kimi-k3 · ARGUS-SCA-76dae554b30f40dd9193b77ffbd602b9-R3B · REPRO-20
0:14
0:15
web search
0:53
0:53
extract_facts
no facts extracted
0:55
0:55
supportclaim_contract
1:01
1:01
1:01
1:05
1:05
1:05
1:08
1:08
1:13
1:13
$ls /pruva/project-cache/repo-mirrors/ /workspace/bundle/.pruva/ /workspace/bundle/repro/ /workspace/bundle/logs/ 2>&1; cat /pruva/project-cache/.pruva/cache_manifest.json
0.2s
EXPLOIT-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.
1:13
error

Unknown error

1:26
1:26
1:30
$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
0.2s
{"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]
1:43
1:43
$node --version; npm --version; which xvfb-run Xvfb; python3 --version; id; cat /etc/os-release | head -3; df -h /pruva/project-cache | tail -1
0.4s
v24.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
1:44
1:49
1:49
$which Xvfb xvfb-run; ls /usr/bin | grep -i xvfb; dpkg -l | grep -i xvfb | head
0.2s
1:57
08 · How to Fix

How to Fix CVE-2026-70608

Upgrade hermes-agent · github to 41.10.3 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-70608 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-70608

Is CVE-2026-70608 exploitable?

Yes. Pruva independently reproduced CVE-2026-70608 in hermes-agent and verified the exploit fires end-to-end in a sandboxed environment. A runnable proof-of-concept script and the full agent transcript are on this page (reproduction REPRO-2026-00326).

How severe is CVE-2026-70608?

CVE-2026-70608 is rated high severity.

What type of vulnerability is CVE-2026-70608?

CVE-2026-70608 is classified as CWE-693.

Which versions of hermes-agent are affected by CVE-2026-70608?

hermes-agent electron <39.8.10, >=40.0.0-alpha.1 <41.10.3, >=42.0.0-alpha.1 <42.0.1 is affected by CVE-2026-70608.

Is there a fix for CVE-2026-70608?

Yes. CVE-2026-70608 is fixed in hermes-agent 41.10.3. Upgrading to the fixed version remediates the issue.

How can I reproduce CVE-2026-70608?

Pruva provides a verified reproduction script on this page. Download it and run it inside an isolated environment such as a container or virtual machine — never against production. The reproduction was confirmed end-to-end by Pruva's automated agents.

Is the CVE-2026-70608 reproduction verified?

Yes. Pruva reproduced CVE-2026-70608 with high confidence in a sandboxed environment, capturing the full agent transcript and artifacts as evidence.
11 · References

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.