Skip to content

gddy dns set wipes the entire zone — issues a PUT to a non-existent v3 dns-records endpoint that the API executes as a full-collection replace #136

Description

@camicasii

Summary

gddy dns set, when the target type+name already has at least one record, deletes the entire DNS zone — every A/AAAA/CNAME/MX/TXT/SRV record is removed, leaving only the GoDaddy-managed system records (NS, SOA) plus _domainconnect.

The reconcile step issues a PUT /v3/domains/zones/{zone}/dns-records/{recordId}. That endpoint does not exist in the v3 API (the OpenAPI spec at https://developer.godaddy.com/openapi/domains-v3.json defines only GET, POST, and DELETE /{recordId} for dns-records — there is no PUT). The server nonetheless returns 200 and appears to treat the request as a replace of the whole record collection with the single record carried in the body, so the rest of the zone is discarded.

--dry-run does not help: for dns set it is a no-op stub that makes no API calls and prints {"action":"dry-run: would execute"}, so it never reveals the blast radius.

Version: gddy 0.1.10 (commit a2fc6d8ba2de, built 2026-07-17)

Steps to reproduce

On a zone that has several records, target a type+name that already holds ≥1 record:

# seed a name with two A records
gddy dns add --type A --name foo --data 5.5.5.5 example.com
gddy dns add --type A --name foo --data 6.6.6.6 example.com

# now "replace" them with a single value
gddy dns set --type A --name foo --data 7.7.7.7 example.com

Expected: only the two foo A records are reconciled to foo → 7.7.7.7; every other record in the zone is untouched.

Actual: the whole zone collapses to just the system records (NS, SOA, _domainconnect). All unrelated A/CNAME/MX/TXT/SRV records are gone.

HTTP trace (--debug transport)

> GET    https://api.godaddy.com/v3/domains/zones/example.com/dns-records?name=foo&page=1&pageSize=100&totalRequired=true&type=A
< 200                                   # returns the 2 existing records

> PUT    https://api.godaddy.com/v3/domains/zones/example.com/dns-records/{recordId}
> {"data":"7.7.7.7","name":"foo","ttl":3600,"type":"A"}
< 200                                   # <-- phantom endpoint; zone gets wiped here

> DELETE https://api.godaddy.com/v3/domains/zones/example.com/dns-records/{otherRecordId}
< 404                                   # RECORD_NOT_FOUND: ids were regenerated by the PUT

The reconcile for set reuses an existing record "in place" via PUT .../dns-records/{recordId}. Since v3 has no such route, the platform treats it as a collection-level replace using only the one record in the body.

Root cause

  • The dns set reconcile calls PUT /v3/domains/zones/{zone}/dns-records/{recordId} to update an existing record in place.
  • v3 dns-records exposes only GET, POST, DELETE /{recordId} — no PUT. The API returns 200 but performs a destructive whole-collection replace, not a single-record update. The subsequent per-record DELETE then 404s because record IDs were regenerated.

Impact

High / data loss. A routine "point this record somewhere else" on any name that already exists destroys the entire zone. In our case this took down ~30 production frontends before it was understood. --dry-run gives a false sense of safety because it previews nothing for set.

Suggested fixes

  1. Stop using PUT /dns-records/{recordId} for in-place updates — it is not a real v3 endpoint. Reconcile using only the documented verbs (POST to create, DELETE /{recordId} to remove).
  2. Make dns set --dry-run actually fetch state and print the concrete plan (records to create/replace/delete), like a real preview.
  3. Until fixed, consider documenting dns set as unsafe for zones with existing records, and pointing users to add + delete.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions