Skip to content

v0.5.0

Choose a tag to compare

@night4me night4me released this 21 Aug 21:33
· 122 commits to main since this release

Major READ capability expansion — no WRITE change, no security-model
change.
Public MCP contract grows from 42 to 84 READ tools (exactly a
100% increase over the last published baseline, v0.4.2), covering
roughly 80% of the useful READ capability universe identified by this
project's own capability discovery audit (267 OpenAPI paths / 243 GET
operations reviewed; every GET given exactly one disposition — up from
roughly 40% coverage at the v0.4.2 baseline). Every tool below was
independently re-verified for secret-bearing fields before
implementation, then verified against a real pfSense instance (LAB or,
where explicitly owner-authorized, production) before public
registration — never assumed from schema alone. Closes with an
independent, adversarial release-readiness audit (security regression
sweep, CE/Plus compatibility verification including a live production
Plus 26.07 pass, packaging/fresh-install/upgrade-path verification, and
a full README restructure) — see "Security" and "Changed" below for its
findings.

Added

  • 2 new READ tools (production live-verified, pre-dating the P0
    backlog below), public MCP contract 42 → 44 (0 WRITE, unchanged):
    • pfsense_get_firewall_nat_outbound_mappings — outbound NAT
      mappings (source/destination/target address/alias fields
      redacted by default, matching FirewallNatPortForward's
      established convention; source_hash_key is a hash seed, not a
      credential, and stays visible).
    • pfsense_get_firewall_nat_one_to_one_mappings — 1:1 NAT mappings
      (external/source/destination redacted the same way).
    • Owner-authorized, narrowly-scoped live production READ
      verification (not LAB) of exactly these two endpoints: both typed
      GETs succeeded with zero configured mappings in either category;
      field-level type/nullability compatibility confirmed via an exact,
      byte-for-byte match between the live OpenAPI schema and the pinned
      v2.10 reference the models were derived from.
  • 7 new READ tools, public MCP contract 44 → 51 (0 WRITE, unchanged):
    • pfsense_get_interface_vlans — 802.1Q VLAN interfaces.
    • pfsense_get_routing_static_routes — static routes (network/gateway
      redacted by default).
    • pfsense_get_interface_groups — interface group membership, useful
      for interpreting firewall rules that target a group.
    • pfsense_get_firewall_schedules — time-based firewall schedules.
    • pfsense_get_system_restapi_version — installed REST API package
      version and update availability.
    • pfsense_get_firewall_virtual_ips — CARP/IP-alias/proxy-ARP virtual
      IPs (address fields redacted by default; the CARP shared secret is
      never modeled at all — see "Security" below).
    • pfsense_get_system_certificate_authorities — trusted CA inventory
      (the CA private key is never modeled at all — see "Security" below).
    • Two of the seven (system_restapi_version,
      system_certificate_authorities) were verified against real,
      populated LAB objects, not just an empty envelope — the latter
      against the LAB's own internal CA.
  • 2 more READ tools (P1 Batch A, partial), public MCP contract 51 →
    53 (0 WRITE, unchanged):
    • pfsense_get_status_ipsec_sas — live IPsec SA/tunnel status,
      including nested child SAs.
    • pfsense_get_status_ipsec_child_sas — live IPsec child SA status.
    • IPsecSaStatus.child_sas embeds full IPsecChildSaStatus objects
      (schema-confirmed $ref) and is constructed through that model's
      own parser for every nested item, not passed through as a raw dict.
    • status/wireguard/tunnels/status/wireguard/peers were also
      implemented and offline-tested this batch but remained unregistered
      at first: this LAB did not have pfSense-pkg-WireGuard installed,
      so live verification was blocked — see the next entry for how this
      was resolved.
  • 2 more READ tools (P1 Batch A completion), public MCP contract 53 →
    55 (0 WRITE, unchanged):
    owner explicitly authorized installing
    pfSense-pkg-WireGuard on the LAB for non-production READ
    verification only. Preflight: reconfirmed LAB identity distinct from
    production, identified the LAB as the sole pfSense-named VM in its
    Proxmox cluster, and took a fresh rollback snapshot before any
    change. Post-install: confirmed pfSense/pfREST healthy, re-ran a
    52-tool regression subset with zero regressions, then live-verified
    both endpoints (HTTP 200, correct envelope, zero configured tunnels/
    peers; raw responses inspected directly for unexpected fields).
    • pfsense_get_status_wireguard_tunnels — live WireGuard tunnel
      status, including nested peer status.
    • pfsense_get_status_wireguard_peers — live WireGuard peer status.
    • The package installation was a one-off authenticated LAB
      administrative call, made outside and independent of this
      project's own WriteApiClient/WriteEndpoints allow-list
      mechanism, which remains untouched and still empty except
      FIREWALL_ALIAS_DESCRIPTION — not a WRITE-capability expansion of
      the shipped server.
  • 4 more READ tools (P1 Batch B), public MCP contract 55 → 59 (0
    WRITE, unchanged):
    • pfsense_get_status_openvpn_servers — live OpenVPN server status,
      including nested connection/route status.
    • pfsense_get_status_openvpn_clients — live OpenVPN client status.
    • pfsense_get_status_openvpn_server_connections — flat, all-servers
      OpenVPN client connection status.
    • pfsense_get_status_openvpn_server_routes — flat, all-servers
      OpenVPN client route status.
    • Resolved the standing open question of whether the standalone
      connection/route endpoints duplicate OpenVpnServerStatus's own
      nested conns/routes fields using the pinned schema's own
      Parent model declaration (both standalone endpoints declare
      Parent model: OpenVPNServerStatus) — the identical structural
      relationship already established as non-redundant between
      IPsecSaStatus/IPsecChildSaStatus, so all four were implemented
      as genuinely independent capabilities rather than assumed
      duplicates.
    • OpenVpnServerStatus.conns/.routes embed full
      OpenVpnServerConnectionStatus/OpenVpnServerRouteStatus objects
      (schema-confirmed $ref) and are constructed through those
      models' own parsers for every nested item, not passed through as
      raw dicts.
  • 3 more READ tools (P1 Batch C), public MCP contract 59 → 62 (0
    WRITE, unchanged):
    • pfsense_get_dns_forwarder_host_overrides — dnsmasq (DNS Forwarder)
      host overrides: addresses, aliases, and descriptions.
    • pfsense_get_dns_resolver_domain_overrides — Unbound (DNS Resolver)
      domain overrides: forwarding target address and DNS-over-TLS
      settings.
    • pfsense_get_dns_resolver_access_lists — Unbound (DNS Resolver)
      access lists: allow/deny action and the network ranges each list
      applies to.
    • All three re-checked against the pinned schema for secrets (none
      found) and modeled following the existing shipped
      DnsResolverHostOverride precedent: full field visibility, no
      include_identifying_metadata redaction, since address/network
      data is the core content of this capability class (the same
      rationale already documented for DhcpServer).
    • All three LAB-verified live: HTTP 200, zero configured objects
      (ENDPOINT_VERIFIED); no package required (base
      pfSense/dnsmasq/Unbound features).
  • 3 more READ tools (P1 Batch D, interface extras), public MCP
    contract 62 → 65 (0 WRITE, unchanged):
    • pfsense_get_interface_available_interfaces — all interfaces
      available for assignment (not just already-assigned ones):
      identifier, in-use status, hardware boot message.
    • pfsense_get_interface_gres — GRE tunnel interfaces.
    • pfsense_get_interface_laggs — LAGG (link aggregation) interfaces.
    • All three re-checked against the pinned schema for secrets (none
      found). mac (AvailableInterface) and 7 of InterfaceGRE's 11
      fields (tunnel-endpoint addresses) are redacted by default,
      matching InterfaceStatus.macaddr and RoutingStaticRoute's
      established conventions; InterfaceLAGG's members/laggif stay
      visible, matching InterfaceBridge's established no-redaction
      precedent. InterfaceLAGG's proto-conditional fields
      (lacptimeout/lagghash/failovermaster) use .get() with the
      schema's own declared default, matching the install_version
      precedent for a field that can be legitimately absent rather than
      merely null.
    • interface/available_interfaces LAB-verified with
      FIELD_MODEL_LIVE_VERIFIED: 2 real populated objects (the LAB's
      actual vtnet0/vtnet1 WAN/LAN interfaces), with redaction
      confirmed against real data. interface/gres and
      interface/laggs both LAB-verified ENDPOINT_VERIFIED (zero
      configured objects); no package required for any of the three
      (base pfSense features).
    • The models/client methods/Endpoints entries were implemented and
      offline-tested one commit before registration, deliberately
      unregistered in the interim, matching the established
      "implemented, offline-tested, blocked" precedent from P1 Batch A's
      WireGuard pair.
  • 5 more READ tools (P1 Batch E, routing + DHCP extras), public MCP
    contract 65 → 70 (0 WRITE, unchanged):
    • pfsense_get_routing_gateway_groups — gateway groups: name,
      failover trigger, description, prioritized member gateways.
    • pfsense_get_routing_gateway_default — current default IPv4/IPv6
      gateway assignment.
    • pfsense_get_dhcp_relay — DHCP Relay configuration.
    • pfsense_get_dhcp_server_address_pools — additional DHCP scopes
      across all configured DHCP servers.
    • pfsense_get_dhcp_server_custom_options — DHCP custom options
      across all configured DHCP servers.
    • RoutingGatewayGroupPriority.gateway/.virtual_ip and
      DefaultGateway.defaultgw4/.defaultgw6 (gateway name references)
      and DHCPRelay.server (literal relay target addresses) are
      redacted by default, matching RoutingStaticRoute.gateway and
      GatewayConfig.gateway's established conventions.
      RoutingGatewayGroup.priorities embeds full
      RoutingGatewayGroupPriority objects and is constructed through
      that model's own from_api() for every item.
      DHCPServerAddressPool/DHCPServerCustomOption are schema-declared
      children of DHCPServer (Parent model: DHCPServer) and follow
      that resource's own established no-redaction convention instead
      ("the whole point of a DHCP server (scope) configuration
      capability").
    • LAB verification found a genuine CE 2.9.0 nullability discrepancy:
      DHCPRelay.interface returned null on the LAB's unconfigured
      DHCP Relay despite the pinned schema declaring it nullable: false
      — fixed by widening the field before promoting, matching the
      SystemRestApiVersion.install_version/DhcpServer precedent.
    • All five reached ENDPOINT_VERIFIED; no package required for any
      of the five (base pfSense features).
    • The models/client methods/Endpoints entries were implemented and
      offline-tested one commit before registration, deliberately
      unregistered in the interim, matching the established
      "implemented, offline-tested, blocked" precedent from P1 Batch A's
      WireGuard pair.
  • 5 more READ tools (P1 Batch F, system identity/config cluster),
    public MCP contract 70 → 75 (0 WRITE, unchanged):
    • pfsense_get_system_hostname — system hostname and domain.
    • pfsense_get_system_timezone — system timezone.
    • pfsense_get_system_dns — system DNS settings: override policy,
      local-vs-remote resolution preference, remote DNS servers.
    • pfsense_get_system_console — whether a password is required to
      access the system console.
    • pfsense_get_system_webgui_settings — web GUI listener settings:
      protocol, port, assigned TLS certificate reference.
    • SystemHostname.hostname/.domain are redacted by default (a
      conservative-posture judgment call, not a schema-confirmed
      secret — they identify the specific managed appliance/network) and
      SystemDNS.dnsserver (literal DNS server addresses) is redacted by
      default, matching RoutingStaticRoute.gateway/GatewayConfig.gateway's
      established conventions. WebGUISettings was independently
      re-verified secret-free during this batch's own re-check
      (protocol/port/sslcertref only — sslcertref is a certificate
      reference, not key material).
    • LAB-verified: system/hostname, system/timezone,
      system/console, and system/webgui/settings all reached
      FIELD_MODEL_LIVE_VERIFIED (real populated data, not just an empty
      envelope); system/dns reached ENDPOINT_VERIFIED (no remote DNS
      servers configured on this LAB). No package required for any of
      the five (base pfSense features).
    • The models/client methods/Endpoints entries were implemented and
      offline-tested one commit before registration, deliberately
      unregistered in the interim, matching the established
      "implemented, offline-tested, blocked" precedent from P1 Batch A's
      WireGuard pair.
  • 3 more READ tools (P1 Batch G, REST API + PKI metadata), public
    MCP contract 75 → 78 (0 WRITE, unchanged):
    • pfsense_get_system_restapi_access_list — the REST API's own IP
      allow/deny access list entries.
    • pfsense_get_system_crls — Certificate Revocation Lists (CRLs).
    • pfsense_get_system_package_available — packages available for
      installation.
    • RESTAPIAccessListEntry.network (the REST API's own literal IP
      allow/deny CIDR) is redacted by default, matching
      GatewayConfig.gateway's established convention.
      CertificateRevocationList.cert/.text are each schema-documented
      as only available for a specific method value and are treated as
      genuinely possibly-absent, matching the InterfaceLAGG precedent.
    • Found during re-verification that
      CertificateRevocationListRevokedCertificate has five schema
      fields marked writeOnly: truecrt, caref, descr, type,
      and prv, confirmed to be the revoked certificate's X509
      private key
      — none of these are ever present in a real GET
      response, and none are modeled, matching the
      CertificateAuthority.prv/SystemCertificate.prv precedent
      exactly rather than trusting the schema's writeOnly promise
      alone.
    • LAB-verified: system/restapi/access_list and
      system/package/available both reached FIELD_MODEL_LIVE_VERIFIED
      (2 real default allow-all entries; 69 real available packages);
      system/crls reached ENDPOINT_VERIFIED (zero configured CRLs on
      this LAB). No package required for any of the three (base pfSense
      features).
    • The models/client methods/Endpoints entries were implemented and
      offline-tested one commit before registration, deliberately
      unregistered in the interim, matching the established
      "implemented, offline-tested, blocked" precedent from P1 Batch A's
      WireGuard pair.
  • 1 more READ tool (P1 Batch H), public MCP contract 78 → 79 (0
    WRITE, unchanged):
    • pfsense_get_firewall_traffic_shapers — traffic shapers:
      interface, scheduler algorithm, bandwidth, and child queues.
    • No field is redacted (pure QoS/bandwidth-shaping configuration
      data, no addresses). Of TrafficShaperQueue's 27 fields, only 6
      are schema-required; the other 21 are each documented as only
      available for a specific scheduler type or sibling boolean flag
      and are treated as genuinely possibly-absent via .get(),
      matching the InterfaceLAGG precedent.
    • LAB-verified ENDPOINT_VERIFIED (zero configured traffic shapers
      on this LAB); no package required (base pfSense feature).
  • 3 more READ candidates implemented and offline-tested (P1 Batch H,
    service/traffic policy cluster) but requiring an absent package —
    package-conditional, NOT a LAB-installation authorization
    :
    services/freeradius/interfaces and services/freeradius/macs
    (require pfSense-pkg-freeradius3) and services/service_watchdogs
    (requires pfSense-pkg-Service_Watchdog). Direct LAB inspection via
    the already-shipped pfsense_get_system_packages tool confirmed
    neither package is installed on this LAB — only
    pfSense-pkg-WireGuard is. FreeRADIUSInterface/FreeRADIUSMAC/
    ServiceWatchdog models, their client methods, and their Endpoints
    entries (verified=False) all exist and are fully offline-tested,
    but are left unregistered pending an owner decision on installing
    either package — matching this project's standing
    package-conditional-candidate rule (only pfSense-pkg-WireGuard
    installation was ever explicitly authorized). FreeRADIUSInterface.addr
    (listening address) and FreeRADIUSMAC.mac/5 framed_* address
    fields are redacted by default once registered, matching
    GatewayConfig.gateway/InterfaceStatus.macaddr's established
    conventions.
  • 3 more READ tools (P1 Batch I, IPsec Phase 2 + encryption
    capability lists), public MCP contract 79 → 82 (0 WRITE,
    unchanged):
    • pfsense_get_vpn_ipsec_phase2s — IPsec Phase 2 entries: mode,
      protocol, encryption/hash options, and rekey timing.
    • pfsense_get_vpn_ipsec_phase1_encryptions — IPsec Phase 1
      encryption algorithm/hash/DH-group capability options.
    • pfsense_get_vpn_ipsec_phase2_encryptions — IPsec Phase 2
      encryption algorithm capability options.
    • Re-confirmed the IPsec PSK lives only on IPsecPhase1, already
      REJECTed separately — no secret material is present on Phase 2
      itself. IPsecPhase2.localid_address/.natlocalid_address/
      .remoteid_address/.pinghost (endpoint and monitoring target
      addresses) are redacted by default, matching
      RoutingStaticRoute.gateway's established convention.
      encryption_algorithm_option is schema-documented as only
      available when protocol is 'esp' and is treated as genuinely
      possibly-absent via .get(), matching the InterfaceLAGG
      precedent; it is schema-confirmed to embed full
      IPsecPhase2Encryption objects and is constructed through that
      model's own from_api() for every item.
      IPsecPhase1Encryption/IPsecPhase2Encryption are pure
      algorithm/cipher capability reference data, no redaction needed.
    • LAB-verified ENDPOINT_VERIFIED for all three (zero configured
      Phase 2 entries; the encryption capability lists were also empty
      on this LAB, since no IPsec Phase 1 is configured to derive
      options from). No package required for any of the three (base
      pfSense feature).
  • 2 more READ tools (P1 Batch J, OpenVPN server config +
    client-specific overrides), public MCP contract 82 → 84 (0 WRITE,
    unchanged):
    • pfsense_get_vpn_openvpn_servers — OpenVPN server configurations:
      mode, protocol, TLS/cert references, ciphers, and topology
      (OpenVpnServer, 73 fields).
    • pfsense_get_vpn_openvpn_csos — OpenVPN client-specific overrides:
      per-client tunnel settings, allowed servers, and DNS/NTP/WINS
      pushes (OpenVpnClientSpecificOverride, 27 fields).
    • Neither schema component has any writeOnly field, unlike the
      Batch G CRL case. caref/certref are CA/certificate
      references, not certificate material, matching this project's
      established treatment of reference IDs as non-secret.
      tlsauth_keydir is re-confirmed (a fourth time across sessions)
      to be a direction-flag enum, not key material. The singular
      vpn/openvpn/server endpoint is redundant with the plural
      vpn/openvpn/servers (same underlying model) and is deliberately
      not implemented, matching the established NAT-mappings precedent.
    • Network/address fields (local_network/local_networkv6/
      remote_network/remote_networkv6/tunnel_network/
      tunnel_networkv6/dns_server1-4/ntp_server1-2/
      wins_server1-2/serverbridge_dhcp_start/serverbridge_dhcp_end
      on the server model; common_name plus the same address-field set
      on the client-specific-override model) are redacted by default,
      matching RoutingStaticRoute.gateway's established convention.
    • 37 of OpenVpnServer's 73 fields and 5 of
      OpenVpnClientSpecificOverride's 27 fields are schema-documented
      as only available under a specific mode/use_tls/gwredir/
      ping_action condition and are treated as genuinely
      possibly-absent via .get(), matching the InterfaceLAGG/
      TrafficShaperQueue precedent.
    • LAB-verified ENDPOINT_VERIFIED for both (zero configured OpenVPN
      servers and zero client-specific overrides on this LAB). No
      package required for either (base pfSense feature).

Security

  • Two confirmed secret-bearing schema fields are never modeled at
    all
    , not merely redacted behind a flag: VirtualIP.password (the
    CARP shared advertisement secret between HA peers) and
    CertificateAuthority.prv (the CA private key) — mirroring the
    already-shipped SystemCertificate model's own established treatment
    of the identical prv distinction. Proven by construction
    (hasattr(model, "password"/"prv") is False), independently
    confirmed against real LAB data for the CA case.
  • A draft test fixture containing a placeholder password key was
    caught and rejected by this project's own fixture_safety.py
    prohibited-credential-field scan before it reached main — fixed by
    injecting secret-field values into the raw response only in-memory,
    at test time, never in a committed fixture file.
  • SystemRestApiVersion.install_version is modeled as optional: LAB
    verification found it genuinely absent from a real live response (not
    merely null), a compatibility finding the schema alone did not
    surface.
  • WireGuardPeerStatus.preshared_key is confirmed present in the
    live status object
    (not merely the config object) and is never
    modeled at all
    , matching the VirtualIP.password/
    CertificateAuthority.prv precedent exactly. WireGuardTunnelStatus.peers
    is schema-confirmed to embed full WireGuardPeerStatus objects and is
    constructed through that model's own parser for every nested item —
    a raw-dict passthrough there would have silently leaked
    preshared_key verbatim into the tunnel-status tool's output.
    Independently re-confirmed via the owner's pfREST 2.10 settings-UI
    sensitive-field classification, which agrees with every exclusion
    this project has already made
    (OpenVPNClient.proxy_passwd/.auth_pass, VirtualIP.password,
    CertificateAuthority.prv, WireGuardPeer.presharedkey,
    WireGuardPeerStatus.preshared_key, WireGuardTunnel.privatekey) —
    this project's own model-level exclusions remain the enforcement
    mechanism regardless; pfREST's own redaction behavior, if any, is
    never relied upon.
  • OpenVpnServerConnectionStatus.common_name/.user_name and the
    analogous common_name fields elsewhere in the OpenVPN status
    cluster are real per-connection human/device identity data, not
    merely network topology — redacted by default like other identifying
    fields, with the extra care this class of field warrants noted
    explicitly rather than treated as an ordinary address field.
  • Release-readiness audit (2026-08-21): hardened the global
    credential-disclosure regression test.

    tests/test_credential_non_disclosure.py's PROHIBITED_FIELDS set,
    which is scanned against every registered tool's full input/output
    MCP schema, previously only checked for {ipsecpsk, password, key}
    by exact field name — a future field literally named auth_pass,
    proxy_passwd, privatekey, presharedkey, preshared_key, or
    prv would not have been caught by this specific automated check
    even though this project has explicitly committed to excluding
    every one of those names. Expanded the set to all seven. Confirmed
    zero regressions and zero live hits with the expanded set across all
    84 registered tools.

Changed

  • Release-readiness audit (2026-08-21): independently re-verified and
    documented pfSense CE/Plus compatibility.
    pfSense CE 2.9.0 remains
    the LAB VERIFIED baseline (unchanged). Added pfSense Plus
    26.07 — LIVE VERIFIED
    : an owner-authorized, strictly READ-only
    production compatibility pass (identity verified first; no
    POST/PUT/PATCH/DELETE, no package/config/privilege changes performed)
    found the live OpenAPI schema structurally identical to the pinned
    v2.10 reference (267/267 paths, 186/186 components — the only
    differences across every field were 5 instance-specific runtime
    default values, never a type or nullability change), and successfully
    exercised 82 of the 84 public READ tools against real production data
    (30 of those as valid, meaningful empty envelopes); the remaining 2
    (pfsense_get_status_wireguard_tunnels/_peers) were correctly and
    automatically classified package-absent (WireGuard not installed on
    that appliance, and not installed by this audit) rather than treated
    as a failure. Zero genuine incompatibilities found; a targeted
    secret-safety re-check against the seven highest-risk live tool
    responses found zero prohibited field names. Added pfSense Plus
    25.11 — SUPPORTED/COMPATIBLE (not live-verified)
    , an explicit
    inference from converging platform/schema evidence (same FreeBSD
    16-CURRENT base OS as the verified CE 2.9.0/Plus 26.07 evidence; one
    platform-version step from a build already proven to have zero schema
    drift; the same pinned pfREST v2.10 package already confirmed
    compatible across three separate platform/edition combinations),
    explicitly not a test result.
  • Corrected the published-baseline framing used throughout this
    [Unreleased] narrative.
    Git archaeology (checking
    KNOWN_READ_TOOL_NAMES's length at the actual v0.4.2 git tag,
    cross-checked against README's and v0.4.2's own CHANGELOG entry,
    both of which already correctly said 42) proved the true
    last-published baseline this entire READ-expansion audit measures
    against was 42 tools, not 44 as this section originally stated.
    The correct headline for this release is 42 → 84 public READ
    tools, exactly a 100% increase
    (not 44 → 84 / ~91%). The two
    "extra" tools accounting for the discrepancy
    (pfsense_get_firewall_nat_outbound_mappings/
    pfsense_get_firewall_nat_one_to_one_mappings) were live-verified
    against production and registered before this audit's own tracked
    narrative began, but had never received their own Added bullet —
    fixed above.
  • Full README restructure (key facts → what you can do → why this
    server → quick start → requirements/compatibility → MCP client setup
    → capability overview → security model → troubleshooting →
    documentation → release status → contributing/license), including a
    category-level capability overview table and a symptom/cause/action
    troubleshooting table. Structural/UX patterns only (badges,
    table-based summaries, per-client setup subsections) were drawn from
    surveying other pfSense MCP projects' README conventions for
    inspiration — no wording, architecture, or security claims copied
    from any external project.

Fixed

  • LAB CE 2.8.1 → 2.9.0 platform-upgrade regression (2026-08-21): a
    full regression smoke test of all 51 public READ tools against the
    freshly upgraded LAB appliance (pfSense CE 2.9.0-RELEASE, FreeBSD
    16.0-CURRENT, reinstalled pfSense-pkg-RESTAPI v2.10 — same REST API
    version, same 267-path schema, distinct install) found 2 of 51 tools
    now fail shape validation: pfsense_get_dhcp_servers and
    pfsense_get_dns_resolver_settings. Root cause: for an unconfigured
    optional field (e.g. no DHCP scope domain/gateway set, no DNS-over-TLS
    certificate configured), the platform now returns null where the
    original 2.8.1 LAB capture had returned an empty string/list — the
    pinned schema still declares these fields nullable: false in both
    cases, so live server behavior was trusted over the schema's stale
    claim (matching this project's own install_version precedent).
    DhcpServer.domain/.domainsearchlist/.failover_peerip/.gateway/
    .mac_allow/.mac_deny and DnsResolverSettings.sslcertref/
    .tlsport are widened to also accept None; existing 2.8.1-shaped
    fixtures (empty string/list) continue to validate unchanged. All 51
    tools re-verified passing against the upgraded LAB after the fix; no
    tool count change (51 unchanged), no new capability, no security
    impact — a pure type-widening correctness fix.
  • Release-readiness audit (2026-08-21): three stale-documentation
    findings.
    Two model docstrings
    (firewall_nat_one_to_one_mapping.py, firewall_nat_outbound_mapping.py)
    still said "Not yet cross-checked... verified (False)" despite both
    Endpoints entries having been verified=True for some time.
    docs/PYPI_RELEASE.md's release checklist hardcoded "Confirm 42 READ
    tools" — would have misdirected the owner during this very release;
    replaced with a pointer to the live registry check instead of a
    number that will go stale again next release.