Skip to main content

PyPI - Version Test suite status

surblclient

A small client library for querying SURBL, URIBL, and other RBL-style blocklists over DNS.

Give it a domain (or URL host, or IP address) and it tells you whether the domain is listed, and on which sub-lists.

Installation

uv add surblclient

Or, from a checkout:

uv pip install .

Usage

>>> from surblclient import surbl
>>> "test.surbl.org" in surbl
True
>>> surbl.lookup("test.surbl.org")
('test.surbl.org', ['ph', 'mw', 'abuse', 'cr'])
>>> "google.com" in surbl
False

lookup resolves the base domain before querying, so subdomains work too:

>>> surbl.lookup("foo.bar.baz.test.surbl.org")
('test.surbl.org', ['ph', 'mw', 'abuse', 'cr'])
>>> base, lists = surbl.lookup("foo.bar.baz.test.surbl.org")
>>> print(f"{base} blacklisted in {lists}")
test.surbl.org blacklisted in ['ph', 'mw', 'abuse', 'cr']

It returns a (base_domain, lists) tuple on a hit, False when the domain is confirmed not listed, and None when the answer is unknown — a temporary DNS error, or the service refusing the query (see below).

The same interface is available for URIBL and the Spamhaus DBL:

>>> from surblclient import uribl, spamhausdbl
>>> "test.uribl.com" in uribl
True
>>> spamhausdbl.lookup("dbltest.com")
('dbltest.com', ['bad'])

The DBL labels a hit as either "bad" (inherently bad / safe to block) or "abused-legit" (an otherwise-good domain seen in abuse — meant for scoring, not outright blocking), reflecting Spamhaus's two return-code ranges.

Note that in can only return a bool, so it collapses the None (unknown/refused) case into False. If you need to distinguish "not listed" from "couldn't check", use lookup() and test for None:

result = surbl.lookup(domain)
if result is None:
    ...        # unknown — do NOT treat as clean (often a blocked resolver)
elif result is False:
    ...        # confirmed not listed
else:
    base, lists = result   # listed

A single query can return several 127.0.0.x records (one per sublist); per RFC 5782 §6 a client must treat any returned record as a listing, so this library reads them all and combines them (bit masks for SURBL/URIBL, value-range tests for the DBL — both per §6).

Resolver requirements

SURBL, URIBL, and the Spamhaus DBL all refuse queries that arrive via public/shared DNS resolvers (Google Public DNS, OpenDNS, Cloudflare, Quad9, and most ISP caching resolvers) and rate-limit heavy users. A refused query comes back as an error sentinel (127.0.0.1 for SURBL/URIBL; 127.255.255.254 "public resolver" or 127.255.255.255 "excessive queries" for the DBL), which this library reports as None (unknown) — so on a public resolver every lookup silently returns "unknown" and the library can't do its job.

This is the services' documented anti-abuse policy, not a bug in this library:

  • URIBL — "All queries that we refuse, we return a 127.0.0.1 response to … Public DNS providers such as OpenDNS or Google Public DNS are effected due to the high volume of queries they generate, as are many other internet service providers (ISP) that use caching nameservers …" (uribl.com/refused.shtml)
  • SURBL — "If you get a result of 127.0.0.1 when doing a DNS query into the public nameservers, then it means your access is blocked … A good administrative solution is to run a local caching nameserver …" (surbl.org/faq/guidelines)
  • Spamhaus — public resolvers are blocked with return code 127.255.255.254 ("Query via public/open resolver"); high-volume use needs the Data Query Service (DQS) or rsync feed. (spamhaus.org/faqs/dnsbl-usage)

To use this library reliably, run your own recursive resolver (e.g. unbound) on the machine doing the checks and point it at the DNS roots, then resolve through 127.0.0.1. High volume use needs a data feed / datafeed instead of the public DNS mirrors; the free public service has usage limits (broadly, fewer than 1,000 users or 250,000 messages/day).

Development

This project is managed with uv.

uv sync                      # set up the environment
uv run python -m unittest -v # run the tests (mocked; no network)
uv build                     # build the wheel and sdist

The default test run mocks DNS, so it is deterministic and offline. The live integration tests in tests/test_live.py hit the real services and are skipped unless you opt in — and they only pass through a non-public resolver (see Resolver requirements):

SURBL_LIVE_TESTS=1 uv run python -m unittest -v

License

MIT — see LICENSE.

Metadata

Release files for surblclient 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for surblclient 0.2.0
File Size Uploaded Provenance
surblclient-0.2.0.tar.gz 79.5 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for surblclient 0.2.0
File Interpreter ABI Platform
surblclient-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 162.2 kB

Release files / surblclient-0.2.0.tar.gz

Download URL surblclient-0.2.0.tar.gz
Size 79.5 kB
Tags Source
SHA-256 checksum
How to use checksums
802fe7e033336cd2e72119b128e7738f5a70ddd70f75fc5c8d53edcd32f0888e
BLAKE2b-256 checksum
How to use checksums
c0dd24dcfc7640cc9bf09749ac61e0b10c4c35fbac4c023eba0dbaa223c58c69
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / surblclient-0.2.0-py3-none-any.whl

Download URL surblclient-0.2.0-py3-none-any.whl
Size 82.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6150447d07e62f303c648c7d4dbb340ca042556fcbecd39010aaa6837ccb4c9b
BLAKE2b-256 checksum
How to use checksums
a957745f44bb3f2bf2faaeb90a648c7d28b05aacccb4c963949e6fc03b5dcc09
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for surblclient 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page