codingjoe / codingjoe/relay

Domain.clean() lets any account claim names under the platform domain

Open
#219 0 comments 0 reactions 0 assignees View on GitHub
bug security
Dominant language
Python
Stars
4
Forks
0
Avg merge
8h 24m
Merged PRs (30d)
115

Description

## Impact

Any GitHub account can register a `Domain` row for a name under the platform
domain (for example `mail.relay.relays.to`). No DNS proof is required. Relay
then serves that name from its own infrastructure, which has two consequences:

1. Relay's MX accepts mail for the claimed name, so the claimant receives
relay's own DMARC aggregate and forensic reports plus TLS-RPT reports.
Production publishes those addresses:

```console
$ dig +short _dmarc.relays.to TXT
"v=DMARC1; p=none; ... rua=mailto:dmarc@mail.relay.relays.to; ruf=mailto:ruf@mail.relay.relays.to;"
$ dig +short _smtp._tls.relays.to TXT
"v=TLSRPTv1;rua=mailto:tls@mail.relay.relays.to"
$ dig +short mail.relay.relays.to MX
10 mx1.relays.to.
10 mx2.relays.to.
```

The reports are filed against the claimant's organization, so the claimant
can read relay's sending IPs, volumes, and SPF/DKIM results. The claimant
also receives `postmaster@mail.relay.relays.to` mail.

2. The name becomes a valid MTA-STS policy host, so relay issues a publicly
trusted certificate for it. The gate added in #135 blocks this specific
angle by rejecting non-managed platform descendants, but the underlying
registration hole stays open for every other use of the name.

The documentation promises that no two organizations can claim overlapping
names. That promise does not hold for descendants of the platform domain.

## Steps to reproduce

Each step was verified locally against the worktree code with
`RELAY_PLATFORM_DOMAIN="relays.to"` and `RELAY_MANAGED_SENDER_DOMAIN="open.relays.to"`:

```console
register 'foo.relays.to' -> allowed
register 'mail.relay.relays.to' -> allowed
register 'mta-sts.relays.to' -> allowed
register 'ns1.relays.to' -> allowed
register 'open.relays.to' -> rejected # only the managed subtree is protected
root_for("mail.relay.relays.to") -> the claimant's row
```

## Root cause

`domains/models.py:285-325`. The managed-subtree check only rejects
`RELAY_MANAGED_SENDER_DOMAIN` and its descendants:

```python
if not self.is_managed:
root = canonicalize_domain_name(settings.RELAY_MANAGED_SENDER_DOMAIN)
if name == root or name.endswith(f".{root}"):
raise ValidationError(...)
```

The overlap check that follows should catch a platform-domain descendant,
because the ancestors of `foo.relays.to` include the platform row `relays.to`.
It cannot, because that row is excluded on purpose:

```python
if name == platform_name:
overlapping_domains = Domain.objects.none()
else:
overlapping_domains = overlapping_domains.exclude(name__iexact=platform_name)
```

The exclusion exists because every managed sender domain is a descendant of the
platform domain, so the platform row would otherwise conflict with legitimate
managed rows. The exclusion is not limited to the managed case, so it also
removes the only protection for arbitrary platform descendants.

## Suggested fix

Reject non-managed names that equal or descend from the platform domain,
mirroring the existing managed-subtree rule. Managed rows stay exempt, because
they are platform descendants by design.

Open questions for the implementer:

- Existing rows need a decision. A data migration can either delete them or
report them for manual review.
- Decide whether `Domain.full_clean()` alone is enough, or whether the
`DomainCreateView` form needs its own validation message.
- Confirm that no legitimate customer name is a descendant of the platform
domain.

## Related

- #135 closed the certificate side of the same trust primitive: the on-demand
TLS permission endpoint now rejects any non-managed platform descendant.
- Found by a security review of the #135 change, not introduced by it.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.