Skip to content

Fix ScanMalware attribution to subdomain tenants - #953

Merged
Ian Hellen (ianhelle) merged 3 commits into
microsoft:mainfrom
jonaslejon:fix/scanmalware-host-attribution
Oct 15, 2026
Merged

Ian Hellen (ianhelle) merged 3 commits into
microsoft:mainfrom
jonaslejon:fix/scanmalware-host-attribution

Conversation

@jonaslejon

Copy link
Copy Markdown
Contributor

What this fixes

For a dns or hostname lookup, the ScanMalware provider picks the newest completed scan whose matched_on contains url. The scan list for a host also holds its subdomains, and on a shared platform a subdomain is an unrelated tenant. So in v3.1.0 a lookup of netlify.app or workers.dev returns Severity high, Verdict Malicious, taken from a tenant's phishing page, and -quick attaches that tenant's scan as LastScanUrl.

A scan is now selected only when its own host is the observable's host, or its www. name, as well as matched_on containing url. This builds on cd856f7, which moved LastScan / LastScanUrl / LastTitle onto the selected scan; this change narrows which scan is selected. URL lookups already required the exact URL and are unchanged, as are IP lookups.

Disclosure: I run ScanMalware and wrote this provider (#938).

Before and after, live against scanmalware.com

Through ScanMalware().lookup_ioc(...), v3.1.0 against this branch:

Observable v3.1.0 this branch
netlify.app high, Malicious, LastScanUrl a tenant information, no scan attached
workers.dev high, Malicious, LastScanUrl a tenant information, no scan attached
github.io Low Risk from https://excelformulabot.github.io information, no scan attached
netlify.app (-quick) LastScanUrl a tenant no scan attached
example.com Low Risk, its own scan unchanged
https://gilded-macaron-79e0e2.netlify.app/ (url) high, Malicious unchanged

Result stays True and Scans keeps the full count; only the scan the details and verdict come from is stricter.

Tests

tests/context/test_scanmalware.py gains 8 test cases (22 to 30). Five fail on main: a subdomain tenant (with and without -quick), the platform's own scan behind a newer tenant scan, a lookalike host (example.com.ru under example.com), and a deeper subdomain under a hostname. Three guard against over-tightening: the www. name, letter case and an explicit port still count.

  • pytest tests/context/test_tiproviders.py tests/context/test_scanmalware.py: 71 passed
  • ruff check msticpy --ignore PLW0603: clean
  • pylint with the CI flags: 10.00/10
  • mypy with the CI flags: no issues

The same rule is in the MISP expansion module (MISP/misp-modules#916, released in v3.0.11).

The scan list for a host also holds its subdomains. On a shared platform
a subdomain is an unrelated tenant, so a lookup of netlify.app or
workers.dev took a tenant phishing page's verdict (Severity high), and
-quick attached the tenant's scan as LastScanUrl.

A scan is now selected only when its own host is the observable's host or
its www name, in addition to matched_on containing "url". URL lookups
already required the exact URL and are unchanged.

@ianhelle Ian Hellen (ianhelle) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stricter host attribution addresses the shared-platform issue, but the new host guard introduces a trailing-dot normalization regression that can suppress a matching threat verdict. Please normalize both hostnames consistently and add the regression coverage described inline before merging.

Comment thread msticpy/context/tiproviders/scanmalware.py Outdated
The host guard stripped a trailing dot from the observable's host but not
from the scanned URL's host. A scan of https://example.com./, looked up as
that same URL, compared example.com with example.com. and was skipped, so
its verdict was never requested and the result stayed informational.

Both hosts are now normalized the same way. The exact-URL check for URL
observables is unchanged. Two regression tests cover the dotted URL as both
observable and scan, and a domain observable whose scan URL has the dot.

Found in review by Ian Hellen.
@ianhelle

Copy link
Copy Markdown
Contributor

Nice! Thanks Jonas 💯

@ianhelle
Ian Hellen (ianhelle) added this pull request to the merge queue Oct 15, 2026
Merged via the queue into microsoft:main with commit a7f8b43 Oct 15, 2026
9 checks passed
stanfrbd pushed a commit to stanfrbd/cyberbro that referenced this pull request Oct 18, 2026
The host check stripped a trailing dot from the observable's host but not
from the scanned URL's host. A scan of https://example.com./, looked up as
that URL (or as example.com), compared example.com with example.com. and
was skipped, so its verdict was never reported.

Both hosts are now normalized the same way; the exact-URL check is
unchanged. The same asymmetry was found in review of the sibling msticpy
fix (microsoft/msticpy#953).
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.

2 participants