#23185·netbox

Deleting a SiteGroup destructively cascade-deletes CircuitTerminations attached to member Sites

Author: klauspsCreated Sep 16, 2026Updated Sep 17, 2026
Labelstype: bugstatus: needs ownerseverity: lownetbox

NetBox Edition

NetBox Community

NetBox Version

v4.6.8

Python Version

3.13

Steps to Reproduce

  1. Create a SiteGroup named SG1.
  2. Create a Site named Site A and assign it to SG1.
  3. Create a provider, circuit type, and circuit.
  4. Create a CircuitTermination for the circuit with:
    • Termination type: Site
    • Termination: Site A
  5. Optionally connect a cable to the CircuitTermination to demonstrate the additional loss of the associated CableTermination.
  6. Delete only the SG1 SiteGroup.
  7. Inspect the circuit and the object change log. This behavior was observed on NetBox v4.6.8. The relevant CircuitTermination model fields still use the same deletion behavior in the current stable v4.7.1 source.

Expected Behavior

Deleting a SiteGroup should only remove that organizational grouping.

Because Site.group is nullable and uses SET_NULL, Site A should survive with no assigned group. A CircuitTermination whose actual termination target is Site A must also survive, together with its cable endpoint association.

Deleting a grouping object must not delete circuit infrastructure attached to sites that merely belonged to that group.

Observed Behavior

Site A survives and its group is cleared, but the CircuitTermination is deleted through a cascade from the hidden _site_group cache field.

If the CircuitTermination is connected to a cable, the associated CableTermination is also deleted.

This is highly dangerous and destructive behavior. The user requests deletion of an organizational grouping object, but NetBox performs unrelated, potentially large-scale deletion of circuit and cabling data whose actual termination target still exists.

In the production incident that exposed this issue, one SiteGroup deletion request caused the deletion of:

  • 147 CircuitTermination objects
  • 141 CableTermination objects

The affected Sites and Circuits themselves remained. The resulting damage was discovered only later when an external synchronization process attempted to recreate the missing terminations and cable endpoint associations.

This is a disproportionate and unexpected cascade with a substantial risk of silent mass data loss.

Suspected Cause

CircuitTermination.cache_related_objects() denormalizes the ancestry of the actual termination target into cache-only fields.

When the termination target is a Site, it stores the Site's group as follows:

https://github.com/netbox-community/netbox/blob/v4.7.1/netbox/circuits/models/circuits.py#L426-L429

python
elif termination_type == apps.get_model('dcim', 'site'):
    self._region = self.termination.region
    self._site_group = self.termination.group
    self._site = self.termination

However, the cache-only _site_group field uses on_delete=models.CASCADE:

https://github.com/netbox-community/netbox/blob/v4.7.1/netbox/circuits/models/circuits.py#L336-L342

_site_group = models.ForeignKey(
    to='dcim.SiteGroup',
    on_delete=models.CASCADE,
    related_name='circuit_terminations',
    blank=True,
    null=True
)

Consequently, Django's deletion collector treats every CircuitTermination that
cached the deleted group as a dependent object, even when its actual generic
termination target is a surviving Site.

This appears to be the same class of defect reported in #22682 and fixed by
#22693 for CachedScopeMixin models:

- https://github.com/netbox-community/netbox/issues/22682
- https://github.com/netbox-community/netbox/pull/22693

That fix covered Prefix, Cluster, and WirelessLAN. CircuitTermination maintains
a separate implementation of equivalent cache fields and therefore appears to
have been omitted from the fix.

The _region cache field also still uses CASCADE and appears vulnerable to
the same destructive behavior when deleting a Region.

### Proposed Fix

Cache-only ancestor relationships must never cause deletion of a
CircuitTermination whose actual termination target still exists.

Apply the same principle as #22693 to CircuitTermination:

- Prevent `_site_group` and `_region` cache fields from cascading deletion when
  they represent ancestors of a Site or Location termination.
- Consider `SET_NULL` for these cache-only ancestor fields.
- Preserve or explicitly implement the intended behavior for
  CircuitTerminations whose actual generic termination target is directly a
  SiteGroup or Region.
- Add a database migration for the corrected foreign-key behavior.
- Add regression tests confirming that:
  - deleting a SiteGroup preserves terminations attached to member Sites;
  - deleting a Region preserves terminations attached to descendant Sites;
  - cable endpoint associations remain intact;
  - direct Region/SiteGroup termination deletion follows the explicitly
    intended behavior.

Given the potential for unexpected mass deletion of circuit and cabling data,
this should be treated as a data-loss bug rather than a filtering or cache
consistency issue.