CVE-2026-4983: Verified Reproduction
CVE-2026-4983: Open VSX Registry serves HTML inline enabling session/token exfiltration
CVE-2026-4983 is verified against the affected target. Vulnerability class: XSS. This medium reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00202.
What Is CVE-2026-4983?
CVE-2026-4983 is a medium-severity stored cross-site scripting vulnerability (CWE-79) in the Open VSX Registry, where the /vscode/unpkg/ endpoint serves user-supplied HTML/SVG files from VSIX extension packages inline, enabling session/token exfiltration. Pruva reproduced it (reproduction REPRO-2026-00202).
CVE-2026-4983 Severity & CVSS Score
CVE-2026-4983 is rated medium severity, with a CVSS base score of 4.1 out of 10.
Medium — meaningful risk under specific conditions. Schedule a fix in the normal cycle.
How to Reproduce CVE-2026-4983
pruva-verify REPRO-2026-00202 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00202/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-4983
- reached the target end-to-end
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
VSIX package containing an HTML file with embedded JavaScript, published via POST /api/-/publish, then accessed via GET /vscode/unpkg/{ns}/{ext}/{ver}/extension/payload.html
- POST /api/-/publish?token={token} (upload VSIX with HTML)
- GET /vscode/unpkg/{namespace}/{extension}/{version}/extension/payload.html (served with text/html, no CSP)
How the agent worked
Root Cause and Exploit Chain for CVE-2026-4983
The Open VSX Registry /vscode/unpkg/ endpoint serves user-supplied files extracted from published VSIX packages with insecure HTTP response headers. In versions before 1.0.2, HTML files (.html) are served with Content-Type: text/html and without a Content-Security-Policy or Content-Disposition: attachment header, causing browsers to render the HTML inline in the registry's origin context. This enables an unauthenticated attacker to upload a VSIX containing a crafted HTML payload with JavaScript that executes in the open-vsx.org origin, allowing session token exfiltration, persistent PAT generation, and unauthorized publication of malicious extension versions.
- Package/component affected:
org.eclipse.openvsx:openvsx-server— specifically the/vscode/unpkg/{namespace}/{extension}/{version}/{path}endpoint inLocalVSCodeServiceand the file-serving logic inStorageUtilService/StorageUtil - Affected versions: All versions before 1.0.2 (confirmed on v1.0.1)
- Risk level: Medium (CVSS advisory severity)
- Consequences:
- JavaScript execution in the
open-vsx.orgorigin context - Session cookie/token exfiltration from authenticated users
- Persistent Personal Access Token (PAT) generation
- Unauthorized publication of malicious extension versions
- Supply chain attack via compromised extension updates distributed to VS Code, VSCodium, Cursor, Windsurf, and compatible editors
- JavaScript execution in the
Impact Parity
- Disclosed/claimed maximum impact: Session/token exfiltration, persistent PAT generation, unauthorized publication of malicious extension versions, supply chain attack
- Reproduced impact from this run: Confirmed that the
/vscode/unpkg/endpoint serves user-controlled HTML files withContent-Type: text/htmland noContent-Security-Policyheader, which would cause a browser to render the HTML inline and execute embedded JavaScript in the registry origin. The response body contains a<script>tag that accessesdocument.cookieand would execute in the browser. - Parity:
full— the HTTP response header behavior that enables the attack is fully reproduced against the real running server. The script does not render the HTML in a browser (no headless browser), but the header evidence proves the browser would render it inline with JavaScript execution capability. - Not demonstrated: Actual browser-side JavaScript execution and token exfiltration (requires a browser environment and an authenticated victim session). The HTTP-level evidence is sufficient to prove the vulnerability.
Root Cause
The vulnerability exists in the file-serving code path of the Open VSX Registry server. When a user requests a file from a VSIX package via /vscode/unpkg/{namespace}/{extension}/{version}/{path}:
LocalVSCodeService.browse()handles the request and callsgetWebResource()to extract the requested file from the VSIX ZIP archive to a temporary path.The extracted file is served via
StorageUtilService.getFileResponse(Path path), which sets response headers usingStorageUtil.getFileType(fileName).The vulnerable code (
StorageUtil.getFileTypein v1.0.1):static MediaType getFileType(String fileName) { if (fileName.endsWith(".vsix")) return MediaType.APPLICATION_OCTET_STREAM; if (fileName.endsWith(".json")) return MediaType.APPLICATION_JSON; if (fileName.endsWith(".sigzip")) return MediaType.valueOf("application/zip"); var contentType = URLConnection.guessContentTypeFromName(fileName); if (contentType != null) return MediaType.parseMediaType(contentType); return MediaType.TEXT_PLAIN; }For
.htmlfiles,URLConnection.guessContentTypeFromName("payload.html")returnstext/html.The vulnerable response headers (
StorageUtilService.getFileResponse(Path)in v1.0.1):public ResponseEntity<StreamingResponseBody> getFileResponse(Path path) { var fileName = path.getFileName().toString(); var headers = new HttpHeaders(); headers.setContentType(StorageUtil.getFileType(fileName)); // → text/html headers.setCacheControl(StorageUtil.getCacheControl(fileName)); return ResponseEntity.ok().headers(headers).body(...); }No
Content-Security-Policyheader is set. NoContent-Disposition: attachmentheader is set for non-.vsixfiles. The browser therefore renders the HTML inline and executes any embedded JavaScript.The fix (v1.0.2) introduces
HttpHeadersUtil.createFileResponseHeaders()which:- Uses Apache Tika to detect the actual MIME type from file content
- For text-viewable types (including
text/html), forcesContent-Type: text/plain;charset=UTF-8to prevent HTML rendering - Sets
Content-Disposition: attachmentfor non-text files to force download - Always sets a strict
Content-Security-Policy: default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; sandbox - Sets
X-Content-Type-Options: nosniffandX-Frame-Options: DENY
Fix commit: The fix is included in tag v1.0.2 (commit 9491f32a6d459a4d499c5028d37c0d0386771e9f). The key changes are in:
server/src/main/java/org/eclipse/openvsx/util/HttpHeadersUtil.java(newcreateFileResponseHeadersmethod)server/src/main/java/org/eclipse/openvsx/storage/StorageUtil.java(removed vulnerablegetFileTypemethod)server/src/main/java/org/eclipse/openvsx/storage/LocalStorageService.java(uses new header util)server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java(uses new header util)
Reproduction Steps
- Reference:
bundle/repro/reproduction_steps.sh - What the script does:
- Creates a minimal VSIX package containing
extension/payload.htmlwith a<script>tag that accessesdocument.cookie - Builds the Open VSX Registry server from source at v1.0.1 (vulnerable) and v1.0.2 (fixed) using Docker (
gradle:jdk-25-and-25for building,eclipse-temurin:25-jdkfor running) - Starts PostgreSQL 16.2 in Docker
- For each version: starts the server, inserts a user and personal access token into PostgreSQL, creates a namespace via the API, publishes the VSIX via
POST /api/-/publish?token=test_token, then requests the HTML file viaGET /vscode/unpkg/testpub/testext/1.0.0/extension/payload.html - Captures and compares the HTTP response headers between vulnerable and fixed versions
- Creates a minimal VSIX package containing
- Expected evidence:
- Vulnerable (v1.0.1):
Content-Type: text/html, noContent-Security-Policy, noContent-Disposition: attachment - Fixed (v1.0.2):
Content-Type: text/plain;charset=utf-8,Content-Security-Policy: default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; sandbox
- Vulnerable (v1.0.1):
Evidence
Log file locations
bundle/logs/vuln_v1.0.1_headers.txt— HTTP response headers from vulnerable serverbundle/logs/vuln_v1.0.1_body.html— HTML payload served by vulnerable serverbundle/logs/fixed_v1.0.2_headers.txt— HTTP response headers from fixed serverbundle/logs/fixed_v1.0.2_body.html— HTML content served by fixed server (as text/plain)bundle/logs/header_comparison.txt— Side-by-side header comparisonbundle/logs/reproduction_verdict.txt— Final verdict summarybundle/logs/server_vuln_v1.0.1.log— Vulnerable server startup logbundle/logs/server_fixed_v1.0.2.log— Fixed server startup logbundle/logs/build_v1.0.1.log/bundle/logs/build_v1.0.2.log— Build logsbundle/repro/runtime_manifest.json— Runtime evidence manifest
Key excerpts
Vulnerable (v1.0.1) response headers — serves HTML inline:
HTTP/1.1 200 OK
Content-Type: text/html
Cache-Control: max-age=2592000, public
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
X-Frame-Options: DENY
Content-Type: text/html→ browser renders HTML- No
Content-Security-Policy→ JavaScript executes freely - No
Content-Disposition: attachment→ file rendered inline, not downloaded
Fixed (v1.0.2) response headers — safe serving:
HTTP/1.1 200 OK
Content-Type: text/plain;charset=utf-8
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Cache-Control: max-age=86400, must-revalidate, public
Content-Security-Policy: default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; sandbox
X-XSS-Protection: 0
Content-Type: text/plain;charset=utf-8→ browser displays as plain textContent-Security-Policy: default-src 'none'; ...→ blocks all script execution, network requests, framing
Environment details
- Server: Open VSX Registry built from source (eclipse-openvsx/openvsx), Spring Boot with embedded Jetty
- JDK: Eclipse Temurin 25 (JDK 25)
- Database: PostgreSQL 16.2
- Build tool: Gradle 9.5.1
- Docker: Used for all components (build, database, runtime)
Recommendations / Next Steps
Upgrade to v1.0.2 or later — The fix adds comprehensive response header hardening:
- Content-type sniffing via Apache Tika (not just file extension)
- Forced
text/plainfor all text-viewable types (HTML, CSS, JS, Markdown) - Strict CSP on all served files
Content-Disposition: attachmentfor non-text filesX-Content-Type-Options: nosniffandX-Frame-Options: DENY
Audit existing extensions — Check for previously published extensions containing HTML files that may have been used for exploitation.
Consider additional mitigations:
- Add
Content-Disposition: attachmenteven for text-viewable files to force download - Implement a content allowlist for file types that can be served via
/vscode/unpkg/ - Consider serving user-uploaded content from a separate origin (e.g.,
files.open-vsx.org) to isolate from the registry origin
- Add
Testing recommendation — Add integration tests that verify response headers for various file types (
.html,.svg,.js,.css) served via the/vscode/unpkg/endpoint.
Additional Notes
- Idempotency: The reproduction script is fully self-contained and idempotent. It creates and tears down all Docker containers. Running it again produces the same results. The script cleans up all containers on exit via a trap handler.
- Negative control: The script tests both the vulnerable (v1.0.1) and fixed (v1.0.2) versions, demonstrating that the fix changes the response headers from dangerous (
text/html, no CSP) to safe (text/plain, strict CSP). - Real product proof: The reproduction uses the actual Open VSX Registry server built from source, running with a real PostgreSQL database. The extension is published through the real API (
POST /api/-/publish) and the file is served through the real/vscode/unpkg/endpoint — not a mock or harness. - Scanning disabled: The test config disables extension scanning (
ovsx.scanning.enabled: false) to allow the test VSIX to be published without being blocked by security checks. In production, scanning is enabled but does not prevent HTML files from being included in VSIX packages.
CVE-2026-4983 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.
Artifacts and Evidence for CVE-2026-4983
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-4983
FAQ: CVE-2026-4983
How does the CVE-2026-4983 stored XSS attack work?
How severe is CVE-2026-4983?
How can I reproduce CVE-2026-4983?
References for CVE-2026-4983
Authoritative sources for CVE-2026-4983 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.