Skip to main content

Command Palette

Search for a command to run...

Building a Domain Security Analyzer — Project Setup & DNS Lookup

Published
8 min read

Ever wondered how security tools check if a domain is legit? In this series, we're building one from scratch — a tool that analyzes any domain's security configuration.

No fancy frameworks upfront. No overwhelming theory dumps. Just one step at a time, figuring things out as we go.

What We're Building

A CLI tool (eventually a web app) that:

  • Checks DNS records

  • Validates SSL certificates

  • Analyzes security headers

  • Gives a security score with recommendations

We'll start simple and add complexity only when we need it.

Today's Goal

By the end of this post, you'll have:

  1. A GitHub repository for the project

  2. A Python virtual environment

  3. A working script that fetches DNS A records for any domain


Prerequisites

Before we start, make sure you have:

Git — Download from git-scm.com

Python 3.12 — Download from python.org/downloads/release/python-31212. During installation, check "Add python.exe to PATH".

VSCode — Download from code.visualstudio.com. Install the Python extension from the Extensions tab.


Step 1: Create the GitHub Repository

Head to github.com/new and create a new repository:

  • Name: domain-security-analyzer

  • Description: "A tool to analyze domain security configurations"

  • Visibility: Public

  • Check "Add a README file"

  • Add a .gitignore template → select Python

Click Create repository.

Now clone it. Open VSCode, then open the folder where you keep projects (File → Open Folder). Open the terminal (View → Terminal or Ctrl+`) and run:

git clone https://github.com/YOUR_USERNAME/domain-security-analyzer.git
cd domain-security-analyzer

Step 2: Set Up the Project Structure

Create a source folder:

mkdir src
cd src

Why src and not backend?

backend implies there's a frontend — terminology for web apps where you have server code and client code. We don't have a web app yet. We have a simple Python script that runs in terminal.

src (short for "source") is a generic name meaning "this is where the code lives." It doesn't imply any architecture. Later when we add a web interface, we might restructure. For now, src is honest about what we have.


Step 3: Create a Virtual Environment

Why do we need this?

Say you have two projects on your computer:

Project A needs: requests version 2.25.0
Project B needs: requests version 2.31.0

Without a virtual environment, both projects share the same global Python. You install requests once, and it's version 2.31.0. Now Project A breaks because it needs the older version.

A virtual environment gives each project its own isolated Python with its own packages. They don't interfere with each other.

Creating the virtual environment:

py -3.12 -m venv venv

Breaking it down:

  • py -3.12 — run Python 3.12 specifically (Windows launcher)

  • -m venv — run the built-in venv module

  • venv — name of the folder to create (convention, you could call it anything)

This creates a venv/ folder containing a copy of Python 3.12 and an empty space for this project's packages.

Activating the virtual environment:

venv\Scripts\activate

On Mac/Linux, use source venv/bin/activate instead.

This modifies your terminal so that python and pip point to the virtual environment instead of global Python. You'll see (venv) in your prompt as confirmation.


Step 4: Install dnspython

What is DNS?

When you type google.com in your browser, your computer doesn't know where Google actually is. It asks a DNS server: "What's the IP address for google.com?" The DNS server responds with something like 142.250.193.46. Then your browser connects to that IP.

DNS is the internet's phone book — it translates human-readable domain names to machine-readable IP addresses.

What are A Records?

DNS stores different types of records:

TypeWhat it storesExample
AIPv4 address142.251.223.206
AAAAIPv6 address2607:f8b0:4004:800::200e
MXMail servermail.google.com
TXTText (SPF, DMARC)v=spf1 include:_spf.google.com

The "A" record is the most basic — it maps a domain name to an IPv4 address. We'll explore other record types in future posts.

Install the library:

pip install dnspython
pip freeze > requirements.txt

dnspython lets us query DNS servers programmatically.

What are dependencies?

Dependencies are external packages your code needs to run. Our code will have import dns.resolver — but Python doesn't have this built-in. It comes from dnspython. Our code depends on it.

The requirements.txt file saves these dependencies:

dnspython==2.6.1

If someone else clones your repo, they run pip install -r requirements.txt and get the exact same packages. Without this file, they'd see ModuleNotFoundError and have to guess what to install.


Step 5: Write the Analyzer Script

Create a file called analyzer.py in the src folder (right-click src in VSCode → New File):

import dns.resolver
import sys


def get_a_records(domain):
    """Fetch A records for a domain."""
    try:
        answers = dns.resolver.resolve(domain, "A")
        return [rdata.address for rdata in answers]
    except dns.resolver.NXDOMAIN:
        return f"Error: Domain '{domain}' does not exist"
    except dns.resolver.NoAnswer:
        return f"Error: No A records found for '{domain}'"
    except Exception as e:
        return f"Error: {e}"


def main():
    if len(sys.argv) != 2:
        print("Usage: python analyzer.py <domain>")
        print("Example: python analyzer.py google.com")
        sys.exit(1)

    domain = sys.argv[1]
    print(f"\n=== DNS Analysis for {domain} ===\n")

    print("A Records (IPv4 addresses):")
    result = get_a_records(domain)

    if isinstance(result, list):
        for ip in result:
            print(f"  → {ip}")
    else:
        print(f"  {result}")


if __name__ == "__main__":
    main()

Let's break it down:

import dns.resolver

Imports the DNS lookup functionality from the dnspython package we installed.

import sys

Imports Python's built-in sys module. We need it to read command-line arguments.

def get_a_records(domain):

Defines a function that takes a domain name and returns its IP addresses.

    try:
        answers = dns.resolver.resolve(domain, "A")

Asks DNS servers: "What are the A records for this domain?" The "A" specifies we want IPv4 addresses (not mail servers or other record types).

Different domains return different numbers of answers. Big websites often have multiple IPs for reliability — Google might return 2, Facebook might return 1, some return 5. The code handles any number.

        return [rdata.address for rdata in answers]

Loops through all answers and extracts each IP address into a list. If there are 3 IPs, you get a list of 3 strings.

    except dns.resolver.NXDOMAIN:
        return f"Error: Domain '{domain}' does not exist"

Catches the error when a domain doesn't exist (NXDOMAIN = "Non-Existent Domain").

    except dns.resolver.NoAnswer:
        return f"Error: No A records found for '{domain}'"

Catches when domain exists but has no A records.

    except Exception as e:
        return f"Error: {e}"

Catches any other unexpected error.

What are command-line arguments?

    if len(sys.argv) != 2:

When you run a program in terminal, you can pass extra information after the program name:

python analyzer.py google.com

Here google.com is a command-line argument. Python stores these in sys.argv:

sys.argv[0] = "analyzer.py"  # script name
sys.argv[1] = "google.com"   # first argument

Why use arguments?

Without them, you'd have to edit the code every time:

domain = "google.com"  # want github.com? edit this line

With arguments, one script works for any domain:

python analyzer.py google.com
python analyzer.py github.com
python analyzer.py facebook.com

No code changes — the domain comes from outside the program.

Same reason pip install dnspython works. Imagine editing pip's code every time you want a different package!

    domain = sys.argv[1]

Grabs the domain from the command line.

    if isinstance(result, list):
        for ip in result:
            print(f"  → {ip}")
    else:
        print(f"  {result}")

If we got a list of IPs (success), print each one. If we got an error string, print that instead.

What does if __name__ == "__main__" mean?

if __name__ == "__main__":
    main()

This confused me too at first. Here's what it does:

When you run directly: python analyzer.py google.com

Python sets __name__ = "__main__", so main() runs.

When you import from another file:

# in some other file
import analyzer
ips = analyzer.get_a_records("google.com")

Python sets __name__ = "analyzer", so main() does NOT run.

Why does this matter? Without the check, importing the file would automatically run main() and print stuff. You don't want that — you just want to borrow the function.

Think of it like a restaurant kitchen: if a customer walks in the front door, serve them food. If another chef borrows your recipe, just give them the recipe — don't start cooking.


Step 6: Test It

From the src folder:

python analyzer.py google.com

You should see:

=== DNS Analysis for google.com ===

A Records (IPv4 addresses):
  → 142.251.223.206

Try a fake domain:

python analyzer.py thisfakesitedoesnotexist123.com
=== DNS Analysis for thisfakesitedoesnotexist123.com ===

A Records (IPv4 addresses):
  Error: Domain 'thisfakesitedoesnotexist123.com' does not exist

The error handling works. We've built something that talks to DNS servers.


What We Have Now

domain-security-analyzer/
├── src/
│   ├── analyzer.py
│   ├── requirements.txt
│   └── venv/
├── .gitignore
└── README.md

Commit Your Progress

Navigate to your project root and push:

cd ..
git add .
git commit -m "Add DNS A record lookup"
git push origin main

What's Next

In the next post, we'll add more DNS record types — MX (mail servers), SPF, and DMARC. These records reveal whether a domain is protected against email spoofing. We'll start seeing real security insights.

But for now — you have a working DNS lookup tool. That's the foundation of every domain security scanner.

See you in the next one.


Find the code for this post on GitHub.

More from this blog

Figure it Out

15 posts