# Spoofy: checking SPF and DMARC spoofability from a tested lookup table

> Spoofy is a Python tool that decides whether a domain can be spoofed in email, using a hand-built table of SPF and DMARC combinations tested against a real mail provider rather than a rule engine. It reports a code, a boolean verdict and a per-domain breakdown, and it needs Python 3.9 or above.

**MattKeeley/Spoofy** — Spoofy is a program that checks if a list of domains can be spoofed based on SPF and DMARC records.

- Repository: https://github.com/MattKeeley/Spoofy
- Stars: 774 · Forks: 83
- Language: Python
- License: NOASSERTION
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/mattkeeley-spoofy

## What Spoofy actually decides

Spoofy takes a list of domains and tells you, for each one, whether mail can be spoofed to look like it came from that domain. The decision is driven by two DNS records: SPF, evaluated per RFC 7208, and DMARC, evaluated by an RFC 9989 tree walk that includes the sp and np tags for subdomains.

The output is a code and a boolean. The code runs from 0 to 9 and the table in the README maps each one. Code 0 means spoofing possible, 1 subdomain spoofing possible, 2 organisational domain spoofing possible, 3 spoofing might be possible with p=quarantine and pct below 100, 4 mailbox dependent, 5 organisational domain spoofing might be possible, 6 subdomain spoofing might be possible, 7 a combination of subdomain possible and organisational domain maybe, 8 spoofing is not possible, and 9 unable to determine because a DNS lookup failed.

The SPOOFING_POSSIBLE column is true for codes 0, 1, 2 and 7, null for the three mailbox dependent cases and for 3 and 9, and false only for code 8. That null is the interesting part of the design, because a lot of the ambiguity in this area is genuinely unresolvable without sending real mail to a real inbox.

The code is derived from the domain's SPF all mechanism together with the DMARC p, sp and aspf tags as the record writes them, not from a simplified approximation.

## Why a tested table instead of a rule engine

The README anticipates the obvious question, why another tool for checking whether a domain can be spoofed, and answers with a list of five differences.

The first is custom, manually tested spoof logic, with the claim of real world test results rather than guessing or speculating. The second is standards-based record discovery, RFC 7208 for SPF and the RFC 9989 DMARC tree walk including sp and np for subdomains. The third is accurate bulk lookups over a shared caching resolver with failover across 1.1.1.1, 8.8.8.8 and 9.9.9.9. The fourth is an SPF DNS query and void lookup counter plus detection of unregistered SPF include domains. The fifth is optional DKIM selector enumeration via an API.

The table itself is the asset. The README states that the spoofability table lists every combination of SPF and DMARC configurations that affect deliverability to the inbox, except for DKIM modifiers, and links to a downloadable master spreadsheet. The code holds that spreadsheet as data, and a module command rewrites the data after the spreadsheet changes while a test file checks that every row is reproduced.

```bash
python3 -m spoofy.master_table
```

So the logic is data, and the data has a single source in the spreadsheet. That is unusual and it is the reason the verdicts can be audited by a human rather than reverse-engineered from the source.

## The untested combinations and how they are resolved

Real DNS does not always produce a configuration the table covers, so the README documents the fallbacks in detail, and each one is a defensible decision rather than an omission.

An enforcing p with aspf but no sp leaves 24 untested combinations. Since sp defaults to p, the tested row with sp written out is used. A DMARC record inherited from a parent domain, meaning a subdomain with no _dmarc record of its own, takes the subdomain outcome tested for the parent's SPF and DMARC records, with np in place of sp when the subdomain does not exist. A pct below 100 with p=quarantine gives code 3, because the unsampled mail gets p=none, while with p=reject the unsampled mail is still quarantined, so pct does not change the verdict. A t=y testing mode under RFC 9989 drops the policy one level, from reject to quarantine or from quarantine to none, before the lookup. A failed DNS lookup gives code 9 rather than being treated as a missing record.

That last rule is the one to remember operationally. A resolver hiccup produces an unknown, not a pass, and a bulk run over thousands of domains will contain a few of those.

## Running Spoofy on one domain or on a list

The tool installs from PyPI and needs Python 3.9 or above.

```bash
pip3 install spoofy
spoofy -d example.com
```

To run it from a clone instead, install the requirements and run the script in place of the console entry point.

```bash
pip3 install -r requirements.txt
./spoofy.py
```

The usage text in the README lists the two invocation forms, one for a single domain and one for a file of domains, with the options. The -d flag processes a single domain, -iL provides a file containing a list of domains to process where blank lines and comments are skipped, -o selects the output format of stdout as the default, xls or json, -t sets the number of threads with a default of 4, --dkim enables DKIM selector enumeration via the API, and --dns-server queries a resolver of your choosing instead of the default three.

The examples show the combinations in practice.

```bash
    spoofy -d example.com -t 10
    spoofy -d example.com --dkim
    spoofy -iL domains.txt -o xls
    spoofy -iL domains.txt -o json --dkim
```

A domain list with comments and blank lines is a plain text file, which makes it easy to keep a list of your own domains and re-run the audit whenever the DNS records change.

## Where Spoofy is the wrong tool

The first limitation is the licence, and it is a hard one for anything commercial. The project is licensed under the Creative Commons Attribution-NonCommercial 4.0 International Licence, and the package metadata repeats it as CC-BY-NC-4.0 with the licence file included in the distribution. GitHub itself cannot classify the repository licence, which is a hint that the terms are not a standard software licence in the usual sense. A commercial organisation that wants to run this against its own domain portfolio needs a different arrangement.

The second limitation is scope. Spoofy reads DNS records and decides what a receiving mailbox would do. It does not send mail, it does not test DKIM beyond optional selector enumeration, and it does not model everything a receiving provider does. The README's own methodology section is candid about this, noting that Protonmail and Gmail use reverse lookup checks that affected the subdomain spoofing results, and that Microsoft 365 was chosen for the main testing because it offered greater control over the handling of mail.

The third is that the table reflects the mail handling of one major provider plus some retesting on two others. A recipient on a different provider may deliver differently, which is exactly why codes 4, 5 and 6 exist and return null rather than a boolean. If you need certainty for a specific domain, the table will tell you it is mailbox dependent, and only actual mail tests will settle it.

The fourth is the DKIM coverage. The spoofability table covers every SPF and DMARC combination except DKIM modifiers, so a domain whose protection rests on DKIM rather than on DMARC alignment is outside what the table can reason about.

## How the results were produced, and the maintenance picture

The methodology section explains where the table came from, and it is worth reading because it tells you how much weight to put on a verdict. The creation of the table involved listing every relevant SPF and DMARC configuration, combining them, and then collecting SPF and DMARC information using an early version of Spoofy on a large number of US government domains.

Whether a combination was spoofable was then determined with an email security pentesting suite at emailspooftest.com using Microsoft 365. Some combinations were retested on Protonmail and Gmail because those services can place spoofed mail in the inbox with a banner, or in spam without one, which is what produced the mailbox dependent codes. The README notes the testing and data collection took several days.

The project is not archived and the last push was on 2026-09-27, so it is still being worked on. The published release history is slower, with v1.0.3 dated 2025-12-08, v1.0.2 from August 2024 and v1.0.1 from March 2023, and the package metadata carries version 1.1.1, which suggests work has happened on the main branch that has not been tagged as a GitHub release.

The disclaimer is explicit that the tool is for testing and academic purposes only and can only be used where strict consent has been given, and that using it is the end user's responsibility to obey applicable law. For a tool that queries the public DNS records of a domain you may not own, that sentence is the real usage boundary, not the licence file.

## Conclusion

Spoofy suits a security engineer auditing a portfolio of domains for SPF and DMARC weaknesses, since the verdicts come from a tested table rather than from guesswork and a failed lookup is reported as unknown rather than as a pass. It does not suit commercial use, because the licence is Creative Commons Attribution-NonCommercial 4.0, and it does not send any mail itself. Verify your Python version first, at 3.9 or above, and treat code 9 as an unanswered question rather than a clean result.

## FAQ

### How do I install and run Spoofy?

Spoofy requires Python 3.9 or above and installs from PyPI with pip3 install spoofy, then runs against a domain with spoofy -d example.com. From a clone you can instead run pip3 install -r requirements.txt and use ./spoofy.py in place of the console command.

### What do the Spoofy result codes mean?

Codes 0 to 2 mean spoofing, subdomain spoofing and organisational domain spoofing are possible, code 7 mixes subdomain possible with organisational maybe, code 8 means spoofing is not possible, codes 3 to 6 are the might-be-possible and mailbox dependent cases, and code 9 means a DNS lookup failed so the result is unknown.

### Can Spoofy check a list of domains at once?

Yes. The -iL option takes a file containing a list of domains, with blank lines and comments skipped, and the -t option sets the number of threads with a default of 4. Output can be written as stdout by default, xls or json.

### What licence does Spoofy use and can I use it commercially?

The project is licensed under the Creative Commons Attribution-NonCommercial 4.0 International Licence, and the package metadata sets license to CC-BY-NC-4.0. The non-commercial term means a company would need a different arrangement before running it, and the README also states the tool is for testing and academic purposes where strict consent has been given.

## Sources

- [Issues](https://github.com/MattKeeley/Spoofy/issues)
- [MattKeeley/Spoofy on GitHub](https://github.com/MattKeeley/Spoofy)
- [README](https://github.com/MattKeeley/Spoofy/blob/main/README.md)
- [Releases](https://github.com/MattKeeley/Spoofy/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mattkeeley-spoofy
