External posture scan
This is the "watch them" half of Covenant. It looks at a vendor's domain from the outside and turns what it observes into explainable findings — each carrying the exact evidence that made it fire — which then flow into the score.
Live vs deferred: what actually runs from your browser
A static browser app (and even a serverless Worker) cannot do a raw TLS handshake, read another site's headers, probe ports, or query a licensed breach corpus. Covenant is honest about this split:
| Probe | Status | How |
|---|---|---|
| Email authentication — SPF | live now | A real DNS-over-HTTPS (DoH) TXT lookup of the apex domain via a public resolver. |
| Email authentication — DMARC | live now | A real DoH TXT lookup of _dmarc.<domain>; the policy (none/quarantine/reject) is parsed. |
| Email authentication — DKIM | reported "unknown" | DKIM needs the selector, which DoH can't enumerate, so it is never falsely flagged as missing. |
| TLS / certificate | deferred | Hosted scan runner (Go + Nuclei) / Lookout agent — raw TLS handshake. |
| Security headers (HSTS / CSP / X-Frame-Options) | deferred | Cross-origin header reads are browser-blocked; the hosted runner fetches them. |
| Exposed ports | deferred | Raw socket port probing — hosted runner. |
| Breach / leak feeds | deferred | Licensed breach/leak corpus — hosted runner. |
Running a live scan
- Open the vendor (it must have a Primary domain set).
- In the External posture findings card, click Run live scan.
- The button shows "Scanning…"; when done, a summary lists what was probed now (email auth), how many new findings were added, and what was deferred to the hosted runner.
.example domains and will be refused on purpose.The SSRF guard
A vendor's domain is untrusted input, so before any probe Covenant normalizes it and refuses anything that could be abused for server-side request forgery or internal-network scanning:
| Refused | Reason shown |
|---|---|
IPv4 or IPv6 literals (e.g. 10.0.0.1, [::1]) | "IP literals are not allowed; give a domain" |
| Private / loopback / link-local / CGNAT ranges | "private/loopback addresses are not allowed" |
Embedded credentials (user@host) | "credentials in target are not allowed" |
Explicit ports (host:8080) | "explicit ports are not allowed" |
Internal/reserved TLDs (.local, .internal, .test, .example, .invalid, …) | "internal/reserved TLD '.xxx' is not externally scannable" |
| Anything that isn't a valid public DNS hostname | "not a valid public domain" |
If you paste a full URL, the scheme and path are stripped first; only the hostname is scanned.
The explainable evaluator & per-finding evidence
The scoring is a pure, deterministic function: it turns structured observations into catalog-keyed findings, and a finding exists only because an observation justifies it — nothing is fabricated. Each finding records the specific evidence, shown in the app as "Evidence: …". Examples:
- SPF observed absent → "No SPF (v=spf1) TXT record found for the domain."
- DMARC policy is
p=none→ "DMARC present but policy is p=none (monitor-only, not enforced)." - Certificate expired (deferred runner) → severity escalates to critical with "Certificate EXPIRED N day(s) ago."
Every finding also shows remediation guidance ("Fix: …") pulled from the 12-check catalog, and a tag showing whether it came from a live scan or was simulated or entered manually.
The check catalog (12 checks)
| Check | Category | Default severity |
|---|---|---|
| TLS certificate expiring soon | TLS | high |
| Weak TLS version or cipher offered | TLS | medium |
| HSTS not enforced | HEADERS | low |
| No Content-Security-Policy | HEADERS | low |
| Missing X-Frame-Options / frame-ancestors | HEADERS | low |
| Unexpected service/port exposed | PORTS | high |
| Server software version disclosed | PORTS | low |
| No SPF record published | EMAIL_AUTH | medium |
| No DMARC policy (or p=none) | EMAIL_AUTH | medium |
| DKIM signing not detected | EMAIL_AUTH | low |
| Vendor domain found in a known breach corpus | BREACH | high |
| Vendor brand/domain mentioned on a leak/paste site | LEAK | medium |
Simulated scan (for demos & deferred checks)
Run simulated scan deterministically fabricates plausible findings from the vendor's domain so you can exercise the dispute/accept/remediate and remediation flow without a real backend. Results are stable per domain and tagged simulated. This is clearly distinct from the live scan and never claims to be real reconnaissance.
Working with findings
Each finding has a severity, category, title, evidence, remediation, and a status. Use the buttons to set status:
| Action | Effect on score |
|---|---|
| Dispute | Status → DISPUTED. Still counts as a penalty until resolved. |
| Accept | Status → ACCEPTED. Removed from the score penalty. |
| Remediated | Status → REMEDIATED. Removed from the score penalty. |
Severity costs (per open/disputed finding): critical −25, high −15, medium −8, low −3, info 0. You can also add a finding manually (category, severity, description). Re-scanning merges by check id, so a finding you already disputed or accepted is not clobbered.
Continuous monitoring cadence
Set a per-vendor rescan cadence — off / daily / weekly / monthly — and click Save cadence. Covenant tracks the last scan time and tells you when a rescan is due; the ledger heatmap shows the monitoring state. The actual scheduled rescans are the deferred hosted runner's job; the scheduling decision is computed locally now.