Domain, TLS & Email Auth Hygiene Auditor — Find the expiring cert, the spoofable domain and the dangling CNAME first.
One read-only pass over the domains you own: DNS and DNSSEC (validated by the resolver AD bit), the live TLS certificate chain, expiry and CAA, SPF expanded against the RFC 7208 10-lookup limit, DMARC, DKIM, MTA-STS and TLS-RPT, HTTPS security headers, dangling CNAME takeover risk and Certificate Transparency. Every finding comes with the exact DNS record, Nginx line, vercel.json block or Netlify _headers entry to fix it.
SKU: domain-infra-hygiene · Skills: domain-infra-hygiene-setup + domain-infra-hygiene · Runtime: 1,877 lines stdlib Python · Price: $19.00 USD one-time · card, Apple Pay or Google Pay
What It Does
- DNS & DNSSEC: A/AAAA/CNAME/NS/MX/CAA/TXT/DNSKEY over DNS-over-HTTPS (Cloudflare, Google fallback). DNSSEC is
VALIDATEDonly with a DNSKEY and theADbit; a DNSKEY withoutAD, or a SERVFAIL that resolves with checking disabled, is a broken chain (HIGH). Optional--expect-nscatches nameserver drift. - Email authentication: SPF expanded recursively and counted against the RFC 7208 10-lookup limit (
include,a,mx,ptr,exists,redirect), with loop,+all/?alland missing-include detection; DMARCp/sp/pct/rua; DKIM RSA key size per selector; MTA-STS and TLS-RPT; andv=spf1 -all+p=reject+ null MX0 .for domains that never send mail. - TLS & CAA: one handshake on :443 — chain verification, SAN match, protocol (< TLS 1.2 flagged), weak ciphers, expiry windows at 30/14/7 days, and the live issuer cross-checked against your
CAApolicy. - HTTPS, takeover & CT: HSTS (max-age,
includeSubDomains), CSP,X-Content-Type-Options,Referrer-Policy, frame protection,Permissions-Policy, the HTTP→HTTPS redirect, dangling-CNAME provider fingerprints on your own apex andwww, and Certificate Transparency issuers vs CAA. - Remediation & monitoring: copy-paste BIND records, Nginx
add_headerlines, avercel.jsonheaders block and a Netlify / Cloudflare Pages_headersfile;--state --deltafor daily or weekly routines that alert on cert rotation, NS/SPF/DMARC changes, DNSSEC loss, new CT names and header regressions.
How It Runs (6 Steps)
.wattcoin_license_check.py check --sku domain-infra-hygiene must return "state": "OK" before every run.domain_infra_hygiene.py and bot_common.py against the pinned values; any mismatch stops the run.AD-bit validation, NS drift, CAA, dangling CNAMEs.--state --delta events and fail-closed exit codes.Live audit: botappstore.com and coinloader.com
Sample disclosure: this is a representative sample, not a customer report. It was produced against public or fictional test targets; account and credential values are placeholders or redacted (for example wck_<REDACTED>), and no private keys, seed phrases or secrets are ever shown. Your own report will differ in content and length.
Unedited --format markdown output of /usr/bin/python3 -I /workspace/scripts/domain_infra_hygiene.py botappstore.com coinloader.com --format markdown on 2026-10-27 (exit 1: MEDIUM findings at the default --fail-on medium). Port 80 shows UNVERIFIED because the sandbox blocks outbound :80, which the report states rather than guessing.
# Domain, TLS & Email Auth Hygiene Report
```
snapshot 2026-10-27 20:04:30 UTC | tool=domain-infra-hygiene
endpoints api.certspotter.com, cloudflare-dns.com, http:80, https:443, tls:443
```
**Domains:** 2 | **Worst:** MEDIUM | Critical 0 · High 0 · Medium 7 · Low 12 · Info 5 | **Exit code:** 1 (fail-on=medium)
| Domain | Worst | Crit | High | Med | Low | Info | DNSSEC | Mail | TLS expires | HSTS |
|---|---|---:|---:|---:|---:|---:|---|---|---|---|
| `botappstore.com` | MEDIUM | 0 | 0 | 3 | 6 | 3 | VALIDATED | no mail | 2027-01-23 (87d) | max-age=63072000 |
| `coinloader.com` | MEDIUM | 0 | 0 | 4 | 6 | 2 | UNSIGNED | no mail | 2027-01-23 (87d) | max-age=63072000 |
## `botappstore.com`
### DNS
| Record | Value |
|---|---|
| A | 216.198.79.1 |
| AAAA | - |
| NS | ns-cloud-e1.googledomains.com, ns-cloud-e2.googledomains.com, ns-cloud-e3.googledomains.com, ns-cloud-e4.googledomains.com |
| MX | - |
| CAA | - |
| DNSKEY / AD | 3 / True (VALIDATED) |
| CNAME `www.botappstore.com` | d3b71d5b846c057b.vercel-dns-017.com (status 0, resolves yes) |
### Email authentication
| Check | Result |
|---|---|
| Sends mail | no |
| SPF | none |
| DMARC | none |
| DKIM | - (tried: default,google,selector1,selector2,k1,s1,s2,mail,dkim) |
| MTA-STS | - |
| TLS-RPT | - |
### TLS (:443)
| Field | Value |
|---|---|
| Protocol / cipher | TLSv1.3 / TLS_AES_128_GCM_SHA256 (128-bit) |
| Issuer | Let's Encrypt (YR1) |
| Subject CN | botappstore.com |
| Valid | 2026-10-25T18:49:02Z → 2027-01-23T18:49:01Z (87 days left) |
| Verified | yes |
| Serial | `052B27E3DE4DE0A54400DC114CE04F93737B` |
| SHA-256 | `5f2c20a8d0679eb3929c16c9747e52b583728be9973510d5b50f167f28a1cbc7` |
| SAN | botappstore.com, www.botappstore.com |
### HTTPS headers & port 80
| Header | Value |
|---|---|
| `strict-transport-security` | max-age=63072000 |
| `content-security-policy` | **missing** |
| `x-content-type-options` | **missing** |
| `referrer-policy` | **missing** |
| `x-frame-options` | **missing** |
| `permissions-policy` | **missing** |
| port 80 | UNVERIFIED (outbound port 80 unreachable/blocked) |
### Certificate Transparency
Source: certspotter · status **OK** · issuances 1 · issuers Let's Encrypt · names: `botappstore.com`, `www.botappstore.com`
### Findings
| Severity | Code | Finding |
|---|---|---|
| MEDIUM | `CSP_MISSING` | no Content-Security-Policy header (XSS / injection blast radius unbounded) |
| MEDIUM | `DMARC_MISSING` | no _dmarc record; domain sends no mail: publish 'v=DMARC1; p=reject;' |
| MEDIUM | `SPF_MISSING` | no SPF record; domain sends no mail: publish 'v=spf1 -all' to block spoofing |
| LOW | `CAA_MISSING` | no CAA record: any public CA may issue certificates for this domain |
| LOW | `FRAME_PROTECTION_MISSING` | no X-Frame-Options and no CSP frame-ancestors (clickjacking) |
| LOW | `HSTS_NO_INCLUDESUBDOMAINS` | HSTS lacks includeSubDomains: subdomains can still be downgraded |
| LOW | `PERMISSIONS_POLICY_MISSING` | no Permissions-Policy header |
| LOW | `REFERRER_POLICY_MISSING` | no Referrer-Policy header |
| LOW | `XCTO_MISSING` | X-Content-Type-Options is not 'nosniff' |
| INFO | `DNSSEC_VALIDATED` | DNSSEC validated (DNSKEY present, AD=true) |
| INFO | `MX_MISSING` | domain has no MX record; if it never receives mail publish RFC 7505 null MX '0 .' |
| INFO | `TLS_CERT_VALID` | certificate valid for 87 more days (expires 2027-01-23) |
### Copy-paste remediation
DNS zone records (BIND syntax; in Cloudflare/Route 53 enter name, type and value):
```dns
botappstore.com. 3600 IN TXT "v=spf1 -all"
_dmarc.botappstore.com. 3600 IN TXT "v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s"
botappstore.com. 3600 IN MX 0 .
botappstore.com. 3600 IN CAA 0 issue "letsencrypt.org"
botappstore.com. 3600 IN CAA 0 issuewild ";"
botappstore.com. 3600 IN CAA 0 iodef "mailto:security@botappstore.com"
```
Nginx (`server {}` block):
```nginx
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-Frame-Options "DENY" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
```
Vercel (`vercel.json`):
```json
{
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Strict-Transport-Security",
"value": "max-age=63072000; includeSubDomains; preload"
},
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "Referrer-Policy",
"value": "strict-origin-when-cross-origin"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "Permissions-Policy",
"value": "camera=(), microphone=(), geolocation=(), payment=(), usb=()"
}
]
}
]
}
```
Netlify / Cloudflare Pages (`_headers`):
```text
/*
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: DENY
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()
```
- CAA: list every CA your CDN/host uses (check its docs) before publishing, or renewals will fail.
- CSP: the starter policy blocks third-party scripts/styles; ship it first as Content-Security-Policy-Report-Only, add the origins you need, then enforce.
- HSTS: includeSubDomains/preload commit EVERY subdomain to HTTPS for the max-age; confirm none serve plain HTTP before adding them (drop 'preload' unless you will submit to hstspreload.org).
## `coinloader.com`
### DNS
| Record | Value |
|---|---|
| A | 216.198.79.1 |
| AAAA | - |
| NS | ns-cloud-d1.googledomains.com, ns-cloud-d2.googledomains.com, ns-cloud-d3.googledomains.com, ns-cloud-d4.googledomains.com |
| MX | - |
| CAA | - |
| DNSKEY / AD | 0 / False (UNSIGNED) |
| CNAME `www.coinloader.com` | 4fadf6ff311d808b.vercel-dns-017.com (status 0, resolves yes) |
### Email authentication
| Check | Result |
|---|---|
| Sends mail | no |
| SPF | none |
| DMARC | none |
| DKIM | - (tried: default,google,selector1,selector2,k1,s1,s2,mail,dkim) |
| MTA-STS | - |
| TLS-RPT | - |
### TLS (:443)
| Field | Value |
|---|---|
| Protocol / cipher | TLSv1.3 / TLS_AES_128_GCM_SHA256 (128-bit) |
| Issuer | Let's Encrypt (YR1) |
| Subject CN | coinloader.com |
| Valid | 2026-10-25T18:39:39Z → 2027-01-23T18:39:38Z (87 days left) |
| Verified | yes |
| Serial | `062EC72524AD6E41A2A97E1CC25618826213` |
| SHA-256 | `d29f5f52f61f4f0e9f32617f3087885b81bbcdbd95a0b452a534c80dbc75ff4e` |
| SAN | coinloader.com, www.coinloader.com |
### HTTPS headers & port 80
| Header | Value |
|---|---|
| `strict-transport-security` | max-age=63072000 |
| `content-security-policy` | **missing** |
| `x-content-type-options` | **missing** |
| `referrer-policy` | **missing** |
| `x-frame-options` | **missing** |
| `permissions-policy` | **missing** |
| port 80 | UNVERIFIED (outbound port 80 unreachable/blocked) |
### Certificate Transparency
Source: certspotter · status **OK** · issuances 5 · issuers Let's Encrypt · names: `coinloader.com`, `www.coinloader.com`
### Findings
| Severity | Code | Finding |
|---|---|---|
| MEDIUM | `CSP_MISSING` | no Content-Security-Policy header (XSS / injection blast radius unbounded) |
| MEDIUM | `DMARC_MISSING` | no _dmarc record; domain sends no mail: publish 'v=DMARC1; p=reject;' |
| MEDIUM | `DNSSEC_NOT_ENABLED` | DNSSEC not enabled (no DNSKEY at apex): DNS answers can be spoofed in transit |
| MEDIUM | `SPF_MISSING` | no SPF record; domain sends no mail: publish 'v=spf1 -all' to block spoofing |
| LOW | `CAA_MISSING` | no CAA record: any public CA may issue certificates for this domain |
| LOW | `FRAME_PROTECTION_MISSING` | no X-Frame-Options and no CSP frame-ancestors (clickjacking) |
| LOW | `HSTS_NO_INCLUDESUBDOMAINS` | HSTS lacks includeSubDomains: subdomains can still be downgraded |
| LOW | `PERMISSIONS_POLICY_MISSING` | no Permissions-Policy header |
| LOW | `REFERRER_POLICY_MISSING` | no Referrer-Policy header |
| LOW | `XCTO_MISSING` | X-Content-Type-Options is not 'nosniff' |
| INFO | `MX_MISSING` | domain has no MX record; if it never receives mail publish RFC 7505 null MX '0 .' |
| INFO | `TLS_CERT_VALID` | certificate valid for 87 more days (expires 2027-01-23) |
### Copy-paste remediation
DNS zone records (BIND syntax; in Cloudflare/Route 53 enter name, type and value):
```dns
coinloader.com. 3600 IN TXT "v=spf1 -all"
_dmarc.coinloader.com. 3600 IN TXT "v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s"
coinloader.com. 3600 IN MX 0 .
coinloader.com. 3600 IN CAA 0 issue "letsencrypt.org"
coinloader.com. 3600 IN CAA 0 issuewild ";"
coinloader.com. 3600 IN CAA 0 iodef "mailto:security@coinloader.com"
```
Nginx (`server {}` block):
```nginx
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-Frame-Options "DENY" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
```
Vercel (`vercel.json`):
```json
{
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Strict-Transport-Security",
"value": "max-age=63072000; includeSubDomains; preload"
},
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "Referrer-Policy",
"value": "strict-origin-when-cross-origin"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "Permissions-Policy",
"value": "camera=(), microphone=(), geolocation=(), payment=(), usb=()"
}
]
}
]
}
```
Netlify / Cloudflare Pages (`_headers`):
```text
/*
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: DENY
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()
```
- CAA: list every CA your CDN/host uses (check its docs) before publishing, or renewals will fail.
- DNSSEC: enable signing at your DNS host, then publish the DS record it shows at your registrar (Cloudflare: DNS > Settings > DNSSEC; Route 53: Hosted zone > DNSSEC signing).
- CSP: the starter policy blocks third-party scripts/styles; ship it first as Content-Security-Policy-Report-Only, add the origins you need, then enforce.
- HSTS: includeSubDomains/preload commit EVERY subdomain to HTTPS for the max-age; confirm none serve plain HTTP before adding them (drop 'preload' unless you will submit to hstspreload.org).
---
SCOPE_NOTE: Passive, read-only public posture inspection for domains the user owns or is authorised to assess: DNS-over-HTTPS lookups (Cloudflare, Google fallback), one TLS handshake on :443, one HTTPS GET for response headers, one plain-HTTP GET on :80 for the redirect check, and public Certificate Transparency logs (Cert Spotter, crt.sh fallback). No port scanning, brute-forcing, fuzzing, subdomain enumeration beyond public CT logs, mail-server or credential probing, and no exploitation; the dangling-CNAME check only inspects the audited domain's own records against known provider fingerprints and never claims or registers anything. Results are point-in-time: DNS caches, CT log lag, CDN edge variation and resolver behaviour can differ by location and minute; DKIM presence is only confirmed for the selectors tried; an empty crt.sh answer is INCONCLUSIVE, not clean. The HTTP->HTTPS redirect is reported UNVERIFIED when outbound port 80 is blocked (common in sandboxes and corporate networks). Remediation records are templates to review before publishing. Not a penetration test, compliance attestation, or legal advice. Refuses: scanning domains the user does not control, subdomain-takeover hunting against third parties, and credential or mail-server probing.
CLI Flags & Exit Codes
| Flag | Meaning |
|---|---|
DOMAIN ... | Domains you own or are authorised to assess (max 50; URLs reduced to host; IPs and special-use names rejected). |
--domains-file PATH | Read more domains from a file (whitespace separated, # comments). |
--dkim-selector SEL[,SEL] | DKIM selectors to probe (default: default, google, selector1, selector2, k1, s1, s2, mail, dkim). |
--no-mail | Assert the domain never sends mail: expect v=spf1 -all, p=reject and null MX 0 .. |
--no-http | Skip the HTTPS header GET and the port-80 redirect check. |
--no-tls | Skip the TLS handshake on :443. |
--ct / --no-ct | Query (default) or skip Certificate Transparency. |
--expect-ns NS[,NS] | Expected nameservers; any mismatch is NS_DRIFT (HIGH). |
--state PATH --delta | Persist snapshots and report events / new / changed / resolved since the last clean run. |
--format {text,markdown,json} | Output format (markdown adds the copy-paste remediation blocks). |
--json | Shortcut for JSON output. |
--out PATH | Write the report to a file instead of stdout. |
--fail-on {none,info,low,medium,high,critical} | Severity that triggers a non-zero exit (default medium). |
--timeout SECONDS | Per-request timeout (default 15). |
--offline PATH | Replay a JSON fixture instead of the network (tests, reproducible runs). |
| Exit code | Meaning |
|---|---|
0 | Completed; nothing at or above --fail-on. |
1 | Worst finding at the threshold is MEDIUM (or LOW/INFO with --fail-on low/info). |
2 | Worst finding at the threshold is HIGH. |
3 | Worst finding at the threshold is CRITICAL. |
10 | Bad input or refusal (invalid or special-use domain, bad flag, --delta without --state). |
11 | Degraded data: a DoH, TLS, HTTPS or CT source failed with nothing at the threshold. Never a clean pass. |
What It Does Not Do
- Own or authorised domains only. Refuses to scan third-party domains, build target lists, rank domains, or hunt subdomain takeovers against infrastructure you do not control.
- Passive only. Ordinary public lookups: DoH queries, one TLS handshake on :443, one HTTPS GET, one HTTP GET on :80 and CT log queries. No port scanning, brute-force or wordlist subdomain enumeration, fuzzing, zone transfers, SMTP or credential probing, and no exploitation.
- Never claims or changes anything. It never registers a dangling CNAME target and never edits DNS, certificates, web servers or CDN settings; remediation records are templates for you to review and publish.
- No “secure” verdicts. Results are point-in-time and resolver/edge dependent. DKIM is only confirmed for the selectors tried, and
UNVERIFIEDorINCONCLUSIVEare never passes. Not a penetration test, compliance attestation, or legal advice.
Scope note (printed with every report): passive, read-only public posture inspection for domains the user owns or is authorised to assess; an empty crt.sh answer is INCONCLUSIVE, not clean; the HTTP→HTTPS redirect is reported UNVERIFIED when outbound port 80 is blocked; remediation records are templates to review before publishing.
Pinned Runtime Artifacts (SHA-256)
/runtime/domain_infra_hygiene.py— SHA-256:73f7a04a48f55b6a9f0a733bdf4bba7cab2b9b6d245c0505567487585f0d1c14/runtime/bot_common.py— SHA-256:d1941b61b95ca664e4c1286c3bf1918c00e976c2c1601199b19200257650e526
Runtime URLs are license-gated: send X-License-Key: wcl_... or X-Agent-Key: wck_... (unauthenticated requests get 401). The served file carries a per-license watermark block (5 lines), so compare the X-Canonical-SHA256 response header (or the SHA-256 after stripping the block) against the pins above. The recommended path is the setup skill installer:
python3 /workspace/.wattcoin_license_check.py --sku domain-infra-hygiene --install-runtime ... --expected-sha256 73f7a04a48f55b6a9f0a733bdf4bba7cab2b9b6d245c0505567487585f0d1c14
The setup and audit skills verify both canonical digests before every run and refuse to execute either file on a mismatch. Both are locked at chmod 500.
Technical FAQ
How does the auditor count SPF DNS lookups against the RFC 7208 10-lookup limit?
How is DNSSEC validated, and what does the AD bit tell you?
How do I protect a domain that never sends email from being spoofed?
What does the TLS handshake check, and how is it cross-checked against CAA?
How do Certificate Transparency and dangling CNAME detection work?
How do stateful --state --delta monitoring routines work?
Licensing & Setup
$19.00 USD one-time, paid by card, Apple Pay or Google Pay: a 1-year license with up to 3 workspace-reset rebinds and no subscription. On Turn 1 the domain-infra-hygiene-setup skill installs the read-only license helper (0500), verifies and installs the SHA-256-pinned runtime, registers and activates your agent key, binds your wcl_... code through stdin, and offers a live smoke test. The license code is never printed in chat.
One wcl_... key, two runtimes. Your license key works inside Grok Bot and from any local terminal or IDE agent (Cursor, Claude Code, Windsurf, CI/CD) via the standalone single-file Python 3 CLI (/runtime/domain_infra_hygiene.py) — Python 3.10+ standard library only, zero pip install dependencies, with --json / --markdown output.