krz/domain-dig

an ios app for DNS & SSL analysis

clone: git clone https://gitbay.org/krz/domain-dig.git

ede15c586af71b8d6f2c190ffdad0324414cac06

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-04-03T21:17:43Z

fix: various bugs

- fix Email section to left align
- ensure container contents can overflow scroll but page cannot
- fix map width
 DomainDig.xcodeproj/project.pbxproj |   8 +-
 DomainDig/ContentView.swift         |  77 +++++++++-------
 DomainDig/HistoryView.swift         |  77 +++++++++-------
 claude.md                           | 171 ------------------------------------
 4 files changed, 94 insertions(+), 239 deletions(-)

diff --git a/DomainDig.xcodeproj/project.pbxproj b/DomainDig.xcodeproj/project.pbxproj
index f37b290..12f8baf 100644
--- a/DomainDig.xcodeproj/project.pbxproj
+++ b/DomainDig.xcodeproj/project.pbxproj
@@ -265,7 +265,7 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 3;
+				CURRENT_PROJECT_VERSION = 4;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -282,7 +282,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 1.2;
+				MARKETING_VERSION = 1.2.1;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -301,7 +301,7 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 3;
+				CURRENT_PROJECT_VERSION = 4;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -318,7 +318,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 1.2;
+				MARKETING_VERSION = 1.2.1;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
diff --git a/DomainDig/ContentView.swift b/DomainDig/ContentView.swift
index 940a25b..b6a7607 100644
--- a/DomainDig/ContentView.swift
+++ b/DomainDig/ContentView.swift
@@ -7,7 +7,7 @@ struct ContentView: View {
 
     var body: some View {
         NavigationStack {
-            ScrollView {
+            ScrollView(.vertical) {
                 VStack(spacing: 0) {
                     inputSection
                     if viewModel.hasRun {
@@ -225,7 +225,7 @@ struct ContentView: View {
                     .background(Color(.systemGray6).opacity(0.5))
                     .cornerRadius(6)
             } else {
-                VStack(alignment: .leading, spacing: 4) {
+                horizontallyScrollableCard {
                     ForEach(viewModel.redirectChain) { hop in
                         HStack(alignment: .top, spacing: 6) {
                             Text("\(hop.stepNumber)")
@@ -248,9 +248,6 @@ struct ContentView: View {
                         }
                     }
                 }
-                .padding(10)
-                .background(Color(.systemGray6).opacity(0.5))
-                .cornerRadius(6)
             }
         }
         .padding(.top, 16)
@@ -281,7 +278,7 @@ struct ContentView: View {
     }
 
     private var ptrRow: some View {
-        VStack(alignment: .leading, spacing: 4) {
+        horizontallyScrollableCard {
             Text("PTR (Reverse DNS)")
                 .font(.system(.subheadline, design: .monospaced))
                 .fontWeight(.semibold)
@@ -302,13 +299,10 @@ struct ContentView: View {
                     .foregroundStyle(.secondary)
             }
         }
-        .padding(10)
-        .background(Color(.systemGray6).opacity(0.5))
-        .cornerRadius(6)
     }
 
     private func dnsRecordSection(_ section: DNSSection) -> some View {
-        VStack(alignment: .leading, spacing: 4) {
+        horizontallyScrollableCard {
             Text(section.recordType.rawValue)
                 .font(.system(.subheadline, design: .monospaced))
                 .fontWeight(.semibold)
@@ -334,9 +328,6 @@ struct ContentView: View {
                 dnsRecordRows(section.wildcardRecords)
             }
         }
-        .padding(10)
-        .background(Color(.systemGray6).opacity(0.5))
-        .cornerRadius(6)
     }
 
     private func dnsRecordRows(_ records: [DNSRecord]) -> some View {
@@ -369,16 +360,14 @@ struct ContentView: View {
             } else if let error = viewModel.emailSecurityError {
                 errorLabel(error)
             } else if let email = viewModel.emailSecurity {
-                VStack(alignment: .leading, spacing: 6) {
+                horizontallyScrollableCard(spacing: 6) {
                     emailSecurityRow("SPF", record: email.spf)
                     emailSecurityRow("DMARC", record: email.dmarc)
                     emailSecurityRow("DKIM", record: email.dkim)
                 }
-                .padding(10)
-                .background(Color(.systemGray6).opacity(0.5))
-                .cornerRadius(6)
             }
         }
+        .frame(maxWidth: .infinity, alignment: .leading)
         .padding(.top, 16)
     }
 
@@ -434,7 +423,7 @@ struct ContentView: View {
     }
 
     private func sslDetail(_ info: SSLCertificateInfo) -> some View {
-        VStack(alignment: .leading, spacing: 8) {
+        horizontallyScrollableCard(spacing: 8) {
             certRow("Common Name", info.commonName)
             certRow("Issuer", info.issuer)
 
@@ -466,9 +455,6 @@ struct ContentView: View {
 
             certRow("Chain Depth", "\(info.chainDepth)")
         }
-        .padding(10)
-        .background(Color(.systemGray6).opacity(0.5))
-        .cornerRadius(6)
     }
 
     // MARK: - HTTP Headers
@@ -484,7 +470,7 @@ struct ContentView: View {
             } else if let error = viewModel.httpHeadersError {
                 errorLabel(error)
             } else {
-                VStack(alignment: .leading, spacing: 4) {
+                horizontallyScrollableCard {
                     ForEach(viewModel.httpHeaders) { header in
                         HStack(alignment: .top, spacing: 4) {
                             Text(header.name + ":")
@@ -497,9 +483,6 @@ struct ContentView: View {
                         }
                     }
                 }
-                .padding(10)
-                .background(Color(.systemGray6).opacity(0.5))
-                .cornerRadius(6)
             }
         }
         .padding(.top, 16)
@@ -533,14 +516,17 @@ struct ContentView: View {
 
     private func ipGeolocationDetail(_ geo: IPGeolocation) -> some View {
         VStack(alignment: .leading, spacing: 6) {
-            certRow("IP", geo.ip)
-            if let org = geo.org {
-                certRow("Org / ISP", org)
-            }
-            let location = [geo.city, geo.region, geo.country_name].compactMap { $0 }.joined(separator: ", ")
-            if !location.isEmpty {
-                certRow("Location", location)
+            horizontallyScrollableContent(spacing: 6) {
+                certRow("IP", geo.ip)
+                if let org = geo.org {
+                    certRow("Org / ISP", org)
+                }
+                let location = [geo.city, geo.region, geo.country_name].compactMap { $0 }.joined(separator: ", ")
+                if !location.isEmpty {
+                    certRow("Location", location)
+                }
             }
+
             if let lat = geo.latitude, let lon = geo.longitude {
                 let coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lon)
                 Map(initialPosition: .region(MKCoordinateRegion(
@@ -550,6 +536,7 @@ struct ContentView: View {
                     Marker(geo.ip, coordinate: coordinate)
                 }
                 .mapStyle(.standard)
+                .frame(maxWidth: .infinity)
                 .frame(height: 180)
                 .cornerRadius(8)
             }
@@ -620,6 +607,32 @@ struct ContentView: View {
         }
     }
 
+    private func horizontallyScrollableCard<Content: View>(
+        spacing: CGFloat = 4,
+        @ViewBuilder content: () -> Content
+    ) -> some View {
+        horizontallyScrollableContent(spacing: spacing) {
+            content()
+        }
+        .padding(10)
+        .background(Color(.systemGray6).opacity(0.5))
+        .cornerRadius(6)
+    }
+
+    private func horizontallyScrollableContent<Content: View>(
+        spacing: CGFloat = 4,
+        @ViewBuilder content: () -> Content
+    ) -> some View {
+        ScrollView(.horizontal) {
+            VStack(alignment: .leading, spacing: spacing) {
+                content()
+            }
+            .scrollTargetLayout()
+        }
+        .scrollBounceBehavior(.basedOnSize, axes: .horizontal)
+        .frame(maxWidth: .infinity, alignment: .leading)
+    }
+
     private func errorLabel(_ message: String) -> some View {
         Label(message, systemImage: "exclamationmark.triangle.fill")
             .font(.system(.caption, design: .monospaced))
diff --git a/DomainDig/HistoryView.swift b/DomainDig/HistoryView.swift
index b64ebf3..1963509 100644
--- a/DomainDig/HistoryView.swift
+++ b/DomainDig/HistoryView.swift
@@ -65,7 +65,7 @@ struct HistoryDetailView: View {
     }()
 
     var body: some View {
-        ScrollView {
+        ScrollView(.vertical) {
             VStack(alignment: .leading, spacing: 0) {
                 cachedBanner
                 reachabilitySection
@@ -157,7 +157,7 @@ struct HistoryDetailView: View {
                         .background(Color(.systemGray6).opacity(0.5))
                         .cornerRadius(6)
                 } else {
-                    VStack(alignment: .leading, spacing: 4) {
+                    horizontallyScrollableCard {
                         ForEach(entry.redirectChain) { hop in
                             HStack(alignment: .top, spacing: 6) {
                                 Text("\(hop.stepNumber)")
@@ -180,9 +180,6 @@ struct HistoryDetailView: View {
                             }
                         }
                     }
-                    .padding(10)
-                    .background(Color(.systemGray6).opacity(0.5))
-                    .cornerRadius(6)
                 }
             }
         }
@@ -195,7 +192,7 @@ struct HistoryDetailView: View {
         VStack(alignment: .leading, spacing: 12) {
             sectionHeader("DNS Records")
             ForEach(entry.dnsSections) { section in
-                VStack(alignment: .leading, spacing: 4) {
+                horizontallyScrollableCard {
                     Text(section.recordType.rawValue)
                         .font(.system(.subheadline, design: .monospaced))
                         .fontWeight(.semibold)
@@ -220,12 +217,9 @@ struct HistoryDetailView: View {
                         recordRows(section.wildcardRecords)
                     }
                 }
-                .padding(10)
-                .background(Color(.systemGray6).opacity(0.5))
-                .cornerRadius(6)
 
                 if section.recordType == .A {
-                    VStack(alignment: .leading, spacing: 4) {
+                    horizontallyScrollableCard {
                         Text("PTR (Reverse DNS)")
                             .font(.system(.subheadline, design: .monospaced))
                             .fontWeight(.semibold)
@@ -242,9 +236,6 @@ struct HistoryDetailView: View {
                                 .foregroundStyle(.secondary)
                         }
                     }
-                    .padding(10)
-                    .background(Color(.systemGray6).opacity(0.5))
-                    .cornerRadius(6)
                 }
             }
         }
@@ -259,16 +250,14 @@ struct HistoryDetailView: View {
         VStack(alignment: .leading, spacing: 12) {
             if let email = entry.emailSecurity {
                 sectionHeader("Email Security")
-                VStack(alignment: .leading, spacing: 6) {
+                horizontallyScrollableCard(spacing: 6) {
                     historyEmailRow("SPF", record: email.spf)
                     historyEmailRow("DMARC", record: email.dmarc)
                     historyEmailRow("DKIM", record: email.dkim)
                 }
-                .padding(10)
-                .background(Color(.systemGray6).opacity(0.5))
-                .cornerRadius(6)
             }
         }
+        .frame(maxWidth: .infinity, alignment: .leading)
         .padding(.top, 16)
     }
 
@@ -310,7 +299,7 @@ struct HistoryDetailView: View {
         VStack(alignment: .leading, spacing: 12) {
             if let info = entry.sslInfo {
                 sectionHeader("SSL / TLS Certificate")
-                VStack(alignment: .leading, spacing: 8) {
+                horizontallyScrollableCard(spacing: 8) {
                     labelRow("Common Name", info.commonName)
                     labelRow("Issuer", info.issuer)
 
@@ -341,9 +330,6 @@ struct HistoryDetailView: View {
 
                     labelRow("Chain Depth", "\(info.chainDepth)")
                 }
-                .padding(10)
-                .background(Color(.systemGray6).opacity(0.5))
-                .cornerRadius(6)
             }
         }
         .padding(.top, 16)
@@ -355,7 +341,7 @@ struct HistoryDetailView: View {
         VStack(alignment: .leading, spacing: 12) {
             if !entry.httpHeaders.isEmpty {
                 sectionHeader("HTTP Headers")
-                VStack(alignment: .leading, spacing: 4) {
+                horizontallyScrollableCard {
                     ForEach(entry.httpHeaders) { header in
                         HStack(alignment: .top, spacing: 4) {
                             Text(header.name + ":")
@@ -368,9 +354,6 @@ struct HistoryDetailView: View {
                         }
                     }
                 }
-                .padding(10)
-                .background(Color(.systemGray6).opacity(0.5))
-                .cornerRadius(6)
             }
         }
         .padding(.top, 16)
@@ -383,14 +366,17 @@ struct HistoryDetailView: View {
             if let geo = entry.ipGeolocation {
                 sectionHeader("IP Location")
                 VStack(alignment: .leading, spacing: 6) {
-                    labelRow("IP", geo.ip)
-                    if let org = geo.org {
-                        labelRow("Org / ISP", org)
-                    }
-                    let location = [geo.city, geo.region, geo.country_name].compactMap { $0 }.joined(separator: ", ")
-                    if !location.isEmpty {
-                        labelRow("Location", location)
+                    horizontallyScrollableContent(spacing: 6) {
+                        labelRow("IP", geo.ip)
+                        if let org = geo.org {
+                            labelRow("Org / ISP", org)
+                        }
+                        let location = [geo.city, geo.region, geo.country_name].compactMap { $0 }.joined(separator: ", ")
+                        if !location.isEmpty {
+                            labelRow("Location", location)
+                        }
                     }
+
                     if let lat = geo.latitude, let lon = geo.longitude {
                         let coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lon)
                         Map(initialPosition: .region(MKCoordinateRegion(
@@ -400,6 +386,7 @@ struct HistoryDetailView: View {
                             Marker(geo.ip, coordinate: coordinate)
                         }
                         .mapStyle(.standard)
+                        .frame(maxWidth: .infinity)
                         .frame(height: 180)
                         .cornerRadius(8)
                     }
@@ -481,6 +468,32 @@ struct HistoryDetailView: View {
         }
     }
 
+    private func horizontallyScrollableCard<Content: View>(
+        spacing: CGFloat = 4,
+        @ViewBuilder content: () -> Content
+    ) -> some View {
+        horizontallyScrollableContent(spacing: spacing) {
+            content()
+        }
+        .padding(10)
+        .background(Color(.systemGray6).opacity(0.5))
+        .cornerRadius(6)
+    }
+
+    private func horizontallyScrollableContent<Content: View>(
+        spacing: CGFloat = 4,
+        @ViewBuilder content: () -> Content
+    ) -> some View {
+        ScrollView(.horizontal) {
+            VStack(alignment: .leading, spacing: spacing) {
+                content()
+            }
+            .scrollTargetLayout()
+        }
+        .scrollBounceBehavior(.basedOnSize, axes: .horizontal)
+        .frame(maxWidth: .infinity, alignment: .leading)
+    }
+
     private func errorLabel(_ message: String) -> some View {
         Label(message, systemImage: "exclamationmark.triangle.fill")
             .font(.system(.caption, design: .monospaced))
diff --git a/claude.md b/claude.md
deleted file mode 100644
index bee59f1..0000000
--- a/claude.md
+++ /dev/null
@@ -1,171 +0,0 @@
-# DomainDig – Project Context
-
-## What this is
-An iOS utility app for querying DNS records, inspecting SSL/TLS certificates,
-checking HTTP headers, measuring TCP reachability, geolocating IP addresses,
-analyzing email security records, tracing redirect chains, performing reverse
-DNS lookups, and scanning common ports for any domain. Designed for developers,
-sysadmins, and anyone who manages or troubleshoots domains and servers.
-
-## Target user
-Technically literate adults — developers, IT/sysadmin, homelab enthusiasts.
-Comfortable with terms like A record, CNAME, MX, TTL, and TLS. The UI should be
-clean and information-dense, not hand-holding.
-
-## Design principles
-- Minimal, utilitarian UI — this is a tool, not a consumer app
-- Results should be readable at a glance — use clear labels, monospace where
-  appropriate for IPs and raw values
-- Dark mode preferred as the primary aesthetic
-- No onboarding, no tutorials, no splash screens
-- Fast — results should appear as soon as they're available, not after all
-  lookups complete
-- Each result section loads independently with its own ProgressView
-- Errors shown inline per-section, never as global alerts
-
-## Features
-
-### DNS Lookup
-Query the following record types for any domain via DNS-over-HTTPS (use
-Cloudflare 1.1.1.1 — https://cloudflare-dns.com/dns-query):
-- A
-- AAAA
-- MX
-- NS
-- TXT
-- CNAME
-
-Display each record type in its own section. Show TTL alongside each result.
-If a record type returns no results, show "No records found" for that type
-rather than hiding the section entirely.
-
-After the apex query, also queries `*.{domain}` for A, AAAA, MX, and TXT.
-Wildcard results are shown as a sub-section beneath each record type, labelled
-with `*.{domain}`. Hidden if no wildcard records are returned.
-
-### SSL/TLS Certificate Check
-Connect to the domain on port 443 via URLSession and inspect the server's
-certificate chain using URLSession delegate methods. Display:
-- Common name (CN)
-- Subject Alternative Names (SANs)
-- Issuer
-- Valid from / Valid until dates
-- Days until expiry — highlight in red if under 30 days, yellow if under 60
-- Certificate chain depth
-
-### HTTP Headers Check
-Fire a HEAD request to `https://{domain}` and display all response headers.
-Header names in cyan, values in primary. Security-relevant headers highlighted
-in yellow: `strict-transport-security`, `x-frame-options`,
-`x-content-type-options`, `content-security-policy`, `referrer-policy`.
-Service: `HTTPHeadersService.swift`.
-
-### Reachability / TCP Latency
-Use `NWConnection` (Network framework) to attempt TCP connections on ports 443
-and 80. Measure time to `.ready` state. Display green/red dot, latency in ms,
-and reachable/unreachable status. Timeout after 5 seconds.
-Service: `ReachabilityService.swift`.
-
-### IP Geolocation with Map
-After DNS A record resolves, take the first IP and query
-`https://ipapi.co/{ip}/json/` for country, region, city, org, lat/lon.
-Display in an "IP Location" section with a SwiftUI `Map` (MapKit) centered on
-the coordinates with a `Marker`. Map height 180pt, `.standard` style.
-Service: `IPGeolocationService.swift`.
-
-### Email Security Analysis
-Parse SPF from existing TXT records (no extra query). Query `_dmarc.{domain}`
-for DMARC and try common DKIM selectors (`default`, `google`, `mail`) via DoH.
-Display green checkmark if found, red ✗ if not. Full record values truncated
-to 80 chars with tap-to-expand. Triggered after DNS completes.
-Service: `EmailSecurityService.swift`.
-
-### Reverse DNS / PTR Lookup
-After DNS A record resolves, construct reverse DNS name (reversed octets +
-`.in-addr.arpa`) and query PTR record via Cloudflare DoH. Displayed inline
-in the DNS Records section below the A record sub-section.
-Service: `ReverseDNSService.swift`.
-
-### Redirect Chain
-Fire HTTP request to `http://{domain}` with redirects disabled. Follow up to
-10 redirects manually, recording each hop's URL and status code. Display step
-number, status code in cyan, URL, and "(final)" on the last hop. Shows
-"No redirects — direct connection" if the first request returns 200.
-Service: `RedirectChainService.swift`.
-
-### Common Port Scanner
-Probe 10 common ports (21/FTP, 22/SSH, 25/SMTP, 80/HTTP, 443/HTTPS,
-587/SMTP-TLS, 3306/MySQL, 5432/PostgreSQL, 8080/HTTP-Alt, 8443/HTTPS-Alt)
-using `NWConnection` with 3-second timeout. All probes run concurrently.
-Green dot for open, grey dot for closed. Closed is expected — no error shown.
-Service: `PortScanService.swift`.
-
-### Results layout
-- Domain input at the top — large text field, keyboard shows on launch
-- Run button to trigger all lookups simultaneously
-- Results displayed in clearly separated sections:
-  Reachability → Redirect Chain → DNS Records (with PTR inline) →
-  Email Security → SSL/TLS Certificate → HTTP Headers → IP Location →
-  Open Ports
-- Each section loads independently — don't block one section waiting for another
-- Email security, PTR, and IP geolocation are chained after DNS; all others
-  run in parallel
-
-### Recent searches
-Store the last 20 searched domains locally using UserDefaults. Display them as
-a tappable list below the text field when no results are showing. Tapping a
-recent domain populates the text field and runs the lookup immediately. Include
-a "Clear" button to wipe history. Most recent at the top.
-
-### Saved domains
-Bookmark button (SF Symbol: `bookmark` / `bookmark.fill`) in the results
-toolbar area, next to the share button. Tapping saves/unsaves the current
-domain. Filled icon when saved. Saved domains viewable from a toolbar button
-that pushes to `SavedDomainsView` — a list of saved domains, tappable to run
-lookups, with swipe-to-delete and an Edit button for bulk deletion.
-Persisted in UserDefaults under key `savedDomains`.
-
-### Lookup history with cached results
-After each successful lookup, a snapshot of all results (DNS, SSL, HTTP headers,
-reachability, geolocation, email security, PTR, redirect chain, port scan) is
-saved to history in UserDefaults as JSON. Capped at 50 entries. `HistoryView`
-shows past lookups with domain and timestamp. Tapping shows full cached results
-in `HistoryDetailView` using the same layout, labelled as cached with the
-original timestamp. Model: `HistoryEntry` (Codable).
-
-### Share / export
-A share button (SF Symbol: `square.and.arrow.up`) in the top-right of the
-results area. Formats the full results as plain text and presents the iOS share
-sheet via UIActivityViewController. The export includes: domain, timestamp,
-reachability, redirect chain, all DNS records with TTLs and PTR, email security
-records, SSL cert fields, HTTP headers, IP geolocation data, and port scan
-results.
-
-## Technical constraints
-- SwiftUI, iOS only
-- Fully offline except for network requests (DNS-over-HTTPS, SSL, HTTP HEAD,
-  TCP connections, geolocation API)
-- No accounts, no analytics, no ads
-- No third-party dependencies — URLSession, Network, MapKit only
-- Targets latest iOS
-
-## Architecture
-- `Models.swift` — All data models (DNS, SSL, HTTP headers, reachability,
-  geolocation, history entry), all Codable for persistence
-- `DomainViewModel.swift` — `@Observable` view model orchestrating all lookups,
-  managing state, history, saved domains, recent searches, and export
-- `ContentView.swift` — Main screen with input, all result sections, toolbar
-- `SavedDomainsView.swift` — Saved domains list with edit/delete
-- `HistoryView.swift` — History list + `HistoryDetailView` for cached results
-- Services: `DNSLookupService`, `SSLCheckService`, `HTTPHeadersService`,
-  `ReachabilityService`, `IPGeolocationService`, `EmailSecurityService`,
-  `ReverseDNSService`, `RedirectChainService`, `PortScanService`
-
-## What good looks like
-A developer pastes a domain, taps run, and within a couple of seconds sees a
-clean breakdown of TCP reachability, redirect chain, every DNS record type with
-reverse DNS, email security posture, the full SSL cert status, HTTP response
-headers with security headers highlighted, IP geolocation with a map, and a
-port scan of common services. It should feel like a native, polished version of
-running `dig`, `openssl s_client`, `curl -I`, `nmap`, and `whois` from the
-terminal.