# JetBrains Marketplace API notes

Reference notes for `scanners/lib/vsix-fetch.mjs` and `scanners/lib/jetbrains-fetch-worker.mjs`.
These endpoints are used to download JetBrains plugin archives for
`/security ide-scan <url>` (v6.6.0).

## Base URL

```
https://plugins.jetbrains.com/
```

## Metadata lookup

```
GET https://plugins.jetbrains.com/api/plugins/{numericId}
GET https://plugins.jetbrains.com/api/plugins/{numericId}/updates?size=N
```

- `{numericId}` is an integer assigned by JetBrains when the plugin is first
  published. It is the first path segment (before the dash) in the
  user-facing URL — e.g. `https://plugins.jetbrains.com/plugin/7973-intellivue`
  → numericId `7973`, slug `intellivue`.
- `GET /api/plugins/{numericId}` returns plugin metadata including
  `xmlId` (the `<id>` from `plugin.xml`), `name`, `vendor.name`,
  `vendor.isVerified`, `programmingLanguage`, `urls`. We use this only when
  `--online` is set, and never trust `vendor.isVerified` as a safety signal —
  it attests publisher identity, not plugin safety (see OX Security 2025).
- `GET /api/plugins/{numericId}/updates?size=N` returns the last N versions
  with `id`, `version`, `cdate` (unix millis), `file` (CDN path), and
  `recommended`. We use this to resolve "latest stable" without trusting
  the plugin page HTML.

## Direct download

Two equivalent URL shapes, both officially supported:

```
GET https://plugins.jetbrains.com/pluginManager?action=download&id={xmlId}&build={productCode}-{buildNumber}
GET https://plugins.jetbrains.com/plugin/download?pluginId={xmlId}&version={v}
```

- `action=download` is what the IntelliJ IDE itself uses via its plugin manager.
  `{productCode}` is `IC` (IDEA Community), `IU` (IDEA Ultimate), `PY`, `PS`, `GO`,
  `RD`, `CL`, `WS`, `RM`, `DB`, etc. `{buildNumber}` is a short build string like
  `241.18034.62`. When both are present, the server returns the compatible
  version; otherwise it returns a 400.
- `plugin/download?pluginId={xmlId}&version={v}` is stable and simpler — we
  prefer this shape when the caller passes `?version=`. If no version is given
  we resolve `latest` via the `updates` endpoint first, then request that
  version explicitly.
- The response is a ZIP (or in rare legacy cases a JAR). `Content-Type` is
  `application/octet-stream` or `application/java-archive`. The fetcher
  tolerates both.

## Redirect host whitelist

The JetBrains CDN reroutes downloads. Our fetcher (`vsix-fetch.mjs`) accepts
redirects only to:

- `plugins.jetbrains.com`
- `downloads.marketplace.jetbrains.com`
- `cache-redirector.jetbrains.com`

Any other host aborts with `Error: redirect not allowed: <host>` and the worker
exits non-zero. This matches the VS Code Marketplace redirect policy in
`marketplace-api-notes.md`.

## URL-to-numericId resolution

`detectUrlType` accepts three JetBrains URL shapes:

```
https://plugins.jetbrains.com/plugin/{numericId}-{slug}
https://plugins.jetbrains.com/plugin/{numericId}-{slug}/versions/{version}
https://plugins.jetbrains.com/pluginManager?action=download&id={xmlId}
https://plugins.jetbrains.com/plugin/download?pluginId={xmlId}
```

For the first two, we split the first path segment on `-` and take the
leading integer as `numericId`. For the `action=download` / `download?pluginId`
forms we pass the URL through directly.

## Rate limits

JetBrains publishes no explicit rate limit. Observed in the field: low-tens of
requests per minute per IP is safe. Our default is 1 req/s (conservative) — we
issue at most three requests per URL scan (metadata + updates + download) so
rate budget is generous.

## Caps & defenses (shared with VS Code fetcher)

- TLS verification enabled (no `--insecure` opt-in).
- HTTPS only. Plain HTTP is rejected at `detectUrlType` and at fetch time.
- Manual redirect handling. Allowed hosts whitelisted per source type.
- 30-second total timeout via `AbortController`.
- 50MB compressed archive cap. Streaming reader aborts when cap exceeded.
- SHA-256 computed during streaming for `meta.source.sha256`.

## What is NOT supported (v6.6.0)

- No equivalent of OpenVSX — JetBrains Marketplace is the only distribution
  channel, so URL scans fall through to direct-URL VSIX rules if the caller
  passes a raw `.zip` or `.jar` URL on any other host.
- Custom plugin repositories (`updatePlugins.xml` feeds on self-hosted servers)
  are out of scope. A target like `https://my-repo.example.com/updatePlugins.xml`
  is not a plugin archive and will fail `detectUrlType`.
- Archive signature verification — JetBrains ships plugins unsigned by default;
  the Marketplace signs metadata at publish time but the ZIP itself carries no
  embedded signature we can verify offline.

## References

- JetBrains plugin-content — https://plugins.jetbrains.com/docs/intellij/plugin-content.html
- JetBrains plugin-configuration-file — https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html
- JetBrains Marketplace REST (informal) — https://plugins.jetbrains.com/docs/marketplace/api.html
- YouTrack IJPL-212393 — signature-warning inconsistency
- OX Security 2025 — JetBrains verified-badge bypass
