Security Commands

The security group provides SSL/TLS certificate inspection, HTTP security header auditing, email security record validation (SPF/DKIM/DMARC), subdomain discovery, and continuous certificate monitoring.

nadzoring security --help

security check-ssl

Inspect the SSL/TLS certificate of one or more domains. Checks expiry date, issuer, subject, Subject Alternative Names (SAN), public key strength, domain match, and which TLS protocol versions the server accepts (TLSv1.0 through TLSv1.3).

By default a compact summary table is displayed. Use --full to include the complete SAN list, protocol support details, chain length, and raw serial number.

nadzoring security check-ssl [OPTIONS] DOMAIN [DOMAIN ...]

Options

Option

Default

Description

--days-before / -d

7

Days before expiry at which to flag the certificate as warning

--no-verify

off

Disable certificate chain verification (useful for self-signed or internal CA certificates)

--full

off

Show all fields — SAN list, protocol support, chain length, serial number — instead of the compact summary

Status values

Status

Meaning

valid

Certificate is valid and not near expiry

warning

Fewer than --days-before days remaining before expiry

expired

Certificate has already passed its expiry date

error

Could not connect to the host or parse the certificate

Examples

# Compact summary (default)
nadzoring security check-ssl example.com

# 30-day warning window, multiple domains
nadzoring security check-ssl --days-before 30 google.com github.com cloudflare.com

# Skip chain verification — useful for self-signed certs
nadzoring security check-ssl --no-verify internal.corp.example.com

# Full details: SAN, protocol support, chain, serial number
nadzoring security check-ssl --full ya.ru

# Save full JSON report
nadzoring security check-ssl --full -o json --save ssl_report.json example.com github.com

Python API

from nadzoring.security.check_website_ssl_cert import (
    check_ssl_certificate,
    check_ssl_expiry,
    check_ssl_expiry_with_fallback,
)

# Verified check (full certificate chain validation)
result = check_ssl_certificate("example.com", days_before=14)

print(result["status"])           # 'valid' | 'warning' | 'expired' | 'error'
print(result["remaining_days"])   # e.g. 142
print(result["expiry_date"])      # '2025-10-15T12:00:00+00:00'
print(result["verification"])     # 'verified' | 'unverified' | 'failed'

# Subject and issuer
print(result["subject"]["CN"])    # 'example.com'
print(result["issuer"]["CN"])     # 'DigiCert TLS RSA SHA256 2020 CA1'
print(result["issuer"]["O"])      # 'DigiCert Inc'

# Subject Alternative Names
for san in result.get("san", []):
    print(san)                    # 'DNS:example.com', 'DNS:www.example.com', ...

# Domain match
print(result["domain_match"])     # True
print(result["matched_names"])    # ['DNS:example.com']

# Public key
key = result["public_key"]
print(key["algorithm"])           # 'RSA' | 'EC' | 'Ed25519' | ...
print(key.get("key_size"))        # 2048  (RSA / DSA only)
print(key.get("curve"))           # 'secp256r1'  (EC only)
print(key["strength"])            # 'weak' | 'good' | 'strong'

# TLS protocol support (TLSv1.0 – TLSv1.3)
protos = result["protocols"]
print(protos["supported"])        # ['TLSv1.2', 'TLSv1.3']
print(protos["has_outdated"])     # False — True if TLSv1.0 or TLSv1.1 supported

# Certificate chain (only when verify=True)
print(result.get("chain_length")) # 3
print(result.get("chain_valid"))  # True

# Expiry-only check (lighter)
result = check_ssl_expiry("example.com")

# Automatic fallback to unverified if chain check fails
result = check_ssl_expiry_with_fallback("self-signed.badssl.com")
print(result["verification"])     # 'unverified' when fallback was used

security check-headers

Analyse the HTTP security headers returned by one or more URLs. Reports which recommended headers are present or missing, identifies deprecated headers (e.g. X-XSS-Protection), lists information-leaking headers (e.g. Server, X-Powered-By), and produces a 0–100 coverage score.

nadzoring security check-headers [OPTIONS] URL [URL ...]

Options

Option

Default

Description

--timeout

10.0

HTTP request timeout in seconds

--no-verify

off

Disable SSL certificate verification for the request

Checked security headers

Header

Purpose

Strict-Transport-Security

Enforce HTTPS connections (HSTS)

Content-Security-Policy

Mitigate XSS and injection attacks

X-Content-Type-Options

Prevent MIME-type sniffing

X-Frame-Options

Prevent clickjacking

X-XSS-Protection

Legacy XSS filter (deprecated — flagged if present)

Referrer-Policy

Control referrer information leakage

Permissions-Policy

Restrict browser feature access

Cross-Origin-Embedder-Policy

Isolate cross-origin resources (COEP)

Cross-Origin-Opener-Policy

Isolate browsing context groups (COOP)

Cross-Origin-Resource-Policy

Control cross-origin resource sharing (CORP)

Cache-Control

Prevent caching of sensitive responses

Examples

# Single URL
nadzoring security check-headers https://example.com

# Batch audit — multiple services at once
nadzoring security check-headers \
    https://api.example.com \
    https://admin.example.com \
    https://static.example.com

# Internal endpoint — skip SSL verification
nadzoring security check-headers --no-verify https://internal.corp.example.com

# Slow server — increase timeout
nadzoring security check-headers --timeout 20 https://slow-api.example.com

# Export JSON for CI / dashboard
nadzoring security check-headers -o json --save headers_audit.json https://example.com

Python API

from nadzoring.security.http_headers import check_http_security_headers

result = check_http_security_headers(
    "https://example.com",
    timeout=10.0,
    verify_ssl=True,
)

print(result["url"])          # final URL after any redirects
print(result["status_code"])  # HTTP status code (e.g. 200)
print(result["score"])        # 0–100 coverage score

# Headers that are correctly set
for header, value in result["present"].items():
    print(f"  ✓ {header}: {value}")

# Recommended headers that are absent
for header in result["missing"]:
    print(f"  ✗ {header}")

# Deprecated headers found in the response
for header in result["deprecated"]:
    print(f"  ⚠ deprecated: {header}")

# Headers that leak server or technology information
for header, value in result["leaking"].items():
    print(f"  ⚠ leaking: {header} = {value}")

if result["error"]:
    print("Request failed:", result["error"])

security check-email

Validate the email security configuration of one or more domains. Checks:

  • SPF (Sender Policy Framework) — mechanism syntax, +all misuse, multiple SPF records, DNS lookup count.

  • DKIM (DomainKeys Identified Mail) — probes 13 common selector names.

  • DMARC (Domain-based Message Authentication, Reporting and Conformance) — parses p, sp, pct, rua, ruf tags and flags weak policies.

Note

Requires one of the following to be available:

  • dnspython Python package (pip install dnspython) — preferred; handles multi-chunk TXT records reliably.

  • dig system utility.

  • nslookup system utility (fallback).

nadzoring security check-email [OPTIONS] DOMAIN [DOMAIN ...]

Examples

# Single domain
nadzoring security check-email gmail.com

# Multiple domains
nadzoring security check-email google.com github.com cloudflare.com

# All owned domains at once
nadzoring security check-email corp.example.com mail.example.com newsletter.example.com

# Full JSON report
nadzoring security check-email -o json --save email_security.json example.com

# Quiet mode — results only, no progress output
nadzoring security check-email --quiet example.com

Python API

from nadzoring.security.email_security import check_email_security

result = check_email_security("example.com")

print(result["domain"])
print(result["overall_score"])  # 0 = none found, 3 = SPF + DKIM + DMARC all present

# SPF
spf = result["spf"]
print(spf["found"])             # True
print(spf["record"])            # 'v=spf1 include:_spf.example.com ~all'
print(spf["mechanisms"])        # ['include:_spf.example.com']
print(spf["all_qualifier"])     # '~' (softfail — recommended minimum)
for issue in spf["issues"]:
    print("  SPF issue:", issue)

# DKIM
dkim = result["dkim"]
print(dkim["found"])
print(dkim["selectors_checked"])  # list of 13 common selectors probed
for selector, record in dkim["records"].items():
    print(f"  DKIM selector '{selector}': {record[:60]}...")
for issue in dkim["issues"]:
    print("  DKIM issue:", issue)

# DMARC
dmarc = result["dmarc"]
print(dmarc["found"])
print(dmarc["record"])           # 'v=DMARC1; p=reject; rua=mailto:...'
print(dmarc["policy"])           # 'none' | 'quarantine' | 'reject'
print(dmarc["subdomain_policy"]) # value of sp= tag, or None
print(dmarc["pct"])              # percentage the policy applies to
print(dmarc["rua"])              # aggregate report destinations
print(dmarc["ruf"])              # forensic report destinations
for issue in dmarc["issues"]:
    print("  DMARC issue:", issue)

# All issues aggregated across SPF + DKIM + DMARC
for issue in result["all_issues"]:
    print("Issue:", issue)

Common issues reported

Category

Issue

SPF

No SPF record found

SPF

Multiple SPF records (RFC violation)

SPF

+all mechanism allows any sender (insecure)

SPF

Missing all mechanism

SPF

Exceeds 10 DNS lookup limit (RFC 7208)

DKIM

No DKIM records found for any common selector

DMARC

No DMARC record found

DMARC

Policy p=none does not protect against spoofing

DMARC

No aggregate report address (rua=) configured

DMARC

Policy applies to less than 100 % of messages (pct<100)


security subdomains

Discover subdomains for a target domain using two complementary methods:

  1. Certificate Transparency logs — queries the public crt.sh API for all certificates ever issued for the domain.

  2. DNS brute-force — resolves prefixes from a built-in wordlist of 80+ common names (or a custom file) concurrently using a thread pool.

Each discovered subdomain is tagged with its discovery source.

nadzoring security subdomains [OPTIONS] DOMAIN

Options

Option

Default

Description

--wordlist

built-in

Path to a custom wordlist file (one prefix per line). Pass an empty string to skip brute-force entirely.

--threads

20

Number of concurrent DNS resolution threads

--timeout

3.0

Per-host DNS resolution timeout in seconds

--no-bruteforce

off

Skip DNS brute-force; use CT logs only

Examples

# CT logs + built-in wordlist (default)
nadzoring security subdomains example.com

# CT logs only — faster, no DNS queries
nadzoring security subdomains --no-bruteforce example.com

# Custom wordlist, more threads, longer timeout
nadzoring security subdomains \
    --wordlist /path/to/big-wordlist.txt \
    --threads 100 \
    --timeout 5 \
    example.com

# Save results as JSON
nadzoring security subdomains -o json --save subdomains.json example.com

# Quiet mode for scripting
nadzoring security subdomains --quiet example.com

Python API

from nadzoring.security.subdomain_scan import scan_subdomains

# CT logs + built-in wordlist
results = scan_subdomains("example.com", max_threads=20, timeout=3.0)

for r in results:
    print(f"{r['subdomain']:40}  {r['ip']:16}  [{r['source']}]")
# source: 'ct_log' | 'brute_force'

# CT logs only
results = scan_subdomains("example.com", wordlist_path="")

# Custom wordlist
results = scan_subdomains(
    "example.com",
    wordlist_path="/path/to/custom_wordlist.txt",
    max_threads=50,
    timeout=5.0,
)

# Filter by source
ct_found    = [r for r in results if r["source"] == "ct_log"]
brute_found = [r for r in results if r["source"] == "brute_force"]
print(f"CT log: {len(ct_found)}  Brute-force: {len(brute_found)}")

Built-in wordlist categories

The built-in wordlist of 80+ prefixes covers:

  • Common web services: www, mail, ftp, smtp

  • Admin panels: admin, portal, cpanel, whm, plesk

  • APIs and apps: api, app, dev, staging, test, beta

  • Infrastructure: vpn, proxy, lb, waf, gateway, ns1, ns2

  • CDN and static assets: cdn, static, assets, media, images

  • DevOps tooling: git, gitlab, jenkins, ci, grafana, kibana

  • Databases: db, mysql, postgres, mongo, redis, elastic


security watch-ssl

Continuously monitor SSL/TLS certificates for one or more domains. On each check cycle the certificate is re-fetched and alerts are fired for:

  • Near expiry (status == warning, fewer than --days-before days remain)

  • Expired certificates (status == expired)

  • Certificate changes (expiry date differs from the previous cycle)

  • Check failures (connection error, parse error)

The command runs indefinitely until Ctrl-C, or for a fixed number of cycles when --cycles is specified.

nadzoring security watch-ssl [OPTIONS] DOMAIN [DOMAIN ...]

Options

Option

Default

Description

--interval / -i

3600

Seconds between full check cycles

--cycles / -c

0

Number of cycles to run (0 = run indefinitely)

--days-before / -d

7

Days before expiry to trigger a warning alert

Alert events

Trigger

Message example

Check failure

Check failed: [Errno 111] Connection refused

Expired certificate

Certificate has EXPIRED

Near expiry

Certificate expires in 5 day(s)

Certificate changed

Certificate changed: expiry 2025-06-01 2025-12-01

Examples

# Monitor one domain indefinitely (Ctrl-C to stop)
nadzoring security watch-ssl example.com

# Monitor multiple domains with a 14-day warning threshold
nadzoring security watch-ssl --days-before 14 \
    example.com github.com cloudflare.com api.example.com

# Check every 5 minutes for a critical service
nadzoring security watch-ssl --interval 300 api.example.com

# Run 10 cycles with a 60-second interval, save all results
nadzoring security watch-ssl \
    --cycles 10 --interval 60 \
    -o json --save ssl_monitor_history.json \
    example.com

# Quiet mode — suppress progress, emit only alert lines
nadzoring security watch-ssl --quiet --cycles 10 --interval 30 example.com

Python API

from nadzoring.security.ssl_monitor import SSLMonitor

monitor = SSLMonitor(
    domains=["example.com", "github.com", "cloudflare.com"],
    interval=3600,   # seconds between cycles
    days_before=14,  # alert when fewer than 14 days remain
)

# Custom alert handler — integrate with Slack, PagerDuty, email, etc.
def on_alert(domain: str, message: str) -> None:
    print(f"ALERT  {domain}: {message}")

monitor.set_alert_callback(on_alert)

# Run a fixed number of cycles (blocking call)
results = monitor.run_cycles(cycles=5)

# Or run indefinitely until KeyboardInterrupt
try:
    monitor.run()
except KeyboardInterrupt:
    pass

# Inspect the full check history
for entry in monitor.history():
    print(
        entry["domain"],
        entry["status"],
        entry["remaining_days"],
        entry["checked_at"],
    )