Validates a MX record set (svcs.MXs) at edit time:
- Null MX (RFC 7505): a "." target must be the only MX in the set, with
preference 0. Both deviations are surfaced.
- Targets: invalid hostnames, out-of-range preferences (uint16) and
duplicate targets (case-insensitive on the FQDN).
- Cross-zone: flags MX targets that are CNAME owners in the same zone
(RFC 5321 sec. 5.1) and warns when an in-zone target lacks any
A/AAAA service. External targets are left to runtime checkers.
Unit tests cover happy paths, the null-MX edge cases, target/preference
validation, duplicate detection and the in-zone cross checks (CNAME
collision, missing address, apex target).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The BIMI draft lets a domain explicitly opt out of BIMI by publishing
a record with v=BIMI1 and an empty l= tag. Surface that in the editor
and treat it as a first-class case in compliance.
- bimi.ts gains isBIMIDeclination() and stringifyBIMIDeclination().
- The editor adds a "Decline to participate in BIMI" checkbox; when
checked, the l/a/e fields are hidden and the TXT record is rewritten
to "v=BIMI1;l=". The checkbox is auto-detected when an existing
declination record is loaded.
- The compliance validator detects declination right after the
selector and version checks, emits a single bimi.declination info
message, and skips the URL/cross-record checks that no longer apply.
- Locales updated (en, fr).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plugs the BIMI editor into the records-compliance framework.
Validators run synchronously and surface:
- Owner-name shape: <selector>._bimi (rejects empty selectors and
non-label characters).
- Version: only "BIMI1" is accepted by the current draft.
- Logo URL: l= is mandatory, must be HTTPS, warns when the path does
not end in .svg.
- VMC URL: optional but flagged as info when missing (Gmail and Yahoo
need it). Must be HTTPS when present; info if it does not look like
a .pem file.
- Evidence URL: must be HTTPS when present.
- Cross-record DMARC check: warns when no DMARC is published or when
every DMARC at the apex sits at p=none, since BIMI is only honoured
with an enforcing DMARC policy.
Locales added under compliance.bimi for en and fr. The new validator
self-registers via $lib/services/compliance/registry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends the compliance context with findAllServices(type?) so a
validator can iterate every service in the zone, not just a single
subdomain. The DMARC validator now uses it to flag configurations
where alignment is structurally impossible:
- p=quarantine|reject and the zone has neither a DKIM nor an SPF
record -> error: every legitimate message will fail DMARC.
- p=none in the same situation -> warning: DMARC has nothing to
align against, monitoring data will be empty.
- adkim=s (strict DKIM alignment) with no DKIM record published
anywhere in the zone -> warning: only SPF alignment can succeed.
Cross-checks are skipped when the zone state is not provided
(ctx.zone === null), so unit tests and isolated calls keep the
previous behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the new POST /api/resolver/mta-sts-policy endpoint into the
MTA-STS validator. The async pass runs after the local TXT checks,
debounced and cancellable through EditorCompliance, and surfaces:
- Transport-level failures: dns-error, tls-error, fetch-error,
too-large.
- HTTP-level failures: not-found (404), http-error (other non-2xx),
redirect (server tried to redirect, RFC 8461 sec. 3.3 forbids it).
- Policy file content: missing/invalid version, missing/invalid mode,
mode=none (warning, effectively disabled), mode=testing (info),
missing mx in enforce/testing modes, missing/out-of-range max_age
(0..31557600), short max_age (< 1 day, warning).
Adds a fetchMTAStsPolicy() wrapper to $lib/api/resolver.ts that accepts
an AbortSignal so the EditorCompliance debounce + abort plumbing covers
this validator like it does for SPF.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds checks for svcs.TLS_RPT against RFC 8460 sec. 3.
The validator surfaces:
- Wrong owner name (must be _smtp._tls.<domain>).
- Missing or non-TLSRPTv1 v= tag.
- Missing rua= report destination.
- Empty entries inside rua=.
- rua URIs that are neither mailto: nor http(s):.
- Malformed mailto URIs (missing @ or domain).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds checks for svcs.MTA_STS against RFC 8461 sec. 3.1.
The validator surfaces:
- Wrong owner name (must be _mta-sts.<domain>).
- Missing or non-STSv1 v= tag.
- Missing id= tag.
- id= containing characters outside [A-Za-z0-9] or longer than 32 chars.
The TXT only carries the policy pointer; the actual policy file at
mta-sts.<domain>/.well-known/mta-sts.txt is out of scope here and will
need an HTTPS fetch (out of scope for the sync pass).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds compliance checks for svcs.DMARC against RFC 7489.
The validator parses the published TXT and surfaces:
- Wrong owner name (record must live at _dmarc.<domain>).
- Missing or non-DMARC1 v= tag.
- Missing, unknown, or "monitoring-only" p= policy.
- Invalid sp= subdomain policy.
- Invalid adkim/aspf alignment values.
- pct= out of [0..100] (error) and pct < 100 (info, partial deployment).
- Non-positive or non-numeric ri=.
- Unknown fo= entries (0 / 1 / d / s) and unknown rf= formats (afrf).
- Empty or malformed rua/ruf URIs (mailto and http(s) accepted; mailto
size suffix !N preserved).
A 25-case test suite covers each issue id, plus happy paths for a
minimal reject record and rua mailto/http URIs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the new POST /api/resolver/spf-flatten endpoint into the SPF
validator. The async path runs after the local checks, debounced and
cancellable through EditorCompliance, and surfaces:
- spf.recursive-many-lookups / spf.recursive-too-many-lookups based on
the recursive lookupCount returned by the backend
- spf.too-many-void-lookups when more than 2 NXDOMAIN/NoData responses
occur during the walk (RFC 7208 §4.6.4)
- per-include diagnostics: spf.include-loop, spf.include-no-spf,
spf.include-resolver-error, spf.include-error — pointing at the exact
domain and mechanism that failed
The async pass is skipped entirely when the local lookup budget is 0,
to avoid a network roundtrip on records that obviously cannot exceed
the limit.
A small flattenSPF() helper is added to $lib/api/resolver.ts to wrap
the auto-generated SDK call and accept an AbortSignal.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extracts the SPF parser/serializer out of the editor into
$lib/services/spf.ts (matching dmarc.ts / mta_sts.ts) and adds a sync
validator that flags non-recursive issues against RFC 7208:
- missing or wrong v=spf1
- absence / multiplicity / non-final placement of ‘all’
- redirect= combined with ‘all’ or duplicated
- ptr deprecation (RFC 7208 §5.5)
- local DNS-lookup budget (warn ≥8, error >10) — recursive flatten will
come later via an async backend endpoint
- mechanisms missing values, empty terms, duplicates, length cap
24 unit tests cover positive and negative cases. The editor itself loses
its inline parser and reuses the shared module; behavior is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the compliance section stayed completely hidden until at
least one issue was reported, which made it indistinguishable from a
broken validator hookup. The panel now renders as soon as a validator
is registered for the current service type, with a "All checks passed"
status when the issue list is empty and the async pass is idle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plugs the first per-record compliance validator into the framework.
Validates a DKIM TXT record (svcs.DKIMRecord) at edit time:
- Selector: must be present, must match the label charset.
- Version: only "DKIM1" is accepted (RFC 6376 sec. 3.6.1).
- Public key: detects missing p=, empty p= (revocation, warning), and
non-base64 payloads. Warns on RSA keys shorter than ~2048 bits and
errors on RSA keys shorter than ~1024 bits per RFC 8301.
- Algorithms: warns on SHA-1 (RFC 8301) and unknown hashes; flags
unknown key types or service types.
- Flags: surfaces t=y (testing) as info; warns on unknown flags.
- Granularity: marks g= as deprecated since RFC 6376.
A unit test suite exercises every issue id plus a few happy paths and
the empty-input case.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the frontend-only compliance framework that lets each editor
contribute spec-conformance checks. The infra is self-contained:
- $lib/services/compliance.ts exposes ComplianceIssue/Severity/Validators
types plus a small registry API.
- $lib/services/compliance/registry.ts is the central side-effect import
point where per-record validators get wired up commit by commit.
- EditorCompliance.svelte renders sync issues immediately and runs async
validators with a debounce + AbortController; it stays hidden when no
validator is registered and when there are zero issues.
- ServiceEditor.svelte mounts the panel under every loaded editor; no
per-editor wiring is needed.
- locales: new "compliance" namespace (en, fr) for shared UI strings.
No validators are registered yet, this commit is intentionally inert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces a domaininfo package with RDAP and WHOIS getters, exposed
through a new DomainInfoUsecase and /api/domaininfo/:domain route (also
mounted under domain scope). Adds a /whois frontend page and a zone
sidebar modal to display registrar, dates, nameservers and status.
Wire UserQuota.MaxChecksPerDay field into the scheduler via the
UserGater: an in-memory daily counter per user
(reset at UTC midnight): gates scheduled executions, with a two-tier
heuristic that skips short-interval jobs first once the budget is 80%
consumed so rare/important checks are not starved by frequent
pings. Planned executions returned by ListPlannedExecutions are marked
with a new ExecutionRateLimited status when the user is over
budget. Manual API triggers bypass the quota.
Add a separate section on the domain checks page to display zone and
service-level checkers that can be configured but won't produce results
at the domain scope. Hide the scheduling and rules cards when configuring
a non-domain checker from the domain context.
Remove separate /rules pages and display rules as a tab alongside
metrics, HTML, and JSON views. Rules become the default view when
no metrics or HTML report is available. Status is now shown as a
colored badge in the rules table.
Add Chart.js-based line chart for checker metrics. The chart appears
on the executions list page (aggregated) and on individual execution
detail pages. Metrics view mode is selectable via the sidebar alongside
HTML report and raw JSON views.
Add all checker UI pages and components:
- Checker list, config, schedule, and rules pages
- Execution list, detail, results, and rules pages
- Sidebar components for domain/service checker status
- Run check modal with option overrides and rule selection
- Domain-scoped and service-scoped check routes
- Admin pages for checker configuration and scheduler management
- Header navigation link for checkers section
Add the frontend infrastructure for the checker UI:
- API client with scoped helpers for domain/service-level operations
- Svelte stores for checker state (currentExecution, currentCheckInfo)
- Utility functions for status colors, icons, i18n keys, date formatting
- Shared helpers: withInheritedPlaceholders, downloadBlob, collectAllOptionDocs
- English translations for all checker UI strings
- Zone model and form types extended for checker support
Refresh the overall visual design across the frontend: floating labels
and centered card layout for auth pages, animated sidebar+results view
for the resolver, polished dropdown menus, toasts, and feedback widget,
and cleaner global styles (navbar shadow, border, transitions).
After publishing zone corrections, compute and store a PropagatedAt
timestamp on each affected service indicating when old cached records
will have expired. For updated/deleted services, this is publish_time +
old service TTL. For new services, it uses the SOA minimum TTL
(negative cache duration), falling back to the zone's DefaultTTL.
The propagation detection reuses the same service matching technique as
ReassociateMetadata (subdomain + type + ServiceRDataHash). Both the
published snapshot and the WIP zone are stamped.
Decouple diff computation from executable provider closures by fetching
provider records and computing diffs locally via DNSControlDiffByRecord.
On apply, build a target record set from user-selected corrections using
BuildTargetRecords, then ask the provider for executable corrections
against that target. A published snapshot is inserted at ZoneHistory[1]
while the WIP zone at position 0 remains unchanged.
Expose service editors publicly (no auth required) at /generator for
SEO discoverability. Each page shows an interactive editor alongside
a live DNS zone record preview powered by a new POST
/service_specs/:ssid/records backend endpoint.
When display_by_groups is enabled, domains are now draggable and group
containers act as drop targets. Dropping a domain onto a different group
updates its group via the API and refreshes the domain list.
Previously, RecordFailure/RecordSuccess were only called when a captcha
provider was configured, making brute-force tracking entirely inactive
on deployments without one.
- Always track login failures and successes regardless of captcha config
- When threshold is crossed with a captcha provider: 401 + captcha_required (existing behaviour)
- When threshold is crossed without a captcha provider: 429 + rate_limited flag
- Frontend: show a rate-limited message and disable the submit button on 429
- Add errors.rate-limited translation key to all locales
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces ProviderPicker and PickProvider reusable components so that
adding a domain from NewDomainInput or FilterDomainInput opens an
inline provider-selection modal instead of navigating away to
/domains/new/:dn.
Replace the ListGroup-based provider list with a Bootstrap Table on the
providers page. Rows are clickable to edit, the domain count links to
the domains page pre-filtered by provider, and action buttons handle
propagation correctly.
Introduces a reusable PageTitle component with a teal overline accent,
display-3 heading, optional monospace domain label, subtitle, and a
children slot for future domain health/check badges. Applied consistently
to the zone viewer, history, logs, export, import, resolver, providers,
account settings, and new-domain pages.
Introduce ServiceDetailsOffcanvas, an offcanvas panel that opens when
clicking a service card. It displays the service description, its DNS
records, and provides actions buttons.
Also remove raw DNS record from service form.
Convert the zone file viewer from a modal dialog to a dedicated page at
/domains/[dn]/export, following the same pattern used for service pages.
Adds a "Copy to clipboard" button in the page title bar and adds the
common.copy-clipboard translation key to all supported locales.
Integrates optional bot protection on the registration endpoint (always
required when a provider is configured) and the login endpoint (triggered
after N consecutive failures for the same IP or email address).
Supported providers: hCaptcha, reCAPTCHA v2, Cloudflare Turnstile.