{
  "variant_id": "ovsx-cve-2026-13323-icon-smuggle-v1",
  "created_at": "2026-07-02",
  "variant_summary": "Alternate trigger of CVE-2026-13323: an attacker smuggles an HTML file as the extension ICON by setting \"icon\": \"payload.html\" in package.json. ExtensionProcessor.getIcon() has no icon-type validation, so the HTML is stored as the ICON FileResource and served via a different entry point (GET /api/{ns}/{ext}/{ver}/file/{iconName}) and a different sink (LocalStorageService.getFile(FileResource) -> getFileResponseHeaders -> StorageUtil.getFileType) than the original /vscode/unpkg/ repro. On v1.0.1 it is served as text/html with no CSP and no Content-Disposition (inline JS execution in the registry origin); the icon URL is advertised in the extension metadata files.icon. On v1.0.2 the fix rewires this sink to HttpHeadersUtil.createFileResponseHeaders (Tika -> text/plain + strict CSP), so the variant is an alternate trigger on the vulnerable version only, NOT a bypass.",
  "relation": "newer_version_sibling",
  "origin_kind": "pruva_variant",
  "repository": "eclipse-openvsx/openvsx",
  "submitted_target": {
    "target_kind": "git_tag",
    "commit_sha": "e92a1a7a448be08570cc4c4969717ed3e2260015",
    "version": "v1.0.1",
    "ref": "v1.0.1",
    "display": "eclipse-openvsx/openvsx@v1.0.1 (vulnerable)"
  },
  "variant_target": {
    "target_kind": "git_tag",
    "commit_sha": "e92a1a7a448be08570cc4c4969717ed3e2260015",
    "version": "v1.0.1",
    "ref": "v1.0.1",
    "display": "eclipse-openvsx/openvsx@v1.0.1 (alternate trigger reproduces here)"
  },
  "same_root_cause_confidence": "high",
  "same_surface_confidence": "medium",
  "claimed_surface": "api_remote",
  "validated_surface": "api_remote",
  "required_entrypoint_kind": "api_remote",
  "required_entrypoint_detail": "HTTP GET /api/{namespace}/{extension}/{version}/file/{iconName} -> RegistryAPI.getFile -> LocalRegistryService.getFile -> StorageUtilService.getFileResponse(FileResource) -> LocalStorageService.getFile(FileResource). The iconName is attacker-controlled via package.json \"icon\" pointing at an HTML file inside the VSIX; the icon URL is advertised in GET /api/{ns}/{ext}/{ver} metadata as files.icon.",
  "attacker_controlled_input": "VSIX package whose package.json sets \"icon\": \"payload.html\" and that contains extension/payload.html (an HTML document with an embedded <script> that reads document.cookie). Published via POST /api/-/publish with a publisher PAT. ExtensionProcessor.getIcon() stores payload.html as the ICON FileResource with no type validation.",
  "trigger_path": "POST /api/-/publish?token={pat} (VSIX with icon=payload.html) -> ExtensionProcessor.getIcon stores ICON FileResource named payload.html -> GET /api/{ns}/{ext}/{ver}/file/payload.html (advertised as files.icon) -> LocalRegistryService.getFile -> StorageUtilService.getFileResponse(FileResource) -> LocalStorageService.getFile -> getFileResponseHeaders(name) -> StorageUtil.getFileType('payload.html') -> text/html, no CSP, no Content-Disposition",
  "observed_impact_class": "info_leak",
  "exploitability_confidence": "high",
  "evidence_scope": "production_path",
  "runtime_manifest_present": true,
  "end_to_end_target_reached": true,
  "inferred": false,
  "claim_block_reason": null,
  "blocking_mitigation": "v1.0.2 fix (commit 9491f32a6d459a4d499c5028d37c0d0386771e9f) rewires LocalStorageService.getFile(FileResource) to HttpHeadersUtil.createFileResponseHeaders(path): Apache Tika detects the HTML content as text/html, which is in TEXT_VIEWABLE_MEDIA_TYPES, so Content-Type is forced to text/plain;charset=utf-8 and a strict CSP (default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; sandbox) plus nosniff and X-Frame-Options: DENY are always set. Verified on the running v1.0.2 server: the variant URL returns text/plain + CSP (INLINE_HTML=0). The variant therefore does NOT reproduce on the fixed version (not a bypass).",
  "file_path": "server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java",
  "line_start": 223,
  "line_end": 233,
  "secondary_anchors": [
    {
      "file_path": "server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java",
      "line_start": 506,
      "line_end": 526
    },
    {
      "file_path": "server/src/main/java/org/eclipse/openvsx/storage/LocalStorageService.java",
      "line_start": 89,
      "line_end": 99
    },
    {
      "file_path": "server/src/main/java/org/eclipse/openvsx/storage/LocalStorageService.java",
      "line_start": 124,
      "line_end": 133
    },
    {
      "file_path": "server/src/main/java/org/eclipse/openvsx/storage/StorageUtil.java",
      "line_start": 23,
      "line_end": 32
    },
    {
      "file_path": "server/src/main/java/org/eclipse/openvsx/RegistryAPI.java",
      "line_start": 611,
      "line_end": 625
    },
    {
      "file_path": "server/src/main/java/org/eclipse/openvsx/util/HttpHeadersUtil.java",
      "line_start": 102,
      "line_end": 155
    },
    {
      "file_path": "server/src/main/java/org/eclipse/openvsx/storage/LocalStorageService.java",
      "line_start": 92,
      "line_end": 92
    }
  ],
  "review_scope_paths": [
    "server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java",
    "server/src/main/java/org/eclipse/openvsx/RegistryAPI.java",
    "server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java",
    "server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java",
    "server/src/main/java/org/eclipse/openvsx/storage/LocalStorageService.java",
    "server/src/main/java/org/eclipse/openvsx/storage/StorageUtil.java",
    "server/src/main/java/org/eclipse/openvsx/storage/AwsStorageService.java",
    "server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java",
    "server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java",
    "server/src/main/java/org/eclipse/openvsx/adapter/LocalVSCodeService.java",
    "server/src/main/java/org/eclipse/openvsx/adapter/UpstreamVSCodeService.java",
    "server/src/main/java/org/eclipse/openvsx/util/HttpHeadersUtil.java",
    "server/src/main/java/org/eclipse/openvsx/UserService.java"
  ],
  "artifact_refs": {
    "variant_manifest": "bundle/vuln_variant/variant_manifest.json",
    "validation_verdict": "bundle/vuln_variant/validation_verdict.json",
    "runtime_manifest": "bundle/vuln_variant/runtime_manifest.json",
    "repro_log": "bundle/logs/vuln_variant/variant_repro.log",
    "root_cause_equivalence": "bundle/vuln_variant/root_cause_equivalence.json",
    "source_identity": "bundle/vuln_variant/source_identity.json",
    "reproducer": [
      "bundle/vuln_variant/reproduction_steps.sh",
      "bundle/vuln_variant/create_variant_vsix.py",
      "bundle/vuln_variant/write_verdict.py"
    ]
  }
}
