CIDR-aware IP anonymizer with prefix-preserving permutation
Project description
ipanon
A deterministic, CIDR-aware IP anonymizer for log sanitization. Replaces IPv4 and IPv6 addresses in text while maintaining CIDR prefix relationships and respecting reserved/private range boundaries.
Features
- Prefix-preserving permutation — IPs sharing a /8 or larger subnet stay grouped after anonymization; subnet relationships are preserved at every prefix length
- Three-tier classification — private ranges stay private, loopback/multicast pass through unchanged, public IPs get fully anonymized
- Subnet-aware host-bit locking — preserves host bits within known subnets, preventing broadcast/network address collisions in router configs
- Deterministic — same salt always produces the same output, enabling cross-log correlation
- IPv4 + IPv6 — full support for both protocols including CIDR notation
- Streaming — reads stdin, writes stdout; works in pipelines
- No dependencies — pure Python, stdlib only
Installation
pip install ipanon
Or from source:
pip install .
Quick Start
# Anonymize a log file (auto-generated random salt printed to stderr)
cat access.log | ipanon > anonymized.log
# Reproducible anonymization with a fixed salt
ipanon --salt mysecret input.log output.log
# Save the IP mapping for later analysis
ipanon --salt mysecret -m mapping.json < input.log > output.log
How It Works
Every IP address is classified into one of three categories:
| Category | Behavior | Examples |
|---|---|---|
| A — Range-preserved | Prefix bits locked, remaining bits permuted. 10.x.y.z stays in 10.0.0.0/8, 172.16.x.y stays in 172.16.0.0/12, etc. |
10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10, 169.254.0.0/16, fc00::/7, fe80::/10 |
| B — Pass-through | Returned unchanged. | 127.0.0.1, 0.0.0.0/8, 224.0.0.0/4, ::1, ff00::/8 |
| C — Public | First octet permuted within a safe pool, lower 24 bits prefix-preserving permuted. Two IPs in the same /8 map to the same anonymized /8. | 8.8.8.8, 1.1.1.1, 2001:db8::1 |
Public IPs are guaranteed never to land in Category A or B address space after anonymization.
Mixed First-Octets
Six IPv4 first-octets (100, 169, 172, 192, 198, 203) contain both reserved sub-ranges and public IPs — for example, 172.16.0.0/12 is private but 172.217.x.x (Google) is public. Because these octets can't safely participate in the normal first-octet permutation pool, public IPs from mixed octets keep their first octet unchanged by default (with a warning).
To get collision-free anonymization for these IPs, use --remap to redirect them to a dedicated pure-public octet:
# Redirect public 172.x IPs to the 42.x range
ipanon --salt s --remap 172=42 < input.log
# Redirect multiple mixed octets
ipanon --salt s --remap 172=42 --remap 192=43 < input.log
Alternatively, use --ignore-subnets to remove the sub-/8 private ranges entirely, which eliminates the mixed-octet problem by treating those IPs as fully public:
# Only 10.0.0.0/8 stays private; 172.16.x, 192.168.x, etc. treated as public
ipanon --salt s --ignore-subnets < input.log
CLI Reference
ipanon [OPTIONS] [INPUT] [OUTPUT]
| Option | Description |
|---|---|
-s, --salt SALT |
Reproducible anonymization salt (random if omitted, printed to stderr) |
--salt-env ENVNAME |
Read salt from environment variable. Mutually exclusive with --salt |
--remap MIXED=TARGET |
Redirect public IPs from a mixed first-octet to a dedicated pure-public target (see Mixed First-Octets). Can repeat |
--pass-through CIDR |
Don't anonymize IPs matching CIDR prefix. /8 prefixes are excluded from the permutation pool (guaranteed collision-free); narrower prefixes use post-hoc detection. Can repeat |
--allow-pt-collisions |
Downgrade pass-through collision errors to warnings |
--ignore-subnets |
Treat sub-/8 IPv4 private ranges as public. Only 10.0.0.0/8 stays range-preserved; Cat B and IPv6 are unaffected |
--ignore-reserved |
Remove ALL reserved range handling (Cat A and Cat B). Every IP — including loopback, multicast, private — gets fully anonymized. Affects both IPv4 and IPv6 |
--networks CIDRS |
Comma-separated CIDRs for subnet-aware host-bit locking (e.g., 10.0.0.0/8-24), or auto to collect from input. Preserves host bits within each subnet. Supports range notation and interface notation. Can combine with --network-file |
--network-file FILE |
File with one CIDR per line for subnet-aware host-bit locking. Blank lines and # comments ignored. Can combine with --networks |
-m, --mapping FILE |
Write JSON mapping of original-to-anonymized IPs to FILE. Includes network list when --networks/--network-file used |
-v, --verbose |
Print stats to stderr. -vv also prints all mappings |
-q, --quiet |
Suppress all warnings. Overrides -v/-vv |
Examples
# Don't anonymize your monitoring subnet
ipanon --salt s --pass-through 10.0.0.0/8 < input.log
# Only keep 10.0.0.0/8 private; treat 172.16.x, 192.168.x, etc. as public
ipanon --salt s --ignore-subnets < input.log
# Treat ALL IPs as public (ignore private/reserved ranges entirely)
ipanon --salt s --ignore-reserved < input.log
# Salt from environment variable
export ANON_SALT="my-secret-salt"
ipanon --salt-env ANON_SALT < input.log
Subnet-Aware Host-Bit Locking
When anonymizing router configurations, the permutation can map valid host addresses onto broadcast or network addresses — which routers reject. The --networks flag prevents this by preserving host bits within known subnets:
# Plain CIDR: host bits within /29 are preserved
ipanon --salt s --networks 192.168.1.0/29 < router.conf
# Range notation: within the /8 block, preserve host bits at /24 boundary
# Bits 8-23 are permuted, bits 24-31 are preserved
ipanon --salt s --networks 10.0.0.0/8-24 < router.conf
# Networks file for multi-router consistency
cat > subnets.txt <<EOF
# Large blocks with /24 host boundary
10.0.0.0/8-24
172.16.0.0/12-24
# Specific small subnets
192.168.1.0/29
# IPv6
2001:db8::/32-64
EOF
ipanon --salt s --network-file subnets.txt rtrA.conf > rtrA.anon
ipanon --salt s --network-file subnets.txt rtrB.conf > rtrB.anon
# Auto-collect subnets from input (one-off convenience)
ipanon --salt s --networks auto < router.conf
Interface notation is accepted — 10.1.2.65/29 is interpreted as network 10.1.2.64/29.
The network list is part of the anonymization configuration alongside the salt. For reproducible multi-device anonymization, save both the salt and the networks file.
Use Cases
Log Sanitization
Anonymize IP addresses in log files before sharing with vendors or publishing:
# Nginx access logs
ipanon --salt "$SECRET" < /var/log/nginx/access.log > sanitized-access.log
# Batch-process multiple log files with the same salt for cross-log correlation
for f in /var/log/app/*.log; do
ipanon --salt "$SECRET" "$f" "sanitized/$(basename "$f")"
done
# Pipe from journald
journalctl -u myservice --no-pager | ipanon --salt "$SECRET" > sanitized.log
# Apache combined log format — structure is preserved, only IPs change
# Before: 203.0.113.50 - - [10/Oct/2024:13:55:36 -0700] "GET /index.html HTTP/1.1" 200 2326
# After: 71.142.89.50 - - [10/Oct/2024:13:55:36 -0700] "GET /index.html HTTP/1.1" 200 2326
# Save the mapping so you can look up the original IP if needed
ipanon --salt "$SECRET" -m mapping.json < access.log > sanitized.log
cat mapping.json
# {"203.0.113.50": "71.142.89.50", "10.0.1.5": "10.218.94.5", ...}
Config File Sanitization
Scrub IP addresses from configuration files before committing or sharing:
# Sanitize firewall rules
ipanon --salt cfg < iptables-rules.txt > iptables-rules.sanitized.txt
# Sanitize network configs, keeping internal structure visible
# --pass-through keeps your well-known subnets readable
ipanon --salt cfg --pass-through 10.0.0.0/8 < network.conf > network.sanitized.conf
# Sanitize DNS zone files
ipanon --salt cfg < db.example.com > db.example.sanitized.com
# Sanitize Kubernetes manifests or Terraform state
ipanon --salt cfg < terraform.tfstate > terraform.sanitized.tfstate
# Sanitize Ansible inventories
ipanon --salt cfg < hosts.ini > hosts.sanitized.ini
Sharing Diagnostic Output
Clean up diagnostic output before pasting into bug reports or support tickets:
# Sanitize traceroute output
traceroute example.com | ipanon --salt diag
# Sanitize tcpdump captures (text output)
tcpdump -nn -r capture.pcap | ipanon --salt diag > sanitized-capture.txt
# Sanitize `ss` or `netstat` output
ss -tunap | ipanon --salt diag
# Sanitize `ip addr` output
ip addr show | ipanon --salt diag
CI/CD Pipelines
# Set salt once per pipeline run for consistent anonymization across steps
export ANON_SALT="$(openssl rand -hex 16)"
# Anonymize test artifacts before uploading
ipanon --salt-env ANON_SALT < test-output.log > sanitized-output.log
ipanon --salt-env ANON_SALT < network-diag.txt > sanitized-diag.txt
Python API
See API.md for complete API documentation.
from ipanon import Anonymizer, scan_and_replace
# Single IP anonymization
anon = Anonymizer(salt="mysecret")
anon.anonymize("8.8.8.8") # → "143.57.192.12" (deterministic)
anon.anonymize("10.1.2.3") # → "10.187.42.5" (stays in 10.0.0.0/8)
anon.anonymize("127.0.0.1") # → "127.0.0.1" (pass-through)
# Bulk text replacement
text = "Server 8.8.8.8 connected to 10.0.1.5 via 192.168.1.1"
scan_and_replace(text, anon)
# → "Server 143.57.192.12 connected to 10.187.42.5 via 192.168.78.201"
Requirements
- Python >= 3.9
- No external dependencies
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ipanon-0.3.3.tar.gz.
File metadata
- Download URL: ipanon-0.3.3.tar.gz
- Upload date:
- Size: 36.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
886219983fcaea38f8284cd0b5848d520b53d6f368f8d443be16ad1ee364b6a4
|
|
| MD5 |
38c2c635564f400b6c9632234bc0e40a
|
|
| BLAKE2b-256 |
02b64084c89eb599eb68a704dff4428589d5e3480f9226a27cf82a5457e6d7c0
|
Provenance
The following attestation bundles were made for ipanon-0.3.3.tar.gz:
Publisher:
publish.yml on dg-i/ipanon
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ipanon-0.3.3.tar.gz -
Subject digest:
886219983fcaea38f8284cd0b5848d520b53d6f368f8d443be16ad1ee364b6a4 - Sigstore transparency entry: 1188414153
- Sigstore integration time:
-
Permalink:
dg-i/ipanon@6dc830eb19207fe9ac71ccda5d67caf7e2a6f32c -
Branch / Tag:
refs/tags/v0.3.3 - Owner: https://github.com/dg-i
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6dc830eb19207fe9ac71ccda5d67caf7e2a6f32c -
Trigger Event:
release
-
Statement type:
File details
Details for the file ipanon-0.3.3-py3-none-any.whl.
File metadata
- Download URL: ipanon-0.3.3-py3-none-any.whl
- Upload date:
- Size: 21.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7c3fb5cbf324ec4e5777b93453277feb0161248807a8f1db453cb0d34989c45e
|
|
| MD5 |
452951ce3ba0e931a38cac3873dea477
|
|
| BLAKE2b-256 |
4ac9c883d2a8b577361688d4cb0aec3c5841c3907879d84e83e30a369b62b3f4
|
Provenance
The following attestation bundles were made for ipanon-0.3.3-py3-none-any.whl:
Publisher:
publish.yml on dg-i/ipanon
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ipanon-0.3.3-py3-none-any.whl -
Subject digest:
7c3fb5cbf324ec4e5777b93453277feb0161248807a8f1db453cb0d34989c45e - Sigstore transparency entry: 1188414174
- Sigstore integration time:
-
Permalink:
dg-i/ipanon@6dc830eb19207fe9ac71ccda5d67caf7e2a6f32c -
Branch / Tag:
refs/tags/v0.3.3 - Owner: https://github.com/dg-i
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6dc830eb19207fe9ac71ccda5d67caf7e2a6f32c -
Trigger Event:
release
-
Statement type: