Safely redact sensitive fields in a pfSense config.xml export so you can share it without exposing passwords, keys, or network identifiers.
Keywords: pfSense config.xml redactor, pfSense sanitiser, firewall config sanitisation, VPN config anonymisation, OpenVPN redaction, WireGuard key redaction, IPsec secret removal, network topology anonymiser, Netgate TAC sharing
pfsense-redactor redacts secrets and optionally anonymises identifiers in pfSense config.xml exports—so you can share them safely with support, vendors, auditors, forums, or AI tools.
Unlike generic XML redaction tools, pfsense-redactor understands pfSense-specific configuration structures and VPN formats.
- ✅ pfSense-native – Understands pfSense XML structures (IPsec, OpenVPN, WireGuard, packages)
- ✅ Zero dependencies – Pure Python stdlib, runs anywhere (Windows, macOS, Linux)
- ✅ Well-tested – Extensively tested across multiple OSes and Python versions
- ✅ Topology-preserving – Anonymise without breaking routing logic or firewall rules
- ✅ Security-first – Built-in protections against path traversal and symlink attacks
- ✅ CI/CD ready – Suitable for automated compliance workflows and GitOps
Use pfsense-redactor when you need to share a pfSense config.xml file
outside the firewall (for example with vendors, consultants, forums, or AI tools)
and want to remove secrets and/or anonymise network identifiers without
breaking topology or routing logic.
pfsense-redactor config.xml redacted.xml --keep-private-ipsKeeps internal addressing visible while removing secrets and redacting public identifiers.
pfsense-redactor config.xml redacted.xml --anonymiseReplaces identifiers with consistent placeholders so relationships remain clear while reducing disclosure.
For pfSense on-firewall sanitisation, see diag_sanitize.php below; pfsense-redactor is intended for off-box use and anonymisation.
See the Usage section and the Command-Line Flags Reference below for all available options.
Common command patterns depending on who you’re sharing with.
# On the firewall (recommended for TAC)
/usr/local/sbin/diag_sanitize.php /conf/config.xml > /conf/config_sanitised.xml
# Off-box: additional anonymisation before sharing further
pfsense-redactor config_sanitised.xml support-safe.xml --keep-private-ips --no-redact-domainsUse --aggressive if you have third-party packages or aren’t sure where secrets live.
# Topology-preserving anonymisation (private IPs are kept visible by default with --anonymise)
pfsense-redactor config.xml ai-ready.xml --anonymise --aggressive
# Now safe to upload to AI tools for configuration analysis# Anonymise everything (including private IPs)
pfsense-redactor config.xml ai-ready.xml --anonymise --no-keep-private-ips --aggressive
# Now safe to upload to AI tools for configuration analysis# Option A: Preserve private IPs for troubleshooting context
pfsense-redactor config.xml vendor-share.xml --anonymise# Option B: Anonymise everything (including private IPs) for stricter privacy
pfsense-redactor config.xml vendor-share.xml --anonymise --no-keep-private-ips# Preview what will be redacted before sharing
pfsense-redactor config.xml --dry-run-verbose# CI/CD integration for automated sanitisation
pfsense-redactor $INPUT_CONFIG $OUTPUT_CONFIG --aggressive --fail-on-warnpfSense includes a built-in configuration sanitisation script:
/usr/local/sbin/diag_sanitize.php /conf/config.xml > /conf/config_sanitised.xmlThis official tool runs on the firewall itself and is primarily intended for safely sharing configurations with Netgate support. It removes high-value secrets (password hashes, pre-shared keys, certificates, etc.) while preserving the original network topology.
pfsense-redactor is complementary, not a replacement.
Built-in diag_sanitize.php |
pfsense-redactor |
|---|---|
| Runs on pfSense only | Runs anywhere (workstation, CI, automation) |
| PHP, internal to pfSense | Python, standalone, MIT-licensed |
| Fixed sanitisation behaviour | Configurable redaction and anonymisation |
| Removes secrets | Removes secrets and can anonymise IPs, domains, MACs and URLs |
| Best suited to Netgate support (TAC) | Best suited to vendors, consultants, AI tools and forums |
pfsense-redactor exists to cover use cases where:
- the configuration has already been exported,
- you do not wish to run additional tooling on the firewall,
- or you require privacy-preserving anonymisation in addition to basic secret removal.
Both tools share the same goal: preventing accidental disclosure of sensitive information when sharing pfSense configurations.
Comparison with Alternatives
| Feature | pfsense-redactor | Generic XML Tools | Manual Redaction | Built-in diag_sanitize.php |
|---|---|---|---|---|
| pfSense-aware structure | ✅ | ❌ | ✅ | |
| Runs off-firewall | ✅ | ✅ | ✅ | ❌ (pfSense only) |
| Network anonymisation | ✅ | ❌ | ❌ | |
| Topology preservation | ✅ | ❌ | ✅ | |
| Configurable modes | ✅ multiple modes | ❌ | N/A | ❌ Fixed |
| CIDR allow-lists | ✅ | ❌ | ❌ | |
| CI/CD integration | ✅ | ❌ | ||
| Cross-platform | ✅ | ✅ | ❌ | |
| WireGuard/IPsec aware | ✅ | ❌ | ✅ | |
| Zero dependencies | ✅ | ✅ | ✅ |
pfsense-redactor removes secrets such as passwords, private keys, certificates, tokens, and shared secrets.
It also redacts public IP addresses, domains, MAC addresses, and URLs unless explicitly preserved.
It supports both. Redaction removes sensitive values entirely, while anonymisation replaces identifiers with consistent placeholders so topology and relationships remain clear.
No. Deterministic anonymisation ensures the same identifier is always replaced with the same alias, preserving logical relationships and routing flow.
Yes. pfsense-redactor is designed for sharing configurations externally without exposing secrets or identifiable network information.
Redacted output is for analysis only and must not be restored to pfSense.
Yes. Unlike generic XML redaction tools, pfsense-redactor understands pfSense configuration layouts, including VPNs, interfaces, gateways, and common package XML structures.
Use --aggressive when sharing configurations publicly or when third-party packages may include unknown sensitive fields. This mode applies broader redaction.
No. Redacted output is for analysis/sharing only and must never be imported back into pfSense.
pip install pfsense-redactorNote: If you encounter an
externally-managed-environmenterror (common on macOS and modern Linux distributions), use one of these alternatives:Option 1: Install with pipx (recommended for CLI tools)
brew install pipx pipx install pfsense-redactorOption 2: Use a virtual environment
python3 -m venv venv source venv/bin/activate pip install pfsense-redactorOption 3: Install in user space
pip install --user pfsense-redactor
git clone https://github.com/grounzero/pfsense-redactor.git
cd pfsense-redactorOption 1: Development mode (recommended for contributing)
pip install -e .Option 2: With virtual environment
python3 -m venv venv
source venv/bin/activate
pip install -e .The tool preserves network architecture and routing logic whilst sanitising secrets and identifiers allowing safe troubleshooting and topology review without disclosing private data.
Keeps firewall and routing context
Removes passwords, keys, public IPs (optional), tokens, certs
Supports anonymisation for consistent placeholder mapping
Understands pfSense config structures, namespaces, VPNs, WireGuard, XML attributes, IPv6 zone IDs
- Passwords & encrypted passwords
- Pre-shared keys (IPSec, OpenVPN, WireGuard)
- TLS/OpenVPN static keys & certs
- SNMP community strings
- LDAP / RADIUS secrets
- API keys & tokens
- PEM blocks (RSA / EC / OpenSSH)
- Subnets & masks (255.x.x.x always preserved)
- Router topology
- VLAN and VPN interfaces
- Firewall rules and gateways
| Data | Behaviour |
|---|---|
| Internal IPs | Preserve with --keep-private-ips |
| Public IPs | Mask or anonymise |
| Email addresses | Mask or anonymise |
| URLs | Preserve structure, mask hostname |
| MAC addresses | Mask format-preserving |
| Certificates | Collapse to [REDACTED_CERT_OR_KEY] |
| Mode | Purpose |
|---|---|
| Default | Safe redaction for sharing logs |
--keep-private-ips |
Preserve private IPs (best for support/AI) |
--anonymise |
Replace identifiers with consistent placeholders (IP_1, domain3.example) |
--aggressive |
Scrub all fields (plugins/custom XML) |
- Python 3.9+
# Output filename auto-generated as config-redacted.xml
pfsense-redactor config.xml
# Or specify output filename explicitly
pfsense-redactor config.xml redacted.xmlpfsense-redactor config.xml redacted.xml --keep-private-ips# Preserve specific public services (never redact)
pfsense-redactor config.xml --allowlist-ip 8.8.8.8 --allowlist-domain time.nist.gov
# Preserve entire CIDR ranges
pfsense-redactor config.xml --allowlist-ip 203.0.113.0/24
# Use an allow-list file (supports IPs, CIDRs, and domains)
pfsense-redactor config.xml --allowlist-file my-allowlist.txtpfsense-redactor config.xml redacted.xml --anonymisepfsense-redactor config.xml redacted.xml --no-redact-domains --keep-private-ipspfsense-redactor config.xml redacted.xml --aggressive# Show statistics only
pfsense-redactor config.xml --dry-run
# Show statistics with sample redactions (safely masked)
pfsense-redactor config.xml --dry-run-verbosepfsense-redactor config.xml --stdout > redacted.xmlpfsense-redactor config.xml --inplace --force| Flag | Description |
|---|---|
--version |
Show program version and exit |
--check-version |
Check for updates from PyPI |
-h, --help |
Show help message and exit |
| Flag | Description |
|---|---|
input |
Input pfSense config.xml file (positional argument) |
output |
Output redacted config.xml file (positional argument, optional with --stdout/--dry-run/--inplace) |
--stdout |
Write redacted XML to stdout instead of file |
--inplace |
Overwrite input file with redacted output (use with caution) |
--force |
Overwrite output file if it already exists |
--allow-absolute-paths |
Allow absolute file paths (relative paths only by default for security) |
| Flag | Description |
|---|---|
--keep-private-ips |
Keep non-global IP addresses visible (RFC1918/ULA/loopback/link-local). Netmasks and unspecified addresses (0.0.0.0, ::) always preserved |
--no-keep-private-ips |
When used with --anonymise, do NOT keep private IPs visible (mask all IPs) |
--anonymise |
Use consistent aliases (IP_1, domain1.example) to preserve network topology. Implies --keep-private-ips unless --no-keep-private-ips specified |
--aggressive |
Apply IP/domain redaction to all element text, not just known fields |
--no-redact-ips |
Do not redact IP addresses |
--no-redact-domains |
Do not redact domain names |
--redact-url-usernames |
Redact usernames in URLs (default: preserve usernames, always redact passwords) |
Allow-lists
- Allow-lists let you preserve specific well-known IPs and domains that don't leak private information.
| Flag | Description |
|---|---|
--allowlist-ip IP_OR_CIDR |
IP address or CIDR network to never redact (repeatable). Applies to text and URLs |
--allowlist-domain DOMAIN |
Domain to never redact (repeatable, case-insensitive, supports suffix matching). Applies to bare FQDNs and URL hostnames |
--allowlist-file PATH |
File containing IPs, CIDR networks, and domains to never redact (one per line) |
--no-default-allowlist |
Do not load default allow-list files (.pfsense-allowlist in current dir or ~/.pfsense-allowlist) |
Testing & Diagnostics
| Flag | Description |
|---|---|
--dry-run |
Show statistics only, do not write output file |
--dry-run-verbose |
Show statistics with sample redactions (safely masked to prevent leaks) |
--fail-on-warn |
Exit with non-zero code if root tag is not 'pfsense' (useful in CI) |
| Flag | Description |
|---|---|
-q, --quiet |
Suppress progress messages (show only warnings and errors) |
-v, --verbose |
Show detailed debug information |
Allow-lists let you preserve specific well-known IPs and domains that don't leak private information.
The tool automatically loads allow-lists from these locations (if they exist):
.pfsense-allowlistin current directory~/.pfsense-allowlistin home directory
To disable: use --no-default-allowlist
Create .pfsense-allowlist or use --allowlist-file:
# Comments start with #
# One item per line (IP, CIDR, or domain)
# Public DNS servers
8.8.8.8
1.1.1.1
# Cloud provider ranges
203.0.113.0/24
198.51.100.0/24
# NTP servers (suffix matching: preserves time.nist.gov and *.time.nist.gov)
time.nist.gov
pool.ntp.org
# Wildcard domains (*.example.org preserves all subdomains)
*.pfsense.org
See allowlist.example for a complete template.
# Add specific IPs or CIDR ranges (repeatable)
--allowlist-ip 8.8.8.8 --allowlist-ip 203.0.113.0/24
# Add specific domains (repeatable, case-insensitive, supports suffix matching)
--allowlist-domain time.nist.gov --allowlist-domain pool.ntp.org
# Load from file (supports IPs, CIDRs, and domains)
--allowlist-file /path/to/allowlist.txt
# Disable default file loading
--no-default-allowlistFeatures:
- CIDR support:
203.0.113.0/24preserves all IPs in that range - Suffix matching:
example.orgpreservessub.example.org,db.corp.example.org, etc. - Wildcard domains:
*.example.orgis equivalent to suffix matching onexample.org - IDNA/punycode: Automatically handles internationalised domains (e.g.,
bücher.example↔xn--bcher-kva.example) - Merged sources: All CLI flags, files, and default files are combined
Note: Items in allow-lists are never redacted in:
- Raw text IP/domain references
- URL hostnames
- Bare FQDNs
<openvpn>
<server>
<local>192.168.10.1</local>
<tlsauth>-----BEGIN OpenVPN Static key-----ABC123...</tlsauth>
<remote>198.51.100.10</remote>
<remote_port>443</remote_port>
</server>
</openvpn><openvpn>
<server>
<local>192.168.10.1</local>
<tlsauth>[REDACTED]</tlsauth>
<remote>XXX.XXX.XXX.XXX</remote>
<remote_port>443</remote_port>
</server>
</openvpn><openvpn>
<server>
<local>IP_1</local>
<tlsauth>[REDACTED]</tlsauth>
<remote>IP_2</remote>
<remote_port>443</remote_port>
</server>
</openvpn>Never restore the redacted file to pfSense.
Redacted output is for analysis only, because:
- CDATA and comments are removed by XML parser
- PEM blocks and binary data are collapsed
- Some optional metadata fields may be stripped
Always keep the original secure copy.
The tool includes built-in protections against malicious file path operations:
Default behaviour (secure):
- Only relative paths are allowed by default
- Directory traversal (
../../../etc/passwd) is blocked - Paths with null bytes are rejected
- Writing to system directories (
/etc,/sys,/proc,/Windows/System32, etc.) is blocked - Safe locations (home directory, current working directory, temp directories) are automatically allowed
Using --allow-absolute-paths:
- Enables absolute paths for intentional use cases
- Still blocks writes to sensitive system directories
- Still blocks directory traversal attempts
- Useful when you need to specify full paths explicitly
Examples:
# Safe: relative path (default)
pfsense-redactor config.xml output.xml
# Blocked: absolute path without flag
pfsense-redactor /etc/config.xml output.xml
# Error: Absolute paths not allowed (use --allow-absolute-paths)
# Blocked: directory traversal
pfsense-redactor ../../../etc/passwd output.xml
# Error: Path contains directory traversal components (..)
# Blocked: writing to system directory (even with flag)
pfsense-redactor config.xml /etc/output.xml --allow-absolute-paths
# Error: Cannot write to sensitive system directory
# Allowed: absolute path to safe location with flag
pfsense-redactor ~/config.xml ~/output.xml --allow-absolute-paths
# Blocked: in-place editing of system files
pfsense-redactor /etc/hosts --inplace --force --allow-absolute-paths
# Error: Cannot use --inplace with this fileProtected system directories:
- Unix/Linux:
/etc,/sys,/proc,/dev,/boot,/root,/bin,/sbin,/usr/bin,/usr/sbin,/lib,/lib64,/var/log,/var/run,/tmp,/run - Windows:
C:\Windows,C:\Windows\System32,C:\Program Files,C:\ProgramData - Critical files:
/etc/passwd,/etc/shadow,/etc/sudoers, etc.
# Statistics only
pfsense-redactor config.xml --dry-run
# Statistics with sample redactions (safely masked to avoid leaks)
pfsense-redactor config.xml --dry-run-verboseSample output with --dry-run-verbose:
[+] Redaction summary:
- Passwords/keys/secrets: 10
- Certificates: 6
- IP addresses: 26
- Domain names: 47
[+] Samples of changes (limit N=5):
IP: 198.51.***.42 → XXX.XXX.XXX.XXX
IP: 2001:db8:*:****::1 → XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX
URL: https://198.51.***.42/admin → https://XXX.XXX.XXX.XXX/admin
FQDN: db.***.example.org → example.com
MAC: aa:bb:**:**:ee:ff → XX:XX:XX:XX:XX:XX
Secret: p****************d (len=18) → [REDACTED]
Cert/Key: PEM blob (len≈2048) → [REDACTED_CERT_OR_KEY]
Sample masking policy (prevents leaks in dry-run output):
- IP: Keep first and last octet/segment, mask middle (e.g.,
198.51.***.42) - URL: Show full URL but mask host as above
- FQDN: Keep TLD and one left label, mask rest (e.g.,
db.***.example.org) - MAC: Mask middle octets (e.g.,
aa:bb:**:**:ee:ff) - Secret: Show length and first/last 2 chars only (e.g.,
p****************d (len=18)) - Cert/Key: Just show placeholder with length (e.g.,
PEM blob (len≈2048))
| Purpose | Command |
|---|---|
| Support & AI review | --keep-private-ips --no-redact-domains |
| Topology map w/o identifiers | --anonymise |
| Nuke everything | --aggressive |
[+] Redaction summary:
- Passwords/keys/secrets: 4
- Certificates: 2
- IP addresses: 11
- MAC addresses: 3
- Domain names: 5
- Email addresses: 1
- URLs: 2
Pull requests welcome. Particularly:
- Additional pfSense element coverage
- Plugin XML tag packs (WireGuard, pfBlockerNG, HAProxy, Snort, ACME, FRR)
- Unit test configs
MIT