diff --git a/source/initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.md b/source/initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.md
index 92d4de9e77..fb15d0c10b 100644
--- a/source/initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.md
+++ b/source/initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.md
@@ -37,7 +37,8 @@ mongodb+srv://{hostname}/{options}
`{options}` refers to the optional elements from the [Connection String](../connection-string/connection-string-spec.md)
specification following the `Host Information`. This includes the `Auth database` and `Connection Options`.
-For the purposes of this document, `{hostname}` will be divided using the following terminology. If an SRV `{hostname}`
+For the purposes of this document, `{hostname}` will be divided using the following terminology. If
+`srvAllowedHostsSuffix` has been configured, then that will act as the `{domainname}`. Otherwise, if an SRV `{hostname}`
has:
1. Three or more `.` separated parts, then the left-most part is the `{subdomain}` and the remaining portion is the
@@ -65,6 +66,29 @@ Only `{domainname}` is used during SRV record verification and `{subdomain}` is
### MongoClient Configuration
+#### srvAllowedHostsSuffix
+
+This option is used to validate hosts. If present, its value MUST be treated as the `{domainname}` for
+[DNS validation](#querying-dns) and
+[SRV polling](../polling-srv-records-for-mongos-discovery/polling-srv-records-for-mongos-discovery.md). For example,
+`srvAllowedHostsSuffix=.mydomain.net`. Drivers MUST apply the following normalization and validation to the value, in
+this order:
+
+1. Any leading or trailing `.` MUST be stripped. For example, `srvAllowedHostsSuffix=.mydomain.net.` is treated as
+ `mydomain.net`. If the resulting stripped value is empty, an error MUST be raised.
+2. The value MUST be converted to its A-label (Punycode) form, so that it is comparable against the A-label hostnames
+ returned by DNS.
+3. The value MUST be normalized to lowercase using ASCII case folding.
+4. The resulting value MUST contain at least two `.` separated labels. For example, `srvAllowedHostsSuffix=net` MUST
+ raise an error.
+5. Drivers SHOULD raise an error if the resulting value is a public suffix, per the algorithm in
+ [Public Suffix List](../public-suffix-list/public-suffix-list.md).
+6. A `.` MUST be prepended. For example, `srvAllowedHostsSuffix=mydomain.net` is treated as `.mydomain.net`.
+
+If this option is not present, the `{domainname}` MUST be inferred from the `{hostname}` (as described in
+[Connection String Format](#connection-string-format)). This option MUST only be configurable at the level of a
+`MongoClient`.
+
#### srvMaxHosts
This option is used to limit the number of mongos connections that may be created for sharded topologies. This option
@@ -84,9 +108,9 @@ requires a string value and defaults to "mongodb". This option MUST only be conf
#### URI Validation
-The driver MUST report an error if either the `srvServiceName` or `srvMaxHosts` URI options are specified with a non-SRV
-URI (i.e. scheme other than `mongodb+srv`). The driver MUST allow specifying the `srvServiceName` and `srvMaxHosts` URI
-options with an SRV URI (i.e. `mongodb+srv` scheme).
+The driver MUST report an error if any of `srvServiceName`, `srvMaxHosts`, or `srvAllowedHostsSuffix` URI options are
+specified with a non-SRV URI (i.e. scheme other than `mongodb+srv`). The driver MUST allow specifying the
+`srvServiceName`, `srvMaxHosts`, and `srvAllowedHostsSuffix` URI options with an SRV URI (i.e. `mongodb+srv` scheme).
If `srvMaxHosts` is a positive integer, the driver MUST throw an error in the following cases:
@@ -124,10 +148,22 @@ If the DNS result returns no SRV records, or no records at all, or a DNS error h
indicating that the URI could not be used to find hostnames. The error SHALL include the reason why they could not be
found.
+Before validating returned hostnames, drivers MUST normalize them as follows, in this order:
+
+1. Any trailing `.` MUST be stripped. For example, `host.mydomain.net.` becomes `host.mydomain.net`.
+2. The hostname MUST be converted to its A-label (Punycode) form.
+3. The hostname MUST be normalized to lowercase using ASCII case folding.
+
+The `{domainname}` that returned hostnames are validated against MUST have the same three normalizations applied, so
+that neither trailing dots, case, nor Unicode/Punycode encoding can affect the comparison. When `{domainname}` comes
+from `srvAllowedHostsSuffix`, steps 1-3 of [srvAllowedHostsSuffix](#srvallowedhostssuffix) already apply them. The
+leading `.` that step 6 prepends makes the value a label-aligned suffix and is not part of this normalization.
+
A driver MUST verify that the host names returned through SRV records share the original SRV's `{domainname}`. In
-addition, SRV records with fewer than three `.` separated parts, the returned hostname MUST have at least one more
-domain level than the SRV record hostname. Drivers MUST raise an error and MUST NOT initiate a connection to any
-returned hostname which does not fulfill these requirements.
+addition, when `srvAllowedHostsSuffix` is not configured and the SRV record hostname has fewer than three `.` separated
+parts, the returned hostname MUST have at least one more domain level than the SRV record hostname. Drivers MUST raise
+an error and MUST NOT initiate a connection to any returned hostname which does not fulfill these requirements. This
+additional requirement does not apply when `srvAllowedHostsSuffix` is configured.
The driver MUST NOT attempt to connect to any hosts until the DNS query has returned its results.
@@ -247,6 +283,39 @@ Several of our users have asked for this through tickets:
The design specifically calls for a pre-processing stage of the processing of connection URLs to minimize the impact on
existing functionality.
+### Rationale for `srvAllowedHostsSuffix`
+
+By default, the parent domain that returned host names are checked against is inferred from the seed by treating its
+leftmost label as the `{subdomain}`. A seed of `mongodb.mydomain.net` results in `mongodb1.us-east-1.mydomain.net` being
+rejected, even when both are owned by the same user. `srvAllowedHostsSuffix` allows users to work around this limitation
+by letting the user state the parent domain directly instead, admitting such nested names and hosts in a different
+domain altogether.
+
+Note that nothing requires the `{hostname}` itself to end with the configured suffix. For example,
+`mongodb+srv://cluster.example.org/?srvAllowedHostsSuffix=.hosts.example.net` is valid, even though
+`cluster.example.org` does not end with `.hosts.example.net`. This is intentional: the option exists so the user can
+state the allowed parent domain explicitly, rather than having it inferred from the connection string. Requiring the two
+to match would restore that coupling, and would rule out arrangements such as a vanity alias in one organization's
+domain resolving to hosts in a provider's domain.
+
+Because it replaces that default requirement, this option relaxes a DNS spoofing safeguard. An attacker able to forge
+SRV responses is otherwise confined to host names under the seed's `{domainname}`; with the option set, they are
+confined to host names under the configured suffix instead. The broader that suffix, the more hosts a forged response
+can direct a driver to.
+
+Requiring that the value not be a public suffix bounds how broad it can get. It rules out values such as `.com` or
+`.co.uk`, which would place no meaningful limit on a forged response. It does not, however, make a given configuration
+safe: `.example.com` is not a public suffix, but it still admits every host in a large organization's domain.
+
+Users should be encouraged to configure the narrowest suffix that covers their deployment, and drivers should say so
+wherever the option is documented. For a seed hostname of `cluster.test.internal.example.com`, prefer the second of
+these:
+
+```text
+mongodb+srv://cluster.test.internal.example.com/?srvAllowedHostsSuffix=.example.com
+mongodb+srv://cluster.test.internal.example.com/?srvAllowedHostsSuffix=.internal.example.com
+```
+
## Justifications
### Why Are Multiple Key-Value Pairs Allowed in One TXT Record?
@@ -283,6 +352,11 @@ In the future we could consider using the priority and weight fields of the SRV
## ChangeLog
+- 2026-09-03: Specify that host names returned through SRV records, and the `{domainname}` they are validated against,
+ are both normalized -- trailing dot stripped, converted to A-label form, ASCII lowercased -- before validation.
+
+- 2026-08-24: Add `srvAllowedHostsSuffix` MongoClient option.
+
- 2024-09-24: Removed requirement for URI to have three '.' separated parts; these SRVs have stricter parent domain
matching requirements for security. Create terminology section. Remove usage of term `{TLD}`. The `{hostname}` now
refers to the entire hostname, not just the `{subdomain}`.
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-case-insensitive.json b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-case-insensitive.json
new file mode 100644
index 0000000000..3b1f67fec9
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-case-insensitive.json
@@ -0,0 +1,10 @@
+{
+ "uri": "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=.BUILD.10GEN.CC",
+ "seeds": [
+ "localhost.build.10gen.cc:27017"
+ ],
+ "options": {
+ "ssl": true
+ },
+ "ping": false
+}
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-case-insensitive.yml b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-case-insensitive.yml
new file mode 100644
index 0000000000..7957ba1f8c
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-case-insensitive.yml
@@ -0,0 +1,8 @@
+# Tests that srvAllowedHostsSuffix comparison is case-insensitive (ASCII case folding per RFC 4343).
+# The suffix .BUILD.10GEN.CC is uppercase but should match the returned host localhost.build.10gen.cc.
+uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=.BUILD.10GEN.CC"
+seeds:
+ - localhost.build.10gen.cc:27017
+options:
+ ssl: true
+ping: false
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-mismatch.json b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-mismatch.json
new file mode 100644
index 0000000000..56e26524c4
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-mismatch.json
@@ -0,0 +1,6 @@
+{
+ "uri": "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=test.build.10gen.cc",
+ "seeds": [],
+ "hosts": [],
+ "error": true
+}
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-mismatch.yml b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-mismatch.yml
new file mode 100644
index 0000000000..db6ee9811b
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-mismatch.yml
@@ -0,0 +1,5 @@
+# DNS record for test12.test.build.10gen.cc returns localhost.build.10gen.cc which would not match test.build.10gen.cc
+uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=test.build.10gen.cc"
+seeds: []
+hosts: []
+error: true
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-period-only.json b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-period-only.json
new file mode 100644
index 0000000000..f3689428c1
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-period-only.json
@@ -0,0 +1,6 @@
+{
+ "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvAllowedHostsSuffix=.",
+ "seeds": [],
+ "hosts": [],
+ "error": true
+}
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-period-only.yml b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-period-only.yml
new file mode 100644
index 0000000000..bd6621e833
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-period-only.yml
@@ -0,0 +1,7 @@
+# srvAllowedHostsSuffix MUST name at least one label. A value of "." is empty once the
+# leading and trailing "." are stripped, so it names no domain at all and MUST raise an
+# error rather than being treated as a suffix that every host matches.
+uri: "mongodb+srv://test1.test.build.10gen.cc/?srvAllowedHostsSuffix=."
+seeds: []
+hosts: []
+error: true
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-psl-not-public-suffix.json b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-psl-not-public-suffix.json
index 30824585ac..d6d02fa51a 100644
--- a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-psl-not-public-suffix.json
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-psl-not-public-suffix.json
@@ -10,8 +10,7 @@
"localhost:27019"
],
"options": {
- "ssl": true,
- "srvAllowedHostsSuffix": "10gen.cc"
+ "ssl": true
},
"ping": true
}
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-psl-not-public-suffix.yml b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-psl-not-public-suffix.yml
index ecdb22b858..4e20a2d22f 100644
--- a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-psl-not-public-suffix.yml
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-psl-not-public-suffix.yml
@@ -11,5 +11,4 @@ hosts:
- localhost:27019
options:
ssl: true
- srvAllowedHostsSuffix: "10gen.cc"
ping: true
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-psl-public-suffix.yml b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-psl-public-suffix.yml
index cfdfb8c9ed..c2f246d189 100644
--- a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-psl-public-suffix.yml
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-psl-public-suffix.yml
@@ -1,6 +1,4 @@
-# "cc" is an ordinary rule in the Public Suffix List, so it is itself a public suffix
-# and must be rejected. The SRV hosts do end with "cc", so the host suffix check
-# passes and the public suffix check is the only thing that can fail here.
+# "cc" is an ordinary rule in the Public Suffix List and is only one label and therefore MUST be rejected.
uri: "mongodb+srv://test1.test.build.10gen.cc/?srvAllowedHostsSuffix=cc"
seeds: []
hosts: []
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-tld-only.json b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-tld-only.json
new file mode 100644
index 0000000000..12098dbf5e
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-tld-only.json
@@ -0,0 +1,6 @@
+{
+ "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvAllowedHostsSuffix=.cc",
+ "seeds": [],
+ "hosts": [],
+ "error": true
+}
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-tld-only.yml b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-tld-only.yml
new file mode 100644
index 0000000000..aeb9be44e6
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-tld-only.yml
@@ -0,0 +1,6 @@
+# srvAllowedHostsSuffix SHOULD NOT be a public suffix. "cc" is an ordinary rule in the
+# Public Suffix List, and isn't two or more `.` separated labels so `.cc` MUST raise an error.
+uri: "mongodb+srv://test1.test.build.10gen.cc/?srvAllowedHostsSuffix=.cc"
+seeds: []
+hosts: []
+error: true
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-trailing-dot.json b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-trailing-dot.json
new file mode 100644
index 0000000000..9befc30060
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-trailing-dot.json
@@ -0,0 +1,10 @@
+{
+ "uri": "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=.build.10gen.cc.",
+ "seeds": [
+ "localhost.build.10gen.cc:27017"
+ ],
+ "options": {
+ "ssl": true
+ },
+ "ping": false
+}
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-trailing-dot.yml b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-trailing-dot.yml
new file mode 100644
index 0000000000..4da9917696
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-trailing-dot.yml
@@ -0,0 +1,8 @@
+# A trailing dot in srvAllowedHostsSuffix must be stripped before comparison,
+# so ".build.10gen.cc." is equivalent to ".build.10gen.cc".
+uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=.build.10gen.cc."
+seeds:
+ - localhost.build.10gen.cc:27017
+options:
+ ssl: true
+ping: false
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-with_dot.json b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-with_dot.json
new file mode 100644
index 0000000000..724f813918
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-with_dot.json
@@ -0,0 +1,10 @@
+{
+ "uri": "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=.build.10gen.cc",
+ "seeds": [
+ "localhost.build.10gen.cc:27017"
+ ],
+ "options": {
+ "ssl": true
+ },
+ "ping": false
+}
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-with_dot.yml b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-with_dot.yml
new file mode 100644
index 0000000000..bb73a50279
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-with_dot.yml
@@ -0,0 +1,8 @@
+# Also tests trailing dot normalization: DNS returns targets with a trailing dot
+# (localhost.build.10gen.cc.) which must be stripped before suffix comparison.
+uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=.build.10gen.cc"
+seeds:
+ - localhost.build.10gen.cc:27017
+options:
+ ssl: true
+ping: false
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-without_dot_fail.json b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-without_dot_fail.json
new file mode 100644
index 0000000000..b7544b66f2
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-without_dot_fail.json
@@ -0,0 +1,6 @@
+{
+ "uri": "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=uild.10gen.cc",
+ "seeds": [],
+ "hosts": [],
+ "error": true
+}
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-without_dot_fail.yml b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-without_dot_fail.yml
new file mode 100644
index 0000000000..57fab7a570
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-without_dot_fail.yml
@@ -0,0 +1,5 @@
+# dot should be prepended to `srvAllowedHostsSuffix` causing the host to be .uild.10gen.cc which does not match any available DNS records
+uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=uild.10gen.cc"
+seeds: []
+hosts: []
+error: true
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-without_dot_pass.json b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-without_dot_pass.json
new file mode 100644
index 0000000000..ea1f5c8918
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-without_dot_pass.json
@@ -0,0 +1,10 @@
+{
+ "uri": "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=build.10gen.cc",
+ "seeds": [
+ "localhost.build.10gen.cc:27017"
+ ],
+ "options": {
+ "ssl": true
+ },
+ "ping": false
+}
diff --git a/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-without_dot_pass.yml b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-without_dot_pass.yml
new file mode 100644
index 0000000000..feca05f8c8
--- /dev/null
+++ b/source/initial-dns-seedlist-discovery/tests/replica-set/srvAllowedHostsSuffix-without_dot_pass.yml
@@ -0,0 +1,6 @@
+uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=build.10gen.cc"
+seeds:
+ - localhost.build.10gen.cc:27017
+options:
+ ssl: true
+ping: false
diff --git a/source/polling-srv-records-for-mongos-discovery/polling-srv-records-for-mongos-discovery.md b/source/polling-srv-records-for-mongos-discovery/polling-srv-records-for-mongos-discovery.md
index 2805a9c23c..8166c9160c 100644
--- a/source/polling-srv-records-for-mongos-discovery/polling-srv-records-for-mongos-discovery.md
+++ b/source/polling-srv-records-for-mongos-discovery/polling-srv-records-for-mongos-discovery.md
@@ -53,9 +53,14 @@ initial seedlist discovery. Periodic scan MUST follow these rules:
and protocol. The SRV service name is provided in the
[srvServiceName](../initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.md#srvservicename) URI option and
defaults to `mongodb`. The protocol is always `tcp`. After prefixing, the URI should look like:
- `_{srvServiceName}._tcp.{hostname}.{domainname}`.
-- A driver MUST verify that the host names returned through SRV records have the same parent `{domainname}`. When this
- verification fails, a driver:
+ `_{srvServiceName}._tcp.{hostname}.{domainname}`. Here `{hostname}.{domainname}` is the host from the connection
+ string, and is not affected by
+ [srvAllowedHostsSuffix](../initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.md#srvallowedhostssuffix):
+ that option changes which parent domain returned host names are verified against, not which records are queried.
+- A driver MUST verify that the host names returned through SRV records have the same parent `{domainname}`. When
+ [srvAllowedHostsSuffix](../initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.md#srvallowedhostssuffix)
+ is configured, its value is the parent domain used for this verification instead of the `{domainname}` derived from
+ the connection string. When this verification fails, a driver:
- MUST NOT add such a non-compliant host name to the topology
- MUST NOT raise an error
- SHOULD log the non-compliance, including the host name
@@ -165,6 +170,8 @@ No future work is expected.
## Changelog
+- 2026-09-03: Account for the `srvAllowedHostsSuffix` MongoClient option when verifying returned host names.
+
- 2024-08-22: Migrated from reStructuredText to Markdown.
- 2022-10-05: Revise spec front matter and reformat changelog.
diff --git a/source/public-suffix-list/public-suffix-list.md b/source/public-suffix-list/public-suffix-list.md
index d0a14679f4..21875b62e8 100644
--- a/source/public-suffix-list/public-suffix-list.md
+++ b/source/public-suffix-list/public-suffix-list.md
@@ -84,6 +84,10 @@ vendored file MUST therefore convert one side before comparing, so that a Punyco
`xn--` labels) and the Unicode rules it is compared against are in the same form. Comparing the two forms directly will
fail to match rules that should match.
+Note: The leading `*` and `!` markers in the PSL are not part of a label and MUST NOT be included in that conversion. A
+driver MUST strip them before converting a rule and reapply them after, so that a rule such as `!公司.cn` converts to
+`!xn--55qx5d.cn` rather than being mangled.
+
### Determining the public suffix
To determine the public suffix of a domain, a driver using the vendored file MUST follow the
@@ -187,4 +191,7 @@ notice does not apply to the rest of this repository.
## Changelog
+- 2026-09-03: Clarify that markers shouldn't be converted to Punycode and add a prose test covering internationalized
+ (Punycode vs. Unicode) rules.
+
- 2026-08-11: Vendor the Public Suffix List and add a script to sync it.
diff --git a/source/public-suffix-list/tests/README.md b/source/public-suffix-list/tests/README.md
index f4b16b8996..1a9b16ebcd 100644
--- a/source/public-suffix-list/tests/README.md
+++ b/source/public-suffix-list/tests/README.md
@@ -36,13 +36,18 @@ This test utilizes the rule `!www.ck` in the PSL, which overrides the `*.ck` rul
When no rule matches, the prevailing rule is `*` and the rightmost label alone is the public suffix. Assert that
`is_public_suffix("nosuchtld") -> true` and `is_public_suffix("foo.nosuchtld") -> false`.
+### 6. An internationalized rule
+
+This test utilizes the rules `рф` and `公司.cn` in the PSL, which are stored as Unicode while the hostnames being compared
+are Punycode-encoded. Assert that `is_public_suffix("xn--p1ai") -> true`,
+`is_public_suffix("example.xn--p1ai") -> false`, and `is_public_suffix("xn--55qx5d.cn") -> true`.
+
## Connection String Tests
The `srvAllowedHostsSuffix-psl-*` tests in the
[Initial DNS Seedlist Discovery tests](https://github.com/mongodb/specifications/tree/master/source/initial-dns-seedlist-discovery/tests/replica-set)
cover the two cases that are observable through a connection string: a suffix that is a public suffix (`cc`) is
-rejected, and one that is not (`10gen.cc`) is accepted. Both use a suffix the test SRV hosts end with, so the host
-suffix check passes and the public suffix check is the only thing that can change the outcome.
+rejected, and one that is not (`10gen.cc`) is accepted.
They live with the seedlist discovery tests because `srvAllowedHostsSuffix` is a connection string option, so a driver
should implement the parsing of the PSL and the uri option together.
diff --git a/source/uri-options/tests/srv-options.json b/source/uri-options/tests/srv-options.json
index 0670612c0d..75bb7d1c5f 100644
--- a/source/uri-options/tests/srv-options.json
+++ b/source/uri-options/tests/srv-options.json
@@ -111,6 +111,15 @@
"loadBalanced": true,
"srvMaxHosts": 0
}
+ },
+ {
+ "description": "Non-SRV URI with srvAllowedHostsSuffix",
+ "uri": "mongodb://example.com/?srvAllowedHostsSuffix=.mongodb.net",
+ "valid": false,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": null
}
]
}
diff --git a/source/uri-options/tests/srv-options.yml b/source/uri-options/tests/srv-options.yml
index 991749b0ef..f8bab2d2b9 100644
--- a/source/uri-options/tests/srv-options.yml
+++ b/source/uri-options/tests/srv-options.yml
@@ -87,3 +87,10 @@ tests:
options:
loadBalanced: true
srvMaxHosts: 0
+ - description: "Non-SRV URI with srvAllowedHostsSuffix"
+ uri: "mongodb://example.com/?srvAllowedHostsSuffix=.mongodb.net"
+ valid: false
+ warning: false
+ hosts: ~
+ auth: ~
+ options: ~
diff --git a/source/uri-options/uri-options.md b/source/uri-options/uri-options.md
index ca1a1b7cfc..ef052493c2 100644
--- a/source/uri-options/uri-options.md
+++ b/source/uri-options/uri-options.md
@@ -43,9 +43,9 @@ The driver MUST report an error if the `directConnection=true` URI option is spe
The driver MUST report an error if the `directConnection=true` URI option is specified with an SRV URI, because the URI
may resolve to multiple hosts. The driver MUST allow specifying `directConnection=false` URI option with an SRV URI.
-### srvServiceName and srvMaxHosts URI options
+### srvServiceName, srvMaxHosts, and srvAllowedHostsSuffix URI options
-For URI option validation pertaining to `srvServiceName` and `srvMaxHosts`, please see the
+For URI option validation pertaining to `srvServiceName`, `srvMaxHosts`, and `srvAllowedHostsSuffix`, please see the
[Initial DNS Seedlist Discovery spec](../initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.md#uri-validation)
for details.
@@ -104,6 +104,7 @@ to URI options apply here.
| serverSelectionTimeoutMS | positive integer; a driver may also accept 0 to be used for a special case, provided that it documents the meaning | defined in [server selection spec](../server-selection/server-selection.md#serverselectiontimeoutms) | no | A timeout in milliseconds to block for server selection before raising an error |
| serverSelectionTryOnce | "true" or "false" | defined in [server selection spec](../server-selection/server-selection.md#serverselectiontryonce) | required for single-threaded drivers | Scan the topology only once after a server selection failure instead of repeatedly until the server selection times out |
| socketTimeoutMS | non-negative integer; 0 means no timeout | no timeout | no | NOTE: This option is deprecated in favor of [timeoutMS](../client-side-operations-timeout/client-side-operations-timeout.md#timeoutms)
Amount of time spent attempting to send or receive on a socket before timing out; note that this only applies to application operations, not SDAM. |
+| srvAllowedHostsSuffix | a valid DNS hostname suffix (e.g. ".mydomain.net") | none; domain is inferred from the SRV hostname | no | A hostname suffix used to validate hosts returned via SRV lookup, replacing the domain inferred from the SRV hostname. Defined in the [Initial DNS Seedlist Discovery spec](../initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.md#srvallowedhostssuffix). |
| srvMaxHosts | non-negative integer; 0 means no maximum | defined in the [Initial DNS Seedlist Discovery spec](../initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.md#srvmaxhosts) | no | The maximum number of SRV results to randomly select when initially populating the seedlist or, during SRV polling, adding new hosts to the topology. |
| srvServiceName | a valid SRV service name according to [RFC 6335](https://datatracker.ietf.org/doc/html/rfc6335#section-5.1) | "mongodb" | no | the service name to use for SRV lookup in [initial DNS seedlist discovery](../initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.md#srvservicename) and [SRV polling](../polling-srv-records-for-mongos-discovery/polling-srv-records-for-mongos-discovery.md) |
| ssl | "true" or "false" | same as "tls" | no | alias of "tls"; required to ensure that Atlas connection strings continue to work |
@@ -184,6 +185,8 @@ changes.
## Changelog
+- 2026-09-03: Add `srvAllowedHostsSuffix` option.
+
- 2026-06-17: Remove pre-4.2 version references.
- 2024-05-08: Migrated from reStructuredText to Markdown.