118 lines
5 KiB
Markdown
118 lines
5 KiB
Markdown
# 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
|