phone-homeCovers 9999.0.0Reviewed by BitbisonUpdated 2026-11-06
When setup.py is loaded during pip installation, it looks up <hex>.dblspuqvpnlgmm1heifgr7hzpkqo9swtw.oast.site, where the hex spells the machine's hostname and the user's login name. It reads no credentials, starts no processes and leaves no files.
On November 5, 2026, strands-aidr 9999.0.0 was published to PyPI. It is the project's only release, and its own description is "SECURITY RESEARCH — dependency confusion PoC". The version number, 9999.0.0, is high enough to win over a private package of the same name when pip reads both a private index and PyPI. When setup.py is loaded, it looks up a DNS name under an oast.site (interactsh) domain. That name carries the machine's hostname and the user's login name, hex-encoded. OSV lists the package as MAL-2026-18657, and this review confirms that report. PyPI has since quarantined the project and no longer serves its files.
What the package does
Installing strands-aidr 9999.0.0 from its source distribution (sha256 607e17f59fda8bbc544109e6b193ebe9a399d31bb49aea191f8620106f30e8eb) does this:
- pip's PEP 517 hook
get_requires_for_build_wheel loads setup.py. That hook process then looks up <hex>.dblspuqvpnlgmm1heifgr7hzpkqo9swtw.oast.site through the system resolver. The hex labels spell sa-build-<hostname>-<user>.
- Nothing connects to the address the name resolves to. The data leaves in the query itself: whatever resolver the machine uses carries it to oast.site's name servers, so an egress firewall that allows DNS does not stop it.
- The package starts no processes and writes no files beyond what a normal install writes. Apart from the hostname and the login name, it reads no credentials or configuration.
What the code does
The behaviour is in setup.py of the published archive (sha256 bd2fe8a7040a0bc0120045a6c4248d5e61dbb32d8e56b51cdfc60daafbb0a7cd, 40 lines). Its docstring states the purpose: "Sends a single DNS callback to prove code execution" (lines 1–4).
- Line 11 sets
CALLBACK_DOMAIN = "dblspuqvpnlgmm1heifgr7hzpkqo9swtw.oast.site". The comment on line 10 asks the user of the template to replace it with "your interactsh domain". Line 16 skips the lookup only while the domain still starts with CHANGEME.
- Lines 19–21 take the first 20 characters of
socket.getfqdn() and the first 12 of $USER (or $USERNAME). They join them as sa-build-<hostname>-<user> and hex-encode the result.
- Lines 23–25 split the hex into DNS labels of 62 characters, append the domain and call
socket.getaddrinfo(query, 80). Only the lookup is made, never a connection. Lines 26–27 swallow any error, so the install never fails.
- Line 29 calls
_dns_callback() at module level, which supplies the word after sa- in the name. The lookup therefore happens every time setup.py is loaded: by each PEP 517 hook pip calls, by pip download or pip wheel of the sdist, and by an editable install. Each time it sends the same name.
- Lines 31–40 are an ordinary
setup() call for src/ with the README as long description. The file defines no cmdclass and does not override any command. That corrects the generic "overrides the install command" reason in the OSV entry: the lookup runs when the file is loaded, before any install command.
pyproject.toml (sha256 8089cf757b7d7b5428a52639d332dc16ced6c2b7943e7ef52f5994d41e74b91d) declares the setuptools.build_meta backend (lines 1–3), which is what makes pip execute setup.py. Lines 5–11 give the name, the version 9999.0.0 and the "SECURITY RESEARCH — dependency confusion PoC" description. setup.cfg (sha256 a29823dd9fbb34b0b00c45fe5823d7c25a3133805e072cad4920cf76dee85bfa) holds only the standard [egg_info] tag settings.
Nothing in these files reads credentials, takes commands from a server or installs anything that persists. The OSV report (source kam193) classifies the package as PROBABLY_PENTEST: it says the package "exfiltrates basic information about the host" on install or import and "has no other purpose".
Timeline (UTC)
| When | What |
| 11/05/26 00:39:23 | strands-aidr 9999.0.0 is published to PyPI, the project's only release |
| 11/05/26 00:44:34 | OSV publishes MAL-2026-18657, reported by Kamil Mańkowski (kam193) |
If you installed it
The package has told its operator your machine's hostname and your login name, and that this machine fetched strands-aidr from public PyPI. If you have a private package of that name, your installer is open to dependency confusion: anyone can publish a higher version on PyPI and have it run on your machines. To fix that:
- Uninstall strands-aidr 9999.0.0.
- Pin the private name to your own index: use
--index-url instead of --extra-index-url, or a per-package index in uv or Poetry.
- Require hashes.
To find every machine that fetched it, search your DNS logs for dblspuqvpnlgmm1heifgr7hzpkqo9swtw.oast.site. The package leaves nothing behind to clean up.
Indicators
- hosts
oast.sitedblspuqvpnlgmm1heifgr7hzpkqo9swtw.oast.site- requests
DNS lookup <hex of "sa-build-<hostname>-<user>">.dblspuqvpnlgmm1heifgr7hzpkqo9swtw.oast.site- files
setup.py (sha256 bd2fe8a7040a0bc0120045a6c4248d5e61dbb32d8e56b51cdfc60daafbb0a7cd)- processes
python …/pyproject_hooks/_in_process/_in_process.py get_requires_for_build_wheel
Examined
- 9999.0.0
sha256:607e17f59fda8bbc544109e6b193ebe9a399d31bb49aea191f8620106f30e8eb
Links