v0.6.0
READ-capability expansion. Public contract: 84 → 95 READ tools
(0 default WRITE, unchanged). Useful READ coverage: 84/105 (80.0%) →
95/105 (90.5%) against this project's own capability-audit denominator
(unchanged — every candidate added this release was already counted in
that original 105, none legitimately expands it). 84 → 94 distinct READ
privileges; 85 → 95 write-protected combined privileges. Every new tool
was exercised against a real pfSense instance (LAB, this project's
disposable CE 2.9.0 appliance) before public registration — 10 of the
11 with an exact field-level key-set match against their typed model
(FIELD_MODEL_LIVE_VERIFIED); the remaining one (WireGuard tunnel
addresses) confirmed reachable and correctly authorized but returned an
empty list on this LAB (ENDPOINT_VERIFIED), so its field shape rests
on independent schema/security review rather than an observed
populated item. No package was installed to support this release; the
one package-conditional addition (WireGuard tunnel addresses) uses a
package already present on the LAB from prior, separately authorized
work.
Added
-
Schema field-drift regression protection (v0.6.0 Phase B, Batch A).
scripts/lib/schema_drift.pyprovides a general mechanism,
independently designed (not derived from the comparison project
investigated in the v0.6.0 competitive audit), that asserts every
field a pinned upstream OpenAPI schema component declares is either a
field this project's Pydantic response model already declares, or is
present in an explicit, reviewedintentional_exclusionsallowlist
(e.g.WireGuardPeerStatus.preshared_key, deliberately never
modeled). This closes a real, previously-unguarded gap: a future
pfREST release adding a field to an already-modeled response object
would otherwise go completely unnoticed, since a Pydantic model
silently ignores unknown upstream keys by construction.
tests/test_schema_field_drift.pyregisters four already-shipped
models (ConfigHistoryRevision,SystemTimezone,
WireGuardTunnelStatus,WireGuardPeerStatus) against a small,
explicitly curated fixture (tests/fixtures/pinned_response_schemas.json)
and proves the mechanism itself fires correctly against synthetic
ordinary-field, secret-like-field, nested-model, exclusion-allowlist,
stale-exclusion, and schema-evolution (nullable) cases. No MCP tool,
capability, or public contract change. Public contract remains 84
READ / 0 default WRITE. -
pfsense_get_diagnostics_config_history_revisions(v0.6.0 Phase B,
Batch B). Lists configuration-history (backup) revisions: change
timestamp, pfSense's own system-generated audit description, the
pfSense version at the time, and the backup file size. Metadata only
— the v0.6.0 Phase A qualification independently confirmed, against
the upstreamConfigHistoryRevision.incModel source (not just the
OpenAPI schema), that this endpoint's response never includes the
backup's actual configuration content, only filesystem-level metadata.
The underlying client method, typed model, andEndpointsentry
already existed (added 2026-08-16 for internal ADR-026 evidence
gathering,verified=Truefrom that session's real LAB call); this
release adds only the public MCP tool registration. A fresh
confirmatory LAB call was attempted this session but could not be
completed (the read-only LAB service account's privilege scope did
not yet include this endpoint, and granting it required admin LAB
access not available in this session) — promotion rests on the
pre-existing 2026-08-16 evidence, disclosed explicitly rather than
overstated; seedocs/PFSENSE_LEAST_PRIVILEGE_MATRIX.md. Public
contract: 84 → 85 READ tools (0 default WRITE, unchanged). -
LogSettingsmodel + client method, implemented and offline-tested
(v0.6.0 Phase B, Batch C — not yet registered). New
src/pfsense_mcp/models/log_settings.py(34 fields, all
boolean/string/integer, nowriteOnly/secret-shaped field anywhere —
re-verified directly against the pinned schema immediately before
implementation, independent of the v0.6.0 Phase A finding) and
PfSenseClient.get_status_logs_settings(). LAB verification could not
be completed this session: the read-only LAB service account's
privilege scope covers only already-registered tools, and granting
api-v2-status-logs-settings-getrequired admin LAB access that did
not authenticate successfully.Endpoints.STATUS_LOGS_SETTINGSremains
verified=False; notools/read/file,Capabilityenum member, or
registry wiring was added, matching this project's established
WireGuard-package-blocker precedent. Public contract unchanged at 85
READ / 0 default WRITE. Registered in the Batch A schema-drift
registry. -
Apply-status sweep, implemented and offline-tested (v0.6.0 Phase B,
Batch D — not yet registered). Eight new models/client methods for
firewall/virtual_ip/apply,interface/apply,routing/apply,
services/dhcp_server/apply,services/dns_forwarder/apply,
services/dns_resolver/apply,vpn/ipsec/apply,vpn/wireguard/apply
— each independently re-verified against the pinned schema (all
trivial{"applied": bool}shapes;interface/applyadditionally
has a flatpending_interfaces: list[str]; no secret material in
any). Mirrors the already-shippedFirewallApplyStatuspattern.
applied/pending_interfacesare modeled| None(schema-declared
nullable: true), deliberately not assumed non-null the way the
pre-existingFirewallApplyStatusis, since no live call has
confirmed any of these eight endpoints' actual behavior yet. None
registered as public tools — same LAB-access blocker as Batch C.
Public contract unchanged at 85 READ / 0 default WRITE. -
WireGuard tunnel addresses, implemented and offline-tested (v0.6.0
Phase B, Batch E — not yet registered). New
WireGuardTunnelAddressmodel/client method for
vpn/wireguard/tunnel/addresses(address/mask/descr, none
writeOnly, no secret material —address/maskredacted by
default, matchingRoutingStaticRoute's established convention).
Independently re-confirmed NOT redundant with the already-shipped
WireGuardTunnelStatus, which has no address/CIDR field at all —
unlike WireGuard peer allowed-IPs, deliberately not implemented
since it is already nested asWireGuardPeerStatus.allowed_ips.
pfSense-pkg-WireGuard is already installed on the LAB (prior,
separately authorized action), so package availability is not this
batch's blocker — the same read-only LAB service-account privilege
scope issue as Batches C/D is. Not registered as a public tool. Public
contract unchanged at 85 READ / 0 default WRITE. -
v0.6.0 Phase B completion: LAB-verified and registered all 10
previously-pending Batch C/D/E candidates. The read-only LAB
service account (pfsense-mcp, id=2) was synced from a stale
42-privilege snapshot (dating to its original 2026-08-19 ADR-033
provisioning) to the current full requirement, after reconciling —
with direct evidence fromAI_CONTEXT.md's own provisioning
checkpoint, not assumption — that this account is the project's
single, intentionally-provisionedwrite_protected-profile service
account (hence its pre-existingapi-v2-firewall-alias-patch
privilege, preserved throughout the sync). All 10 candidates were
then live-verified in one pass:pfsense_get_status_logs_settings—FIELD_MODEL_LIVE_VERIFIED.
Fixed a real defect found by the live call, not assumed correct:
sourceipneeded the samenullable: false→Optionalwidening
already applied to 17 other fields, missed in the earlier partial
fix — a second live parse attempt (not a first-pass assumption)
caught it.pfsense_get_firewall_virtual_ip_apply_status,
pfsense_get_interface_apply_status,
pfsense_get_routing_apply_status,
pfsense_get_dhcp_server_apply_status,
pfsense_get_dns_forwarder_apply_status,
pfsense_get_dns_resolver_apply_status,
pfsense_get_ipsec_apply_status,
pfsense_get_wireguard_apply_status— all 8
FIELD_MODEL_LIVE_VERIFIED, exact key-set match, independently
confirmed per-endpoint (not inferred from one passing).pfsense_get_vpn_wireguard_tunnel_addresses—
ENDPOINT_VERIFIED(200,{"data": []}— no tunnel addresses
configured on this LAB; field safety rests on the Phase A
schema/security review, not an observed populated item).
Public contract: 85 → 95 READ tools (0 default WRITE, unchanged).
Useful READ coverage: 95/105 (unchanged historical denominator, per
the Phase A correction).tests/fixtures/pfsense_openapi_schema_trimmed.json
extended with all 10 endpoints' real captured descriptions (needed
for the security-bootstrap/privilege-derivation test suite, which
resolves privileges from this fixture). Fixed the resulting
hardcoded-count cascade across test files,docs/API.md, and
docs/PFSENSE_LEAST_PRIVILEGE_MATRIX.md.
Fixed
- Stale tool-count/coverage claims across README, docs, and examples,
found during this release's own release-readiness audit: the 84→95
increase above was reflected indocs/API.mdand
docs/PFSENSE_LEAST_PRIVILEGE_MATRIX.md(updated as part of the
READ-capability work itself) but not yet inREADME.md(Key facts,
architecture diagram labels, capability-overview table and per-category
counts, package-conditional-tools note),docs/ARCHITECTURE_DIAGRAMS.md,
docs/API_SURFACE_ARCHITECTURE.md,docs/CONFIGURATION.md,
docs/PYPI_RELEASE.md,docs/SECURITY_MODEL.md,docs/THREAT_MODEL.md,
docs/index.md, or any of the 7 client-setup guides underexamples/.
Evidence-tier distinctions were preserved rather than strengthened:
README's Plus 26.07PRODUCTION VERIFIEDrow keeps its exact "82 of
84" historical claim unchanged (that pass predates this release's 11
additions) and now explicitly states those 11 tools areLAB VERIFIED
only, not production-verified. Historical, dated narrative describing
specific past batches/releases (e.g.v0.5.0's own "42 → 84 tools"
delta) was left untouched, matching this project's established
release-immutability convention.