Zone Transfer
Test whether a domain's nameservers allow DNS zone transfer (AXFR), and show the zone contents when they do.
Warning: Authorised use only. This tool contacts nameservers and can expose a full zone. Use it only on systems you are allowed to test.
Install
Requires Python 3.10+. Runtime dependencies (dnspython, prettytable,
and colored) install with the package.
pip install lupaxa-zone-transfer
zone-transfer --help
You can also run python -m lupaxa.zone_transfer.
CLI
zone-transfer example.com
zone-transfer example.com example.org
zone-transfer example.com --nameserver ns1.example.net --nameserver 203.0.113.10
zone-transfer example.com --format json
zone-transfer example.com --fail-open
zone-transfer example.com --timeout 10 --no-color
python -m lupaxa.zone_transfer --version
Each domain is inspected independently. The tool discovers NS records
(or uses --nameserver for every domain and skips discovery), sorts
hosts by name with IPv4 before IPv6, and tries AXFR on every address.
On a TTY a spinner on stderr shows the current lookup or transfer.
--fail-open exits 2 if any server allowed AXFR.
| Flag | Default | Description |
|---|---|---|
--nameserver, -n |
discover NS |
Nameserver host or IP (repeatable) |
--format, -f |
table |
Output format: table or json |
--fail-open |
off | Exit 2 if any endpoint allowed AXFR |
--timeout |
10 |
DNS and AXFR timeout in seconds (must be > 0) |
--no-color |
off | Disable colour in table output |
--version |
— | Print the package version and exit |
Table output is titled with the domain. Rows are nameserver, address,
family, status, and reason. For each allowed attempt a second table
lists name, type, TTL, and rdata. On a colour terminal, allowed is
red, refused is green, and error is grey. Pass --no-color or set
NO_COLOR for plain text.
JSON is a list of {domain, error, attempts} objects. Each attempt has
nameserver, address, family, status, reason, and records.
Record objects use type (not rtype). error is a string, never
null. records is always a list.
[
{
"domain": "example.com",
"error": "",
"attempts": [
{
"nameserver": "ns1.example.net",
"address": "203.0.113.10",
"family": "IPv4",
"status": "allowed",
"reason": "",
"records": [
{"name": "@", "type": "SOA", "ttl": 3600, "rdata": "ns1.example.net. hostmaster. 1 1 1 1 1"}
]
}
]
}
]
Attempt Status
| Status | Meaning |
|---|---|
allowed |
AXFR succeeded; records holds the zone |
refused |
Server answered not permitted (REFUSED / FORMERR) |
error |
Timeout, connect failure, or no addresses |
Exit Codes
| Code | When |
|---|---|
0 |
Every domain was tested; refused and per-endpoint errors are OK |
2 |
Invalid input, NS discovery failure, or --fail-open + allowed |
A domain-level NS discovery failure sets error and exits 2. A
refused transfer is a normal result and exits 0 unless --fail-open
saw an allowed attempt.
Library
from lupaxa.zone_transfer import inspect_domain, inspect_many
report = inspect_domain("example.com")
pinned = inspect_domain("example.com", nameservers=["203.0.113.10"])
reports = inspect_many(["example.com", "example.org"], timeout=10.0)
for attempt in report.attempts:
print(attempt.endpoint.address, attempt.status)
for record in attempt.records:
print(record.name, record.rtype, record.rdata)
inspect_domain does not raise when AXFR is refused or one address
fails. Those become attempt rows. Empty domains, an empty nameserver
list, or an empty inspect_many list raise InvalidTargetError.
NameserverLookupError is raised by lookup_nameservers;
inspect_domain catches it into DomainReport.error.
ZoneTransferError is the base class.
Pass on_progress to receive Progress events (domain, phase,
message, current, total). Phases are lookup (NS discovery),
resolve (A/AAAA expansion), and try (one AXFR). The library does
not print; the CLI uses that hook for the spinner.
Development
make init
make python-install-dev
make python-check
Metadata
Release files for lupaxa-zone-transfer 0.1.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| lupaxa_zone_transfer-0.1.5.tar.gz | 11.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lupaxa_zone_transfer-0.1.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 26.9 kB
Release files / lupaxa_zone_transfer-0.1.5.tar.gz
| Download URL | lupaxa_zone_transfer-0.1.5.tar.gz |
|---|---|
| Size | 11.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0318bb9b709b6744050ffd2ada79986b36008947abb61a93cd66d21a94637628
|
|
BLAKE2b-256 checksum How to use checksums |
309c2a9c75309efff0305bf7fdc6bcd096a544a8e22be1361a009b1e271069b4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.
Transparency logRelease files / lupaxa_zone_transfer-0.1.5-py3-none-any.whl
| Download URL | lupaxa_zone_transfer-0.1.5-py3-none-any.whl |
|---|---|
| Size | 15.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2974907a51c7120ab9f64efed1577a1a912d7eed06d236591da0d08ad75adacb
|
|
BLAKE2b-256 checksum How to use checksums |
5de2d322e1624fa1e509dbd5d9ce17a19680fe39cfd7f7317181f40dbe6a9f40
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.
Transparency log