Skip to content

Add Extuno expansion module - #900

Open
projectboot wants to merge 2 commits into
MISP:mainfrom
Extuno:add-extuno-lookup
Open

Add Extuno expansion module#900
projectboot wants to merge 2 commits into
MISP:mainfrom
Extuno:add-extuno-lookup

Conversation

@projectboot

Copy link
Copy Markdown

Adds an expansion/hover module that looks up the reputation of a browser extension, an IDE extension or a package, from its store id or its listing URL.

Disclosure: I am the author of Extuno. It is a commercial service with a free tier (five scan credits on signup, no card), and the module requires an API key, in the same way as the VirusTotal, Shodan and AbuseIPDB modules already in this repository.

Why

There are 140 expansion modules and none of them answer for an extension. That gap is not incidental: OSV and the other public advisory databases cover package registries, so an npm or PyPI name found in a build log can already be checked here. An extension id found on a workstation cannot. Nothing public covers the Chrome Web Store, addons.mozilla.org, the VS Code Marketplace, JetBrains or Eclipse, so an analyst holding bidgllfieacmghieipmhgabodmljimfh has nowhere to ask.

What it does

An analyst can enrich either a store listing URL or a text attribute holding store:id:

chrome:bidgllfieacmghieipmhgabodmljimfh
  [text] Extuno: chrome:bidgllfieacmghieipmhgabodmljimfh is listed as malicious (Bundling Unwanted Software)
  [link] https://malext.io/reports/WhatsCluster/

https://marketplace.visualstudio.com/items?itemName=<publisher>.<name>
  [text] Extuno: vscode:... is listed as malicious (malware), removed from the store
  [text] Extuno: Removed from VS Marketplace: Malware.
  [link] https://github.com/microsoft/vsmarketplace/blob/main/RemovedPackages.md

Covered: Chrome, Firefox, Edge, VS Code, JetBrains, Eclipse, Discord client mods, and the npm, PyPI, Packagist, Maven, WordPress and Open VSX registries.

Notes on the implementation

  • A Chrome and an Edge extension id share the same 32 character a-p format, so a bare id that misses on Chrome is also tried against Edge rather than being reported as unknown.
  • A value the module cannot resolve returns an error, never a clean verdict. For a reputation module that is the failure that matters: an analyst must not read a parse failure as "not malicious".
  • "Not on record" is returned as a result rather than an error, because it is an answer.
  • The lookup is read-only and never submits the artifact for analysis. Only the identifier leaves the MISP instance. The API key is checked before any request is made.

Tests

tests/test_extuno.py, eight tests with requests mocked, covering the catalog and analysis paths, the Edge fallback, URL resolution for every supported store, and the two failure modes above. Verified against a live deployment as well.

Tolga SEZER added 2 commits September 8, 2026 17:31
Looks up the reputation of a browser extension, an IDE extension or a package
from its store id or its listing URL, and reports whether it appears in a
known-malicious catalog and whether Extuno's own static and sandbox analysis
reached a verdict on it.

Browser and IDE extensions are the reason the module exists. OSV and the other
public advisory databases cover package registries, so an npm or PyPI name found
in a build log can already be checked. An extension id found on a workstation
cannot: no public advisory database covers the Chrome Web Store, addons.mozilla,
the VS Code Marketplace, JetBrains or Eclipse. This closes that gap for the 140
existing expansion modules, none of which answer for an extension today.

An analyst can paste the store listing URL, or use a text attribute holding
store:id. Both resolve to the same lookup. A Chrome and an Edge extension id
share one 32 character format, so a bare id that misses on Chrome is also tried
against Edge rather than being reported as unknown.

The lookup is read-only and never submits the artifact for analysis; only the
identifier being looked up leaves the MISP instance. An API key is required and
is checked before any request is made.
Every one of these produced a positive-looking answer for a question the module
had not actually answered, which for a reputation module is the failure that
matters most.

An auth failure or a rate limit on the Edge fallback was accepted as the answer.
The first lookup checked for the error marker, the second did not, so a rejected
key or a 429 became "not in the malicious catalog and not analysed". The
fallback doubles the request rate for every bare id, so a rate limit there is
the expected case rather than a rare one. Both lookups now go through one path
that propagates the failure.

Host matching used str.endswith(), so evil-pypi.org matched pypi.org and a
lookalike delivery URL was enriched with the legitimate package's verdict. A
typosquat URL is exactly the indicator a MISP event holds. The boundary is now a
dot, and the genuine hosts and their subdomains still resolve.

The identifier was percent-encoded here and again by requests, so a Maven
coordinate reached the API as org.apache.commons%3Acommons-lang3, an artifact
that cannot exist, and the guaranteed miss was reported as "no record". The same
applied to Discord ids, which are URLs, and to the unicode slugs AMO publishes.

A listing URL truncated at the slug fell back to the slug as the identifier, so
a URL naming a real extension answered "no record". The extension id format was
already defined in the module but never applied; it is now, along with the
numeric id a JetBrains URL carries and the package an npm scope must be
followed by.

A 200 response of the wrong shape escaped as an AttributeError rather than the
error dict the module contract expects. api_url is operator configurable and a
proxy can rewrite a body, so the response and its nested fields are now checked
before use.

The Edge fallback tested the raw verdict field while the parser normalised it,
so a response with no verdict, or a null one, skipped the fallback and reported
a known-malicious Edge extension as unseen.

Six regression tests, one per defect. Each fails against the previous revision.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant