Skip to content

Checks catalog

Checks catalog

The findings model

Each check returns zero or more Findings. A Finding carries:

Field Meaning
severity Severity::Error | Warning | Info
category e.g. Delegation, Nameservers, Email, DNSSEC
check a stable machine id, e.g. delegation.match
message human-readable text
context machine-readable bag of the raw values behind the message

Severity means:

  • Error — a misconfiguration that breaks resolution, mail, or trust.
  • Warning — works today, but fragile, non-redundant, or below best practice.
  • Info — an observation or a healthy result worth surfacing.

Report::passed() is a clean bill of health: no errors and no warnings.

The default catalog

Diagnostics::run() executes these checks, in report order. Every check turns a DNS-level failure into a Finding rather than throwing.

Category Check ids What it verifies
Delegation delegation.ns, delegation.match, delegation.glue The zone has NS records; the parent's NS set matches the zone's own; in-zone nameservers have glue at the parent.
Nameservers ns.count, ns.cname, ns.resolve, ns.respond, ns.authoritative, ns.lame, ns.recursion At least two NS (RFC 1034); none a CNAME (RFC 2181); each resolves to a public IP; each responds and answers authoritatively (not "lame"); none offers open recursion for a foreign name.
SOA soa.presence, soa.parse, soa.serial, soa.mname, soa.timers SOA read from each authoritative NS; every server serves the same serial; MNAME is a listed nameserver; refresh/retry/expire/minimum timers are internally sane.
Email (MX) mx.presence, mx.target, mx.redundancy, mx.ptr, mx.fcrdns MX exists; each exchange is a resolvable public hostname (never an IP literal or CNAME, RFC 2181 §10.3); more than one for redundancy; each mail IP has a PTR that forward-confirms (FCrDNS).
Email (SPF) spf.presence, spf.lookups, spf.all Exactly one v=spf1 record (RFC 7208); the static DNS-lookup budget is not exceeded; it does not end in a permissive +all.
Email (DMARC) dmarc.presence, dmarc.policy A DMARC record at _dmarc.<domain>; a valid, enforcing p= policy (absent, malformed, or p=none is a Warning).
CAA caa.presence Whether the apex publishes a CAA record. Both outcomes are Info — absence is not a fault.
DNSSEC dnssec.chain Wraps the DNSSEC validator: secure/insecure is Info; bogus is an Error.
Propagation (via PropagationCheck) The apex A record's authoritative-vs-recursive propagation: Propagated is Info; Pending/Misconfigured is a Warning.

The DKIM check (opt-in)

DKIM cannot be probed without knowing the selector — there is no way to enumerate selectors over DNS — so DkimCheck is not in the default set. Run it explicitly with a selector via runWith():

use Cbox\Dns\Diagnostics\Diagnostics;
use Cbox\Dns\Diagnostics\Checks\DkimCheck;

$diagnostics = new Diagnostics($dns->resolver());

$report = $diagnostics->runWith('example.com', [new DkimCheck('selector1')]);
// checks selector1._domainkey.example.com -> dkim.presence / dkim.revoked

Adding your own checks

A check implements Cbox\Dns\Diagnostics\Contracts\Check:

use Cbox\Dns\Diagnostics\Contracts\Check;
use Cbox\Dns\Diagnostics\DiagnosticContext;
use Cbox\Dns\Diagnostics\Finding;

final class MyCheck implements Check
{
    /** @return list<Finding> */
    public function run(DiagnosticContext $ctx): array
    {
        $addresses = $ctx->addresses($ctx->domain);   // resolved via the injected resolver

        return $addresses === []
            ? [Finding::warning('Custom', 'my.check', 'Apex has no A/AAAA.')]
            : [Finding::info('Custom', 'my.check', count($addresses).' apex addresses.')];
    }
}

DiagnosticContext exposes the target domain plus the injected collaborators — resolver, authoritative, dnssec, propagation — and helpers nameservers() and addresses($host). Run a custom list, optionally alongside the defaults:

$diagnostics->runWith('example.com', [
    ...Diagnostics::defaultChecks(),
    new MyCheck,
]);