docs(llm-security): research brief + URL-support plan for ide-scan
- jetbrains-research-brief.md: 29 sources, confidence 0.88 — input for v6.6.0 JetBrains/IntelliJ extension scanning. Covers plugin format, install paths per OS+product, Marketplace API, threat landscape (zero confirmed-malicious cases), check-mapping table, sandbox reuse verdict, risk register. - ide-scan-url-support.md: retroactive plan doc for v6.4.0 URL-fetch feature.
This commit is contained in:
parent
9ecd66929c
commit
fb9eb79d17
2 changed files with 442 additions and 0 deletions
140
plugins/llm-security/docs/plans/ide-scan-url-support.md
Normal file
140
plugins/llm-security/docs/plans/ide-scan-url-support.md
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
# Plan: `/security ide-scan <url>` — ekstern URL-support (v6.4.0)
|
||||
|
||||
**Status:** Planlagt
|
||||
**Mål-release:** v6.4.0
|
||||
**Skrevet:** 2026-04-17
|
||||
**Motivasjon:** Hovedbruk av `ide-scan` er pre-installasjonsverifisering — sjekk en extension FØR du installerer den. Dagens scanner krever at extension allerede er installert (`~/.vscode/extensions/`) eller at target peker til en allerede utpakket mappe.
|
||||
|
||||
## Støttede URL-typer
|
||||
|
||||
| Type | Eksempel | Fetch-strategi |
|
||||
|------|----------|----------------|
|
||||
| VS Code Marketplace | `https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code` | POST til `/_apis/public/gallery/.../vspackage` (undokumentert men stabilt mønster) |
|
||||
| OpenVSX | `https://open-vsx.org/extension/anthropic/claude-code` | Offentlig API: `/api/{pub}/{name}/{version}/file/{pub}.{name}-{version}.vsix` |
|
||||
| Direkte .vsix | `https://example.com/ext.vsix` | Enkel GET |
|
||||
| GitHub repo | `https://github.com/anthropic/claude-code` | Bygg fra source? (se "Åpen beslutning") |
|
||||
|
||||
## Sikkerhetsmodell
|
||||
|
||||
VSIX er en ZIP-fil. Extraction er den største angrepsflaten — vi må forhindre:
|
||||
- **Zip-slip** (`../../etc/passwd` i filnavn)
|
||||
- **Symlink-angrep** (VSIX-spec tillater ikke symlinks, men parser må avvise dem)
|
||||
- **Zip-bomber** (10MB komprimert → 100GB ukomprimert)
|
||||
- **Path traversal** via absolutte paths i entry-navn
|
||||
- **Unicode-normalization-angrep** (NFC/NFD-forskjeller som omgår path-checks)
|
||||
|
||||
**Caps:**
|
||||
- Max komprimert størrelse: 50MB (VSIX over dette er mistenkelig)
|
||||
- Max ukomprimert størrelse: 500MB
|
||||
- Max entries: 10 000
|
||||
- Max depth: 20
|
||||
- Max expansion ratio: 100x (sum ukomprimert / sum komprimert)
|
||||
|
||||
## Arkitektur
|
||||
|
||||
```
|
||||
/security ide-scan <url>
|
||||
→ commands/ide-scan.md (dispatcher)
|
||||
→ bin/llm-security.mjs eller direkte
|
||||
→ scanners/ide-extension-scanner.mjs
|
||||
├─ url-detect: pattern-match for supported URL typer
|
||||
├─ lib/vsix-fetch.mjs (NY)
|
||||
│ ├─ detectUrlType(url) → 'marketplace' | 'openvsx' | 'vsix' | 'github'
|
||||
│ ├─ fetchMarketplaceVsix(publisher, name)
|
||||
│ ├─ fetchOpenVsxVsix(publisher, name, version)
|
||||
│ ├─ fetchDirectVsix(url)
|
||||
│ └─ returnerer { buffer, url, contentType, size, sha256 }
|
||||
├─ lib/zip-extract.mjs (NY)
|
||||
│ ├─ zero-dep ZIP-parser (central directory + local file header + deflate via node:zlib)
|
||||
│ ├─ validateEntry(name) — reject zip-slip, absolute paths, symlinks
|
||||
│ └─ extractToDir(buffer, tempDir, caps) → Promise<void>
|
||||
└─ Eksisterende scan-pipeline mot tempDir
|
||||
```
|
||||
|
||||
## Implementasjonssteg
|
||||
|
||||
### Steg 1: `lib/vsix-fetch.mjs` (~150 linjer)
|
||||
- Bruk `fetch()` (Node 18+, zero deps)
|
||||
- Kun HTTPS, ingen redirect til HTTP
|
||||
- Timeout 30s, size-cap 50MB via streaming + abort
|
||||
- TLS-verifisering default (ikke tillat `--insecure`)
|
||||
- SHA-256-beregning underveis
|
||||
- Marketplace: POST-payload + header `Accept: application/octet-stream` — se knowledge/marketplace-api-notes.md (lag den)
|
||||
- OpenVSX: offentlig dokumentert API, enklest
|
||||
- Returnerer `{ buffer, sourceUrl, sha256, publisher, name, version }`
|
||||
|
||||
### Steg 2: `lib/zip-extract.mjs` (~250 linjer)
|
||||
- Parse End of Central Directory (EOCD) fra slutten
|
||||
- Les Central Directory headers → entries-array
|
||||
- For hver entry: les Local File Header, inflate med `node:zlib.createInflateRaw`
|
||||
- Valideringer PER entry (før skriv):
|
||||
- Normaliser path (remove `.`, resolve `..`, reject absolute)
|
||||
- Avvis hvis path escaper targetDir (via `path.resolve` + prefix-sjekk)
|
||||
- Avvis `external_attr` som indikerer symlink (0xA000 flag)
|
||||
- Akkumulere expansion ratio, abort hvis > 100x
|
||||
- Kun filer, ingen dirs (opprett dirs on-the-fly)
|
||||
- Tests: fixtures med kjente angrep (zip-slip fixture, symlink fixture, bomb fixture)
|
||||
|
||||
### Steg 3: Utvid `ide-extension-scanner.mjs`
|
||||
- Early-detect: `if (target.startsWith('http')) → fetch + extract → scan(tempDir)`
|
||||
- Ny option: `--online-source <marketplace|openvsx>` (default: auto-detect fra URL)
|
||||
- Cleanup: `try/finally` med `rm(tempDir, { recursive: true, force: true })`
|
||||
- Error-mapping: network errors → "unreachable", signature fail → "tamper", zip fail → "malformed"
|
||||
- Envelope.meta.source: `{ type: 'url', url, sha256, publisher, name, version }`
|
||||
|
||||
### Steg 4: CLI + command-dispatcher
|
||||
- `bin/llm-security.mjs`: passthrough, ingen endring nødvendig (target er allerede første arg)
|
||||
- `commands/ide-scan.md`: oppdater eksempler + flagg-dokumentasjon
|
||||
|
||||
### Steg 5: Tester (~20 nye)
|
||||
- Mock HTTP-server (node:http) for fetch-tester
|
||||
- Fixture-VSIX: bygg en fra `tests/fixtures/ide-extensions/root-benign/publisher.benign-ext-1.0.0/` via `zip -r` i test-setup
|
||||
- Zip-slip fixture: VSIX med `../../etc/passwd` entry
|
||||
- Zip-bomb fixture: 1KB komprimert → 10GB ukomprimert (syntetisk)
|
||||
- Integration: OpenVSX-mock → fetch → scan → envelope
|
||||
- Unit: detectUrlType, extractToDir med caps
|
||||
|
||||
### Steg 6: Knowledge + docs
|
||||
- `knowledge/marketplace-api-notes.md`: dokumenter undokumenterte Marketplace-endpoints + stabilitet
|
||||
- `docs/ide-scan-url-usage.md`: eksempler for pre-install-workflow
|
||||
- CHANGELOG: v6.4.0-seksjon
|
||||
- Plugin README: legg til URL-eksempler på `/security ide-scan`
|
||||
- Rot-README: oppdater til v6.4.0 (hvis tests passerer 1300+ og URL-support er feature-highlight)
|
||||
|
||||
### Steg 7: Versjon + ship
|
||||
- `npm run bump -- 6.4.0`
|
||||
- Test-suite grønn
|
||||
- Smoke test: `llm-security ide-scan https://open-vsx.org/extension/anthropic/claude-code` — skal funke (åpen API)
|
||||
- Commit + push til Forgejo (main, pre-autorisert)
|
||||
|
||||
## Åpne beslutninger (diskuter i neste sesjon)
|
||||
|
||||
1. **GitHub repo som URL** — skal vi bygge extension fra source? Legger til build-step (node+npm installert + `npm install + vsce package`). Stor kompleksitet. Foreslår: IKKE støtt GitHub i v6.4.0 — bare VSIX-baserte kilder.
|
||||
2. **Marketplace API-stabilitet** — undokumentert endpoint kan endres. Fall-back: OpenVSX-lookup hvis Marketplace feiler (de fleste Marketplace-extensions finnes også på OpenVSX).
|
||||
3. **Cache** — bør vi cache nedlastede VSIX i `~/.cache/llm-security/vsix/` (med SHA-256 som key, 7-dagers TTL)? Sparer båndbredde ved gjentatte scans. JA — enkelt og skru-av-bart via `--no-cache`.
|
||||
4. **Signatur-verifisering av VSIX** — VS Code har begynt å signere publisher uploads (`.signature.p7s` i VSIX). Bør vi verifisere? Krever X.509-parsing (Node har `node:crypto` men P7S er kompleks). Foreslår: v6.5.0, ikke v6.4.0.
|
||||
|
||||
## Akseptansekriterier
|
||||
|
||||
- [ ] `llm-security ide-scan https://open-vsx.org/extension/anthropic/claude-code` returnerer envelope med korrekt publisher/name/version
|
||||
- [ ] `llm-security ide-scan https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code` fungerer (forutsatt endpoint er stabilt)
|
||||
- [ ] Zip-slip-fixture resulterer i BLOCK-verdict, ingen filer skrevet utenfor tempDir
|
||||
- [ ] Zip-bomb-fixture stoppes ved 100x expansion ratio
|
||||
- [ ] Temp-dir renses i alle exit-paths (success, error, abort)
|
||||
- [ ] Nettverksfeil → tydelig feilmelding, ikke stack trace
|
||||
- [ ] Scan av ekte Marketplace-extension (f.eks. `ms-python.python`) fullfører på < 30s på normal forbindelse
|
||||
- [ ] Test-suite 1300+ grønn
|
||||
|
||||
## Estimat
|
||||
|
||||
- Kode: ~600 linjer (vsix-fetch: 150, zip-extract: 250, integrasjon: 100, tester: 100)
|
||||
- Tid: 1 fokusert sesjon med auto mode
|
||||
- Risiko: Moderat — zip-extraction er kjent angrepsvektor, trenger grundige tester. Fetch er enkelt.
|
||||
|
||||
## Referanser
|
||||
|
||||
- OpenVSX API: https://open-vsx.org/swagger-ui
|
||||
- VS Code Marketplace (undokumentert): https://github.com/microsoft/vscode-vsce/blob/main/src/publish.ts
|
||||
- ZIP format spec: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
|
||||
- Zip-slip CVE: https://snyk.io/research/zip-slip-vulnerability
|
||||
- Node fetch + streaming: https://nodejs.org/api/globals.html#fetch
|
||||
302
plugins/llm-security/docs/plans/jetbrains-research-brief.md
Normal file
302
plugins/llm-security/docs/plans/jetbrains-research-brief.md
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
---
|
||||
type: ultraresearch-brief
|
||||
created: 2026-04-17
|
||||
question: "Extend /security ide-scan (llm-security v6.6.0) to cover JetBrains / IntelliJ plugins, mirroring the VS Code v6.3-v6.5 work (discovery + URL fetch + OS sandbox)."
|
||||
confidence: 0.88
|
||||
dimensions: 7
|
||||
mcp_servers_used: []
|
||||
local_agents_used: [architecture-mapper, task-finder]
|
||||
external_agents_used: [docs-researcher, community-researcher, security-researcher]
|
||||
---
|
||||
|
||||
# JetBrains/IntelliJ Extension Security Scanning — v6.6.0 Research Brief
|
||||
|
||||
> Generated by ultraresearch-local v1.0 on 2026-04-17
|
||||
|
||||
## Research Question
|
||||
|
||||
Can we replicate the v6.3–v6.5 VS Code/VSIX work for JetBrains/IntelliJ plugins in `/security ide-scan`, and what is the authoritative technical + threat context needed to plan it?
|
||||
|
||||
Specifically: (1) plugin format, (2) install locations per OS and product, (3) JetBrains Marketplace API, (4) 2024–2026 threat landscape, (5) check-mapping from 7 VS Code checks to JetBrains equivalents, (6) reusability of existing sandbox/zip/fetch primitives, (7) risks and JetBrains-only concerns.
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Feasible and well-scoped. The existing VS Code pipeline (`lib/zip-extract.mjs`, `lib/vsix-sandbox.mjs`, `lib/vsix-fetch.mjs`, `lib/ide-extension-discovery.mjs`, `lib/ide-extension-parser.mjs`) is ~80% reusable — all routing, CLI flags, envelope counters, data loader, and sandbox primitives already accept a `'jetbrains'` type. The 4 real implementation gaps are: (1) directory walk in `discoverJetBrainsExtensions`, (2) `META-INF/plugin.xml` XML parser in `parseIntelliJPlugin` (nested-jar extraction required), (3) populated `knowledge/top-jetbrains-plugins.json`, (4) `type === 'jetbrains'` branch in `scanOneExtension`. Key caveat: zero publicly-confirmed malicious plugins on JetBrains Marketplace — the blocklist must be a typosquat-seed corpus, not a confirmed-bad list. Secondary caveat: signing is advisory (warning-not-block, YouTrack IJPL-212393 confirms warning sometimes absent); the highest-value IDE-specific check is sideload detection, per OX Security 2025 research demonstrating verified-badge bypass on IntelliJ.
|
||||
|
||||
## Dimensions
|
||||
|
||||
### Plugin Format — Confidence: high
|
||||
|
||||
**Local findings:**
|
||||
- `lib/ide-extension-parser.mjs:101-112` — `parseVsixFile` and `parseIntelliJPlugin` are both stubs; only `parseVSCodeExtension` is implemented. JetBrains path returns `null`.
|
||||
- `lib/zip-extract.mjs` (entire file) — format-agnostic ZIP parser with zip-slip/symlink/absolute/ratio defences. ZIP-inside-ZIP extraction is supported (caller concatenates calls).
|
||||
|
||||
**External findings:**
|
||||
- Distribution: either `.jar` (no bundled deps, legacy) or `.zip` (with deps, modern Gradle default). Source: [plugin-content.html](https://plugins.jetbrains.com/docs/intellij/plugin-content.html).
|
||||
- ZIP layout: `<artifact>/lib/<main>.jar + lib/<dep>.jar`. **`META-INF/plugin.xml` lives INSIDE the main jar, not at ZIP root.** Scanner must open outer ZIP → find main jar in `lib/` → open main jar → read `META-INF/plugin.xml`.
|
||||
- Installed plugins on disk are already extracted by the IDE (directory form, not zip). Sideloaded URL downloads are zip form.
|
||||
- `plugin.xml` schema elements relevant for scanning: `<id>`, `<name>`, `<version>`, `<vendor url email>`, `<description>`, `<depends optional config-file>`, `<idea-version since-build until-build>`, `<actions>`, `<extensions defaultExtensionNs="com.intellij">` with children like `applicationService`, `projectService`, `postStartupActivity`, `backgroundPostStartupActivity`, `applicationListener`, `projectListener`, `themeProvider`. Legacy `<application-components>` / `<project-components>` still supported and appear in older plugins.
|
||||
- `require-restart` attribute on `<idea-plugin>` controls dynamic loading. `Premain-Class` in `META-INF/MANIFEST.MF` = Java agent (bytecode instrumentation) — extremely high-risk signal.
|
||||
|
||||
### Install Locations per OS — Confidence: high
|
||||
|
||||
**External findings (per-product confirmed):**
|
||||
|
||||
| Product | Directory string | macOS root | Linux plugins | Windows root |
|
||||
|---------|-----------------|-----------|---------------|--------------|
|
||||
| IntelliJ IDEA Ultimate | `IntelliJIdea{YYYY.N}` | `~/Library/Application Support/JetBrains/` | `~/.local/share/JetBrains/` | `%APPDATA%\JetBrains\` |
|
||||
| IntelliJ IDEA Community | `IdeaIC{YYYY.N}` | same | same | same |
|
||||
| PyCharm Professional | `PyCharm{YYYY.N}` | same | same | same |
|
||||
| PyCharm Community | `PyCharmCE{YYYY.N}` | same | same | same |
|
||||
| WebStorm | `WebStorm{YYYY.N}` | same | same | same |
|
||||
| GoLand | `GoLand{YYYY.N}` | same | same | same |
|
||||
| PhpStorm | `PhpStorm{YYYY.N}` | same | same | same |
|
||||
| RubyMine | `RubyMine{YYYY.N}` | same | same | same |
|
||||
| CLion | `CLion{YYYY.N}` | same | same | same |
|
||||
| DataGrip | `DataGrip{YYYY.N}` | same | same | same |
|
||||
| Rider | `Rider{YYYY.N}` | same | same | same |
|
||||
| RustRover | `RustRover{YYYY.N}` | same | same | same |
|
||||
| DataSpell | `DataSpell{YYYY.N}` | same | same | same |
|
||||
| Android Studio | `AndroidStudio{version}` | `~/Library/Application Support/Google/` (NOT JetBrains) | `~/.config/Google/` | `%APPDATA%\Google\` |
|
||||
| Aqua | `Aqua{YYYY.N}` | same (discontinued April 2025, legacy installs remain) | same | same |
|
||||
| Fleet | separate ecosystem — **exclude from scanner** | n/a | n/a | n/a |
|
||||
|
||||
**Critical Linux quirk:** config lives under `~/.config/JetBrains/<Product>/` but plugins live under `~/.local/share/JetBrains/<Product>/plugins/`. This is different from macOS/Windows where both are co-located.
|
||||
|
||||
**Local findings:**
|
||||
- `lib/ide-extension-discovery.mjs:38-53` — `getJetBrainsBaseDir()` already returns macOS/Windows/Linux base dirs correctly, but Linux points to `~/.local/share/JetBrains` (correct for plugins).
|
||||
- Currently does NOT handle Android Studio (Google/ prefix) — needs second base-dir function.
|
||||
|
||||
**Contradictions:**
|
||||
- The existing `getJetBrainsBaseDir()` returns a single base dir, but Android Studio uses a separate vendor prefix (`Google/` not `JetBrains/`). Resolution: add a second discovery root for Android Studio, or generalize to accept a list of `{vendor, productRegex}` tuples.
|
||||
|
||||
### JetBrains Marketplace API — Confidence: high (live calls confirmed)
|
||||
|
||||
**External findings:**
|
||||
- Base: `https://plugins.jetbrains.com/`
|
||||
- **Lookup by numeric ID:** `GET /api/plugins/{numericId}` → returns `{id, name, xmlId, vendor:{name, isVerified}, downloads, pricingModel, family, tags, link}`. Confirmed live with id=1347 (Scala).
|
||||
- **Lookup by xmlId:** `GET /plugins/list?pluginId={xmlId}` (documented). `GET /api/plugins?xmlId=...` returns HTTP 400 — do NOT use.
|
||||
- **Version listing:** `GET /api/plugins/{numericId}/updates?size=N` → array of `{id, version, file, cdate, since, until, size, downloads, pluginId, author:{isJetBrains, name}}`. Confirmed live.
|
||||
- **Download latest compatible:** `GET /pluginManager?action=download&id={xmlId}&build={productCode}-{buildNumber}` → redirect to file.
|
||||
- **Download specific version:** `GET /plugin/download?pluginId={xmlId}&version={v}` → redirect.
|
||||
- **Direct file download:** `GET /files/{file-path-from-update-object}` (e.g. `/files/1347/991561/scala-intellij-bin-2025.3.39.zip`).
|
||||
- **URL-to-ID resolution:** `https://plugins.jetbrains.com/plugin/7973-intellivue` → numeric ID is `7973` (first path segment before dash).
|
||||
- **Auth:** none for reads. Rate limits not publicly documented.
|
||||
- **OpenVSX equivalent:** **none confirmed.** JetBrains Marketplace is effectively the only public registry. Custom repositories supported via `updatePlugins.xml` on arbitrary HTTPS servers (enterprise).
|
||||
|
||||
### Threat Landscape 2024–2026 — Confidence: high (for documented incidents); medium (for absence-of-evidence)
|
||||
|
||||
**External findings (security + community):**
|
||||
|
||||
- **CVE-2024-37051** (CVSS 9.3, June 2024) — first-party JetBrains GitHub plugin. Malicious PR content exfiltrates OAuth/PAT tokens. 15 IDEs affected 2023.1+. Patched.
|
||||
- **CVE-2025-64671** (2025) — GitHub Copilot for JetBrains. Same attack-surface pattern.
|
||||
- **CVE-2025-57729** (CVSS 6.5, Aug 2025) — unexpected LSP plugin startup.
|
||||
- **CVE-2025-68269** (CVSS 5.4, Dec 2025) — remote project trust bypass.
|
||||
- **CVE-2025-64456** (CVSS 8.4, 2025) — ReSharper DPA Collector missing sig verification → local privilege escalation.
|
||||
- **Log4Shell propagation (Dec 2021):** JetBrains used "API Watcher" to identify plugins bundling log4j; temporarily hid affected versions, no public list of affected plugin names.
|
||||
- **OX Security (July 2025, Nir Zadok & Moshe Siman-Tov Bustan):** demonstrated verified-badge bypass on IntelliJ IDEA via sideloaded `.zip` with spoofed verification values. JetBrains response: "not from Marketplace, user responsibility." Sideload is therefore the primary remaining attack vector that JetBrains does not treat as a vendor bug.
|
||||
- **Third-party Marketplace malicious plugins:** **zero publicly confirmed cases** as of April 2026. This is material: the blocklist file CANNOT be seeded with confirmed-malicious entries because none exist publicly. Seed as typosquat-comparison corpus only.
|
||||
- **Plugin signing reality:** Warning-not-block. YouTrack IJPL-212393 confirms warning sometimes absent. Custom-repository plugins do not get JetBrains CA re-signing — enterprise can self-sign with arbitrary chain.
|
||||
- **No sandbox:** JetBrains' own docs explicitly acknowledge: "Plugins run as part of the IDE and have the same access rights as the IDE itself… Uninstalling does not guarantee all effects are undone." This is structurally identical to VS Code — no sandboxing, full user-privilege execution on project open.
|
||||
|
||||
**Trust-model observations:**
|
||||
- `vendor.isVerified` from Marketplace API is the strongest trust signal but confirms identity, not code safety.
|
||||
- `vendor.email` and `vendor.url` are optional, unvalidated, template-friendly — weak signals.
|
||||
- Plugin ID `Lombook Plugin` is a legitimate pre-existing typo in the real Lombok plugin (Marketplace ID 6317). Scanner must whitelist this exact string; a plugin named `Lombok` or `LombokPlugin` would be the suspicious one.
|
||||
|
||||
### Check Mapping (7 VS Code checks → JetBrains) — Confidence: high
|
||||
|
||||
| VS Code Check | JetBrains Equivalent | Signal source | Status |
|
||||
|--------------|----------------------|---------------|--------|
|
||||
| Blocklist match | Same concept, different ID namespace (`xmlId`) | `plugin.xml` `<id>` vs `knowledge/top-jetbrains-plugins.json[blocklist]` | Reusable — needs populated blocklist (seed-only, no confirmed-bad) |
|
||||
| Theme-with-code | Plugin declares `<themeProvider>` as only extension, OR declares it alongside services/actions/listeners | `plugin.xml` extension count + element types | Needs-new logic (JetBrains has no `categories: ["Themes"]` manifest field — infer from `<themeProvider>` presence) |
|
||||
| Sideload detection | Sideloaded plugin = installed via "Install Plugin from Disk" or directly dropped into plugins dir | On-disk scan: no reliable filesystem marker. **Confidence: low** — JetBrains does not mark sideloaded vs Marketplace-installed on disk. Only URL-target mode reliably knows provenance. | Partial — URL mode = always-sideload; on-disk mode = cannot distinguish reliably. Emit LOW advisory for on-disk plugins with `vendor.isVerified=false` (queried from Marketplace by xmlId). |
|
||||
| Broad activation (`*` / `onStartupFinished`) | `<application-components>` OR `AppLifecycleListener` with `appStarted` (highest risk); `<postStartupActivity>` / `<backgroundPostStartupActivity>` (medium); `applicationService` with `preload="true"` (medium) | `plugin.xml` extension/listener declarations | Needs-new logic with JetBrains-specific ranking |
|
||||
| Typosquat (Levenshtein vs top list) | Same algorithm, different corpus (xmlIds) | `plugin.xml` `<id>` vs `knowledge/top-jetbrains-plugins.json[jetbrains]` | Reusable — needs populated corpus (50+ xmlIds identified, see Q5 in community findings) |
|
||||
| Extension-pack expansion | `<depends>` chain — required vs `optional="true"` | `plugin.xml` `<depends>` elements | Needs-adaptation |
|
||||
| Dangerous uninstall hooks | **No direct equivalent.** JetBrains has `DynamicPluginListener.beforePluginUnload` but plugin's own code runs, not a manifest-declared script like `vscode:uninstall` | — | Skip (no parallel); document in knowledge as "Known Limitations" |
|
||||
|
||||
**JetBrains-only checks (new categories):**
|
||||
- **Java agent declaration (`Premain-Class` in MANIFEST.MF):** HIGH severity — bytecode instrumentation capability.
|
||||
- **Native binary bundling:** `.dll/.so/.dylib/.exe` inside jar resource directories. Legitimate for some plugins (jssc serial port) but also a confirmed implant delivery vector. Log SHA-256 + size.
|
||||
- **Legacy `<application-components>`:** blocks dynamic loading, fires at IDE launch. MEDIUM advisory.
|
||||
- **Shaded jar detection:** bundled jar where `META-INF/MANIFEST.MF` lacks Maven coordinates. Cannot audit via OSV.dev. MEDIUM advisory.
|
||||
|
||||
### Sandbox / Primitive Reusability — Confidence: high
|
||||
|
||||
**Local findings (validated against memory note on sandbox reuse):**
|
||||
|
||||
- `lib/zip-extract.mjs` — **fully reusable**, format-agnostic (ZIP is ZIP). Existing zip-slip/symlink/absolute/ratio/depth/entries caps apply unchanged to JetBrains `.zip`.
|
||||
- `lib/vsix-sandbox.mjs:buildSandboxProfile, buildBwrapArgs` — **fully reusable**, no VS-Code-specific strings.
|
||||
- `lib/vsix-sandbox.mjs:buildSandboxedWorker, runVsixWorker` — **needs-adaptation**: `WORKER_PATH` is hardcoded to `vsix-fetch-worker.mjs`. Generalize to accept `workerPath` parameter (or create wrapper `runPluginWorker(url, tmpDir, workerPath, opts)`).
|
||||
- `lib/vsix-fetch.mjs` — transport primitives (`httpsFetch`, `readBodyCapped`, `httpsFetchSameHost`) **fully reusable**. Routing (`detectUrlType`, `fetchMarketplaceVsix`, `fetchOpenVsxVsix`) **VS-Code-specific**: add `fetchJetBrainsPlugin()` arm and extend `detectUrlType` to recognize `plugins.jetbrains.com/plugin/...` URLs.
|
||||
- Redirect whitelist: add `plugins.jetbrains.com`, `downloads.marketplace.jetbrains.com`, `cache-redirector.jetbrains.com` (verify during implementation via redirect observation).
|
||||
- `lib/vsix-fetch-worker.mjs` — create sibling `lib/jetbrains-fetch-worker.mjs` with same IPC protocol (`--url`, `--tmpdir` argv → single stdout JSON line with `{ok, sha256, size, finalUrl, source, extRoot}`). `extRoot` detection differs: look for top-level dir containing `lib/` rather than `extension/`.
|
||||
- Reused scanners (UNI/ENT/NET/TNT/MEM/SCR):
|
||||
- UNI, ENT: fully reusable (text scanning, format-agnostic).
|
||||
- NET: minor adaptation — add JetBrains CDN hosts to trusted-domains set.
|
||||
- TNT: minor adaptation — add `.kt`, `.groovy`, `.scala` to `CODE_EXTENSIONS` (currently has `.java` but not Kotlin etc.).
|
||||
- MEM: reusable but low-recall for JetBrains — `MEMORY_FILE_PATTERNS` won't match JetBrains filenames. Workaround: in `scanOneExtension` JetBrains branch, include `plugin.xml` and README files in `memFiles`.
|
||||
- SCR: fully reusable — gracefully no-ops on JetBrains plugins (no npm/pip lockfiles).
|
||||
|
||||
### Risks / Unknowns — Confidence: medium
|
||||
|
||||
**What makes this harder than VS Code:**
|
||||
|
||||
1. **Nested ZIP extraction for installed plugins on disk.** When scanning installed plugins, JetBrains plugins on disk are already extracted — directory form, main jar still a ZIP. Scanner must ZIP-extract the main jar to read `plugin.xml`. This is one more layer than VS Code (where `package.json` is plain on disk).
|
||||
2. **Bundled JAR dependency staleness.** Every plugin ships its own `lib/*.jar` copies. No lockfile. Log4Shell propagated this way in 2021. Tier-1 mitigation: parse `META-INF/MANIFEST.MF` for `Implementation-Title`/`Implementation-Version`, batch-query OSV.dev Maven ecosystem. Shaded jars (coordinates stripped) flag as "cannot audit."
|
||||
3. **Android Studio vendor prefix divergence.** Single `getJetBrainsBaseDir()` insufficient — Android Studio uses `Google/AndroidStudio<version>/` under the normal OS base. Generalize discovery.
|
||||
4. **No OSV ecosystem for JetBrains plugin IDs.** Only the bundled Maven deps are OSV-queryable. Third-party plugin IDs have no canonical vulnerability database.
|
||||
5. **Signing is advisory.** `hasSignature` field in `ParsedManifest` is unreliable — IDE warns but does not block; warning sometimes absent (YouTrack IJPL-212393). Use only as a weak INFO signal.
|
||||
6. **Zero-public-malicious-plugin dataset.** Blocklist must be structurally seed-only. Typosquat corpus is high-value; "confirmed bad" section starts empty.
|
||||
7. **Kotlin (`.kt`) not in TNT's CODE_EXTENSIONS.** Will silently skip Kotlin-authored plugins for taint analysis. Add `.kt`, `.groovy`, `.scala`.
|
||||
8. **Fleet plugins are a different ecosystem** (separate SDK, Kotlin Multiplatform) — explicitly exclude; do not attempt to parse.
|
||||
9. **`Premain-Class` = high-severity signal** (Java agent / bytecode instrumentation). Community finding — not present in existing check taxonomy.
|
||||
10. **Native binaries in jars = medium-high signal.** Confirmed implant delivery vector (Nextron Systems on trojanized Material Icon Theme). Log SHA-256 + size.
|
||||
|
||||
**Not found / gaps:**
|
||||
- Marketplace API rate limits (not publicly documented).
|
||||
- Whether IDE re-verifies plugin signatures on every load vs install-time only.
|
||||
- Whether sideloaded plugins get any runtime re-validation.
|
||||
- No academic paper specifically on JetBrains plugin malware analysis (2023–2026).
|
||||
|
||||
## Local Context
|
||||
|
||||
### Architecture
|
||||
|
||||
From `architecture-mapper`: the orchestrator `scanners/ide-extension-scanner.mjs` already routes on `type === 'vscode' | 'jetbrains'`, already counts `meta.extensions_discovered.jetbrains`, already parses `--intellij-only` CLI flag. The call graph is already built — `scanOneExtension` is the only place that unconditionally dispatches to `parseVSCodeExtension`; needs branch.
|
||||
|
||||
### Dependencies
|
||||
|
||||
From `architecture-mapper`: `lib/zip-extract.mjs` is the reusable foundation. `lib/vsix-sandbox.mjs` generalizes with one parameter (`WORKER_PATH`). The main new dependency is an XML parser for `plugin.xml` — zero-dep constraint mandates either hand-rolled DOM-lite (regex-based for simple fields + minimal recursive-descent for nested elements) or use of Node.js built-in primitives. No third-party XML deps permitted per `CLAUDE.md` "Null npm dependencies in hooks/scanners."
|
||||
|
||||
### Conventions
|
||||
|
||||
From project `CLAUDE.md` and code inspection: test harness is `node:test`, fixtures under `tests/fixtures/<feature>/`, scanner prefixes per scanner (IDE for top-level, UNI/ENT/NET/TNT/MEM/SCR for reused). JetBrains additions must preserve scanner prefix `IDE`; may introduce new finding type codes (e.g. `IDE-JB-01` theme-with-code, `IDE-JB-02` broad-activation, etc.).
|
||||
|
||||
### History
|
||||
|
||||
Task-finder located the exact canonical backlog entry: `TODO.md:14` — `v6.6.0-kandidater: JetBrains-discovery (v1.1-stub i dag)`. This is the user-sanctioned scope.
|
||||
|
||||
## External Knowledge
|
||||
|
||||
### Best Practice
|
||||
|
||||
- Parse `META-INF/plugin.xml` from inside main jar (inside outer zip). Official pattern.
|
||||
- Use Marketplace REST API for xmlId→verified/downloads/vendor enrichment. Batch via `/api/plugins/{id}`.
|
||||
- OSV.dev batch API (`POST /v1/querybatch`) with Maven ecosystem queries for bundled jars.
|
||||
- For sideload detection on URL target: always-sideload (by definition of URL install). For on-disk target: query Marketplace API by xmlId; if `vendor.isVerified === false` and plugin has no directory-level signature marker, emit LOW advisory.
|
||||
|
||||
### Alternatives
|
||||
|
||||
Considered and rejected:
|
||||
- **Java bytecode decompilation** — requires JVM subprocess. Out of scope for zero-dep v6.6.0. Defer as v6.7+ if demand emerges.
|
||||
- **Scanning all bundled jars as first-class extensions** — explodes scope. Plan: treat bundled jars as dependency data (OSV queries), not as recursive extension scanning.
|
||||
- **Running IDE's own Plugin Verifier** — requires IntelliJ SDK tooling, not a security check. Out of scope.
|
||||
|
||||
### Security
|
||||
|
||||
- Log4Shell remains the canonical evidence that bundled-jar audit matters.
|
||||
- No JetBrains ecosystem prefix in OSV.dev. Only Maven matching works.
|
||||
- Plugin signing: 2021.2+ dual-signature (author + JetBrains re-sign via AWS KMS). Sideloads bypass entirely; enterprise custom repos can self-sign.
|
||||
- CVE-2024-37051 (GitHub plugin token leak) is the closest analogue to "malicious content → credential exfil" and is the threat model for the LLM01/LLM02 content checks already in MEM.
|
||||
|
||||
### Known Issues
|
||||
|
||||
- YouTrack IJPL-212393 — unsigned plugin warning sometimes not shown. Do not trust signing as binary gate.
|
||||
- JetBrains dismisses sideload bypass as "user responsibility." Scanner fills the gap the vendor declined to fill.
|
||||
- `postStartupActivity` is platform-recommended for plugin project-load code but the platform team explicitly says plugins "must not affect IDE startup sequence." Tension is real; scanner ranks `<application-components>` + `AppLifecycleListener.appStarted` as HIGH and `postStartupActivity` as MEDIUM.
|
||||
|
||||
## Synthesis
|
||||
|
||||
The triangulation surfaces three cross-cutting insights that neither local nor external research alone would produce:
|
||||
|
||||
**1. Reusability is higher than initial estimation suggested.** Task-finder shows wiring is 80% done (routing, envelope, CLI, data-loader). Architecture-mapper shows sandbox primitives generalize via one parameter. Docs-researcher confirms zip structure is nested ZIP (outer + main jar) — and local `lib/zip-extract.mjs` already handles arbitrary ZIP streams. Result: v6.6.0 is primarily *integration* work, not greenfield. Net new code estimated at 300–500 lines (parser + discovery walker + 2–3 new check functions + knowledge file population), plus test fixtures.
|
||||
|
||||
**2. The threat model differs from VS Code in ways that reshape which checks matter.** VS Code's Marketplace has documented malware campaigns; JetBrains' has zero. This inverts the check priority: blocklist match is LOW-value (nothing to match), typosquat is HIGH-value (primary vector per community research), sideload is HIGHEST-value (only unmitigated attack per OX research), broad-activation splits into HIGH (`<application-components>` + `AppLifecycleListener`) vs MEDIUM (`postStartupActivity`), and two entirely new signals emerge: `Premain-Class` Java agents and native-binary bundling.
|
||||
|
||||
**3. The "blocklist cannot contain confirmed-malicious entries" finding forces a schema rethink.** The existing `top-vscode-extensions.json` schema has `"blocklist": ["publisher.name@version"]` for confirmed-bad. The JetBrains equivalent must structure this as a typosquat-comparison corpus (`"jetbrains": [xmlId, ...]`) with an explicitly-empty `"blocklist": []` and a comment explaining why. Scanner warnings about "plugin on blocklist" will never fire for JetBrains in v6.6.0 — that's correct behavior. Enterprise users can seed their own private blocklists via `policy.json` when they discover internal threats.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- **Marketplace API rate limits** — not publicly documented. Implementation should implement conservative 1 req/s default with opt-in higher concurrency. Monitor for 429 responses.
|
||||
- **Custom repository enumeration** — JetBrains supports `updatePlugins.xml` on arbitrary HTTPS servers for enterprise. v6.6.0 focuses on Marketplace; enterprise custom repos deferred to v6.7+.
|
||||
- **Whether IDE re-verifies sideloaded plugin signatures at runtime** — not publicly documented. Assume no; treat sideload as always-elevated-risk.
|
||||
- **Lifetime of a plugin across IDE upgrades** — when user installs IntelliJ IDEA 2025.1 over 2024.3, do plugins copy over? Worth confirming during implementation to decide if discovery walks all version dirs or only the newest.
|
||||
- **Toolbox App plugin cache location** — `~/Library/Caches/JetBrains/Toolbox/plugins/...` is for Toolbox extensions, not IDE plugins. Confirm during implementation that this path should be skipped.
|
||||
|
||||
## Recommendation
|
||||
|
||||
**Proceed with v6.6.0 as a focused integration release.** Implementation plan should structure as:
|
||||
|
||||
### Phase A — Discovery + parsing (core)
|
||||
|
||||
1. Implement `discoverJetBrainsExtensions` in `lib/ide-extension-discovery.mjs` — walk `getJetBrainsBaseDir()/<Product><Version>/plugins/` (every product/version subdir), plus Android Studio under `getAndroidStudioBaseDir()`. Exclude Fleet and Toolbox paths.
|
||||
2. Implement `parseIntelliJPlugin` in `lib/ide-extension-parser.mjs` — for each plugin directory, locate main jar in `lib/` (convention: jar matching plugin dir name, or largest jar), ZIP-extract `META-INF/plugin.xml` + `META-INF/MANIFEST.MF`, return `ParsedManifest` extended with JetBrains-specific fields (`pluginId`, `since`, `until`, `depends`, `extensionDeclarations`, `hasPremainClass`, `nativeBinaries`, `bundledJars[{name, version, shaded}]`).
|
||||
3. Populate `knowledge/top-jetbrains-plugins.json` with ~50 verified xmlIds (see community Q5 — API-verified list). `blocklist: []` with comment explaining absence.
|
||||
4. Wire `type === 'jetbrains'` branch into `scanOneExtension` — dispatch to `parseIntelliJPlugin`, call `runJetBrainsChecks` instead of `runIdeChecks`.
|
||||
|
||||
### Phase B — Checks
|
||||
|
||||
5. `runJetBrainsChecks` with: blocklist match (reuses algorithm), typosquat (reuses algorithm, new corpus), theme-with-code (new: infer from `<themeProvider>` + other extensions), broad-activation (new: rank by extension point), `<depends>`-chain expansion (new), Java-agent detection (new: `Premain-Class` in MANIFEST.MF), native-binary detection (new: scan extracted jar entries for `.dll/.so/.dylib/.exe`).
|
||||
6. Minor scanner adaptations: NET trusted-domains (+ JetBrains CDN hosts), TNT `CODE_EXTENSIONS` (+ `.kt`, `.groovy`, `.scala`), MEM `memFiles` filter in orchestrator (+ `plugin.xml`, `README*`).
|
||||
|
||||
### Phase C — URL target support
|
||||
|
||||
7. Extend `detectUrlType` in `lib/vsix-fetch.mjs` to recognize `plugins.jetbrains.com/plugin/...` URLs. Add `fetchJetBrainsPlugin(publisher?, xmlIdOrNumericId, version?)`.
|
||||
8. Generalize `buildSandboxedWorker` / `runVsixWorker` to accept `workerPath` parameter (maintains backwards compat with existing `runVsixWorker` signature via default).
|
||||
9. Create `lib/jetbrains-fetch-worker.mjs` — same IPC as `vsix-fetch-worker.mjs`, uses `fetchJetBrainsPlugin` + `extractToDir`, detects `extRoot` as top-level dir containing `lib/`.
|
||||
|
||||
### Phase D — Tests + docs
|
||||
|
||||
10. Fixtures: `tests/fixtures/ide-extensions/root-jetbrains/` with benign + adversarial cases (theme-with-code, broad-activation, typosquat, Java agent, native binary, shaded jar).
|
||||
11. Tests: mirror `tests/scanners/ide-extension-scanner.test.mjs` pattern — `rootsOverride` injection, `intellijOnly: true`. Plus `tests/scanners/jetbrains-fetch.test.mjs` with mocked `globalThis.fetch`.
|
||||
12. Documentation: update `commands/ide-scan.md` (remove "v1.1 stub" language), `knowledge/ide-extension-threat-patterns.md` (add JetBrains sections), create `knowledge/jetbrains-marketplace-api-notes.md` (sibling of existing VSIX notes), update root `README.md` + plugin `CLAUDE.md` + `CHANGELOG.md` per project "docs at change" rule.
|
||||
|
||||
### Risk register for implementation planning
|
||||
|
||||
| Risk | Likelihood | Impact | Mitigation |
|
||||
|------|-----------|--------|-----------|
|
||||
| `plugin.xml` parsing scope creep (DTD validation, XSD) | Medium | Medium | Stop at structural extraction of documented-schema fields. Do NOT validate against schema. |
|
||||
| Nested ZIP extraction blows memory on large plugins | Low | Low | Existing 500MB uncompressed cap + 100x ratio cap apply to each nested extraction. Confirm during implementation. |
|
||||
| Marketplace API rate-limiting | Medium | Low | Single-request-per-second default; opt-in `--online-concurrency N`. |
|
||||
| False positives on legacy `<application-components>` | High | Low | MEDIUM severity, not HIGH. Document in finding rationale that deprecated does not imply malicious. |
|
||||
| Android Studio path divergence missed | Low | Medium | Explicit `getAndroidStudioBaseDir()` with OS-specific Google/ prefix. Test fixture for Android Studio discovery. |
|
||||
| `Lombook Plugin` legitimate typo triggers false positive | Certain | Low | Whitelist exact match for this single xmlId in typosquat check. |
|
||||
| Shaded-jar false negatives (bundled vuln lib, coords stripped) | High | High | Out-of-scope for v6.6.0 — emit INFO/MEDIUM advisory "cannot audit (shaded)" for each coord-less jar. |
|
||||
|
||||
Estimated effort: 3–5 focused sessions. No new npm dependencies. No new external network calls beyond optional JetBrains Marketplace API queries (behind `--online` flag, same convention as VS Code).
|
||||
|
||||
## Sources
|
||||
|
||||
| # | Source | Type | Quality | Used in |
|
||||
|---|--------|------|---------|---------|
|
||||
| 1 | https://plugins.jetbrains.com/docs/intellij/plugin-content.html | official | high | Plugin format |
|
||||
| 2 | https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html | official | high | Plugin format, `plugin.xml` schema |
|
||||
| 3 | https://plugins.jetbrains.com/docs/intellij/plugin-components.html | official | high | Broad activation ranking (legacy components) |
|
||||
| 4 | https://plugins.jetbrains.com/docs/intellij/dynamic-plugins.html | official | high | `require-restart`, dynamic plugin detection |
|
||||
| 5 | https://plugins.jetbrains.com/docs/intellij/theme-structure.html | official | high | Theme-with-code check |
|
||||
| 6 | https://plugins.jetbrains.com/docs/intellij/intellij-platform-extension-point-list.html | official | high | Broad-activation extension point enumeration |
|
||||
| 7 | https://plugins.jetbrains.com/docs/intellij/plugin-signing.html | official | high | Signing model, warning-not-block |
|
||||
| 8 | https://plugins.jetbrains.com/docs/marketplace/api-reference.html | official | high | Marketplace API base + endpoints |
|
||||
| 9 | https://plugins.jetbrains.com/docs/marketplace/plugin-update-download.html | official | high | Download endpoints |
|
||||
| 10 | https://plugins.jetbrains.com/docs/marketplace/understanding-plugin-security.html | official | high | No-sandbox acknowledgement |
|
||||
| 11 | https://plugins.jetbrains.com/docs/marketplace/verified-vendor-badge.html | official | high | `vendor.isVerified` semantics |
|
||||
| 12 | https://plugins.jetbrains.com/docs/marketplace/jetbrains-marketplace-approval-guidelines.html | official | high | Moderation + removal policy |
|
||||
| 13 | https://www.jetbrains.com/help/idea/directories-used-by-the-ide-to-store-settings-caches-plugins-and-logs.html | official | high | Install locations (IntelliJ IDEA) |
|
||||
| 14 | https://developer.android.com/studio/intro/studio-config | official (Google) | high | Android Studio path divergence |
|
||||
| 15 | https://fleet-support.jetbrains.com/hc/en-us/articles/7648059182226 | official | high | Fleet exclusion |
|
||||
| 16 | https://blog.jetbrains.com/security/2024/06/updates-for-security-issue-affecting-intellij-based-ides-2023-1-and-github-plugin/ | official | high | CVE-2024-37051 |
|
||||
| 17 | https://blog.jetbrains.com/platform/2021/12/log4j-vulnerability-and-third-party-plugins-on-jetbrains-marketplace/ | official | high | Log4Shell propagation |
|
||||
| 18 | https://www.darkreading.com/application-security/ide-extensions-risks-software-supply-chain | community | high | OX verified-badge bypass |
|
||||
| 19 | https://www.ox.security/blog/can-you-trust-that-verified-symbol-exploiting-ide-extensions-is-easier-than-it-should-be/ | community | high | OX research primary |
|
||||
| 20 | https://youtrack.jetbrains.com/projects/IJPL/issues/IJPL-212393/Installing-unsigned-plugin-did-not-show-error-redux | official (bug tracker) | high | Signing warning unreliability |
|
||||
| 21 | https://platform.jetbrains.com/t/execute-pre-load-activity/958 | official (forum, maintainer) | high | "plugins must not affect IDE startup sequence" |
|
||||
| 22 | https://workflowotg.com/malware-in-ide-plugins-an-attack-vector-to-look-out-for-in-2025/ | community | medium | Threat-model framing |
|
||||
| 23 | https://arxiv.org/html/2503.22391v1 | academic | high | Maven ecosystem vulnerability base rate |
|
||||
| 24 | https://osv.dev/ | official (OSV) | high | Maven ecosystem matching, bundled-jar audit |
|
||||
| 25 | Live API calls to `plugins.jetbrains.com/api/plugins/{id}` | primary | high | Marketplace API schema confirmation |
|
||||
| 26 | Task-finder report (in-conversation, see Phase 4 agent results) | codebase | high | Touchpoint inventory (6 must-change files) |
|
||||
| 27 | Architecture-mapper report (in-conversation, see Phase 4 agent results) | codebase | high | Reusability matrix, dependency diagram |
|
||||
| 28 | `CLAUDE.md` — project instructions at `/Users/ktg/.claude/plugins/marketplaces/ktg-plugin-marketplace/plugins/llm-security/CLAUDE.md` | codebase | high | Zero-dep constraint, test-harness convention, docs-at-change rule |
|
||||
| 29 | `MEMORY.md` — memory note on sandbox reuse | memory | high | Reuse sandbox primitives, do not copy |
|
||||
Loading…
Add table
Add a link
Reference in a new issue