Skip to content

Fix copy_dict_to_element() stale children causing alias misassignment#237

Open
djlongy wants to merge 1 commit intopfsensible:masterfrom
djlongy:fix/dns-resolver-alias-misassignment
Open

Fix copy_dict_to_element() stale children causing alias misassignment#237
djlongy wants to merge 1 commit intopfsensible:masterfrom
djlongy:fix/dns-resolver-alias-misassignment

Conversation

@djlongy
Copy link

@djlongy djlongy commented Mar 1, 2026

Summary

Fix copy_dict_to_element() in pfsense.py to remove stale child elements when an XML element transitions from holding a dict/list (children) to a scalar value. Without this fix, DNS resolver host override aliases bleed into the wrong host record when the host list order changes.

Root Cause

copy_dict_to_element() pairs input list items to XML elements by position. When position N in config.xml held a host with aliases (child <item> elements under <aliases>), and the module receives a host list where position N is now a host without aliases, the scalar assignment branch sets this_elt.text but does not remove the stale <item> children. The alias-less host inherits the previous occupant's aliases.

How to Reproduce

Setup: Configure DNS resolver with two hosts — "server" (with aliases) at position 0, "other" (no aliases) at position 1.

pfsensible.core.pfsense_dns_resolver:
  state: present
  hosts:
    - host: server
      domain: example.com
      ip: 10.0.0.1
      descr: ""
      aliases:
        - {host: alias1, domain: example.com, description: "Alias 1"}
        - {host: alias2, domain: example.com, description: "Alias 2"}
    - host: other
      domain: example.com
      ip: 10.0.0.2
      descr: ""
      aliases: []

Trigger: Re-run the module with hosts in reversed order (other first, server second):

pfsensible.core.pfsense_dns_resolver:
  state: present
  hosts:
    - host: other
      domain: example.com
      ip: 10.0.0.2
      descr: ""
      aliases: []
    - host: server
      domain: example.com
      ip: 10.0.0.1
      descr: ""
      aliases:
        - {host: alias1, domain: example.com, description: "Alias 1"}
        - {host: alias2, domain: example.com, description: "Alias 2"}

Result (before fix): In config.xml, host "other" at position 0 now has alias1 and alias2 as stale <item> children — they bled from "server" which previously occupied position 0.

Result (after fix): Host "other" has no alias items. Host "server" retains its 2 aliases. Correct.

This was reproduced and confirmed on a live pfSense instance.

Fix

In the scalar assignment branch of copy_dict_to_element(), before setting .text, check if the element has child elements left over from a previous dict/list value and remove them:

# Note: ET.Element.clear() is not used here because it also
# resets .tail, which would corrupt pretty-print whitespace.
if len(this_elt) > 0:
    for child in list(this_elt):
        this_elt.remove(child)
    changed = True

Test plan

  • All 5 existing unit tests pass
  • New regression test: test_dns_resolver_hosts_reorder_aliases_stay — provides hosts in reversed order and asserts aliases stay with the correct parent
  • Confirmed on live pfSense — bug reproduced with unpatched module, fix verified with patched module
  • Idempotence verified — second run after fix shows no changes

This patch was developed with AI coding assistance (Claude Code). All changes were reviewed, tested on a live pfSense instance, and verified by a human maintainer before submission.

When copy_dict_to_element() processes a list (e.g. DNS resolver hosts),
it pairs input items to XML elements by position. If a host with aliases
(child <item> elements under <aliases>) swaps position with a host that
has no aliases, the scalar assignment branch sets the element text but
does not remove the stale child elements. The alias-less host inherits
the previous occupant's aliases.

Add a guard in the scalar assignment branch: before setting .text, check
if the element has child elements left over from a previous dict/list
value and remove them. ET.Element.clear() is intentionally avoided
because it also resets .tail, which would corrupt pretty-print
whitespace.

Add a regression test with a dedicated fixture containing two hosts
(one with aliases, one without). The test provides hosts in reversed
order and asserts that aliases remain with the correct parent after
the module rewrites the XML.
@djlongy djlongy marked this pull request as draft March 1, 2026 10:06
@djlongy djlongy marked this pull request as ready for review March 1, 2026 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant