Skip to main content

panhygiene

Offline hygiene checker for Palo Alto Panorama and PAN-OS configurations: unused objects, duplicates, shadowed rules, and risky policy — from a config export, with zero network access.

$ panhygiene running-config.xml

panhygiene — Panorama config, 3 device group(s) + shared

Summary
┏━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┓
┃ Location         ┃ Score ┃ Rules ┃ Objects ┃  Unused ┃   Dupes ┃ Hazards ┃ Cleanup ┃
┡━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━┩
│ shared           │    78 │     1 │      15 │ 8 (53%) │ 2 (13%) │       0 │      11 │
│ Branch-Firewalls │    50 │     2 │       3 │ 2 (67%) │ 1 (33%) │       3 │       4 │
│ Corp             │    92 │     4 │       2 │  0 (0%) │  0 (0%) │       4 │       2 │
│ DC-Firewalls     │    67 │     3 │       3 │ 1 (33%) │  0 (0%) │       2 │       1 │
└──────────────────┴───────┴───────┴─────────┴─────────┴─────────┴─────────┴─────────┘

Hazards (9)
  error    B2  Corp / temp-allow-all       Allow rule permits any source to any
                                           destination on any service and any application.
  error    B7  Branch-FW / branch-ghost    References 'ghost-object', which does not exist.
  warning  A7  Corp / srv-dns              Device-group object shadows the shared object
                                           of the same name.
  warning  B1  DC-FW / dc-db-in-legacy     Fully covered by earlier rule 'dc-allow-broad';
                                           it is redundant.
  ...

Cleanup (18)
  warning  A1  shared / srv-dns-old        Address object (10.0.0.54/32) is not referenced
                                           by any rule or group.
  ...

Findings: 3 error(s), 18 warning(s), 6 info(s).

Why this exists

Palo Alto Networks has announced end-of-life for Expedition, the free tool many engineers relied on for config cleanup. The built-in Policy Optimizer remains excellent for what it covers — unused rules and unused apps — but nothing free covers unused objects, duplicate objects, empty groups, or device-group objects shadowing shared ones. Those are exactly the things that accumulate over years of firewall operations and make every change review slower.

panhygiene fills that gap: a small, sharp, offline CLI that reads a config export and tells you what to clean up and what to worry about.

v0.1.0 status: tested against synthetic configurations and the public config fixtures from Palo Alto's own tooling repos — not yet against a large production Panorama config, because we don't keep any. If the parser stumbles on yours, please run panhygiene config.xml --diagnostics and open an issue with the output — it contains only counts and XML tag names, nothing identifying. Parser reports are the single most valuable contribution right now.

Your config never leaves your machine

A firewall configuration is one of the most sensitive artifacts your organisation has. panhygiene is built around that fact:

  • No network code. The tool reads one local XML file and writes a report. There are no API clients, no credentials, no telemetry, no crash reporting, no update checker. This is a permanent design guarantee, not a version note.
  • Verifiable. The codebase is a few small modules — grep it for socket, http, urllib, or requests and you'll find nothing. Run it in a network namespace or an air-gapped host; nothing changes. Dependencies are rich (terminal rendering) and optionally lxml (faster XML parsing).

Install

pip install panhygiene
# or, isolated:
pipx install panhygiene

Python 3.9+ — the floor is 3.9 deliberately, because that is the system Python on RHEL 9 and its derivatives, which is what actually runs on many network-ops jump hosts. No compiled dependencies required.

Quickstart

  1. Export your config from Panorama:
    • GUI: Panorama > Setup > Operations > Save named Panorama configuration snapshot, then Export named Panorama configuration snapshot.
    • CLI over scp: scp export configuration from running-config.xml to user@host:/path/
    • Standalone firewall exports and <shared>-only configs work the same way.
  2. Run:
panhygiene running-config.xml

Useful variants:

panhygiene config.xml --format html -o report.html   # self-contained report to email
panhygiene config.xml --format json | jq .summary    # automation
panhygiene config.xml --names-only > flagged.csv     # just the names, as CSV
panhygiene config.xml --plain                        # unstyled text for tickets
panhygiene config.xml --only A --strict              # object checks, list soft-kept objects
panhygiene config.xml --skip B6,B5                   # not everything matters everywhere
panhygiene config.xml --fail-on error                # CI: fail only on errors

Exit codes: 0 no findings at/above --fail-on (default info), 1 findings, 2 tool error. Summary entries (group C) never affect the exit code.

Checks

Findings are grouped into Hazards (can change what traffic is allowed, or hide it) and Cleanup (dead weight, safe to tidy once confirmed).

ID Category Severity Detects Why it matters
A1 cleanup warning Unused address objects (transitively — an object referenced only by an unused group is unused) Dead objects slow reviews and hide mistakes
A2 cleanup warning Unused service objects Same
A3 cleanup warning Unused address/service groups, including chains of groups only referencing each other Same
A4 cleanup info Duplicate address objects (identical value, different names), as clusters Consolidation targets; edits in one copy miss the others
A5 cleanup info Duplicate service objects (identical protocol/port) Same
A6 cleanup warning Empty static groups Rules using them match nothing — usually broken intent
A7 hazard warning Device-group object with the same name as a shared object The DG object silently wins in that DG; a classic Panorama footgun
B1 hazard warning Shadowed rules — a later rule fully covered by an earlier one in the same rulebase (exact-superset only, see limitations) The later rule never matches; a shadowed deny is a hole
B2 hazard error Allow rules with any source + any destination + any service + any application The rule that ate the firewall
B3 cleanup warning Allow rules with no security profiles/group Permitted traffic bypasses threat inspection
B4 hazard warning/info Rules with logging fully disabled (warning) or no log forwarding profile (info) Invisible traffic, logs that never reach the SIEM
B5 cleanup info Disabled rules Long-disabled rules are usually dead
B6 cleanup info Rules with no description Intent evaporates when the author leaves
B7 hazard warning References to objects that don't exist in any visible scope Broken config; commits will fail. Warning (not error) in v0.1.0 — see limitations
C1 summary info Per-location counts: rules, objects, unused %, duplicate % Orientation
C2 summary info Hygiene score per location Track cleanup progress over time

Every finding carries: severity, device group, object/rule name, full XPath (--verbose / always present in JSON and HTML), a one-line explanation, and a suggested remediation. Remediations are always advisory text — panhygiene never emits executable config or delete commands.

The hygiene score

No magic weighting — the score is the average of three ratios, mapped to 0–100:

unused_ratio = unused objects / total objects
dup_ratio    = redundant duplicate copies / total objects
rule_ratio   = rules with error- or warning-level findings / total rules
score        = 100 × (1 − (unused_ratio + dup_ratio + rule_ratio) / 3)

Scores reflect the checks you ran: --skip A1 and unused addresses stop counting.

The score is designed for tracking one config over time — run it before and after cleanup sprints and watch it move. It is not meaningful for comparing different organisations or even different device groups with very different sizes and purposes.

Design principle: false positives are the cardinal sin

A cleanup tool you can't trust is worse than no tool. Wherever panhygiene cannot be certain, it stays silent and tells you why:

  • Unmodelled rulebases. References from policy types the tool doesn't fully parse (PBF, QoS, decryption, authentication, DoS, SD-WAN…) still mark objects as "in use", so they are never falsely reported unused. The report prints how many objects were kept this way; --strict lists them for manual review.
  • Disabled rules count as usage — deleting an object referenced by a disabled rule breaks the config.
  • Device-group hierarchy. Parent-DG objects referenced by child DGs resolve correctly when the export contains the hierarchy (<readonly>) section. If it doesn't, panhygiene warns prominently and resolves cross-DG references permissively rather than guessing.
  • Literal values. Raw IPs/CIDRs/ranges in rule fields, predefined services, region codes, and EDL names are never flagged as broken references.

Known limitations (read this)

  • B1 shadowing is deliberately conservative. It detects exact-superset shadowing only, on literal member names — it does not expand groups or compute CIDR containment, and a rule matched on users, URL categories, schedules, or negated fields is never treated as covering another. This means B1 misses real shadowing (e.g. an earlier rule using a group that contains the later rule's specific object). It also means that when B1 does flag a rule, it's worth your attention. Full set-theoretic overlap analysis is a possible future direction; it is not in v1 because its false positives destroy trust.
  • Unused-object analysis considers security policy, NAT policy, group membership, and (conservatively) all other rulebase types. It does not parse templates/network config, so an address object referenced only from e.g. an IKE gateway inside a template would be a false positive — rare for device-group objects, but verify with Where Used before deleting, always.
  • Dynamic address groups match by tag; membership is not resolved. A DAG is only reported unused if no rule references the DAG itself.
  • The predefined-object knowledge is hand-maintained, not extracted from a PAN-OS predefined.xml. The tool knows service-http/service-https, the any/application-default keywords, and treats 2-letter uppercase names as built-in region codes. PAN-OS versions ship predefined objects this list doesn't know, which could make B7 flag a valid reference. That is why B7 is a warning (not an error) in v0.1.0, and why --predefined FILE exists: put any additional predefined names in a file (one per line) and B7 will treat them as always existing. If you hit one, please open an issue naming it — the built-in list only improves through reports.
  • B7 does not validate application names.
  • Duplicate clusters spanning locations are attributed to the first member's location in the per-DG summary counts.
  • The tool analyses the exported XML as-is. Export the running config if you want findings about what is actually enforced.

Performance

The parser streams (via iterparse) and frees each element after converting it, so peak memory is bounded by the largest single rule or object entry — not the file size. A 100 MB+ Panorama export parses comfortably; installing the lxml extra (pip install "panhygiene[lxml]") speeds it up further. B1 is quadratic per rulebase in the worst case but bails early on the first field mismatch; rulebases in the tens of thousands of rules may take a couple of minutes.

CI usage

# fail the pipeline if a config export contains error-level findings
- name: Config hygiene
  run: |
    pip install panhygiene
    panhygiene exported/running-config.xml --fail-on error --format json -o hygiene.json

Contributing

See CONTRIBUTING.md. Two rules are non-negotiable: no real firewall configs in the repo (test fixtures are synthetic, RFC 5737/RFC 1918 only), and no network access in the tool, ever.

License

MIT. Not affiliated with or endorsed by Palo Alto Networks. "Palo Alto Networks", "Panorama", and "PAN-OS" are trademarks of Palo Alto Networks, Inc.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

panhygiene-0.1.0.tar.gz (42.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

panhygiene-0.1.0-py3-none-any.whl (37.6 kB view details)

Uploaded Python 3

File details

Details for the file panhygiene-0.1.0.tar.gz.

File metadata

  • Download URL: panhygiene-0.1.0.tar.gz
  • Upload date:
  • Size: 42.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for panhygiene-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b7049e9e3aa03937ba429eeec817c98c74b79f337d72dadb10efadc52acc6cbe
MD5 b52fd64e104964471b99b9dff52735b9
BLAKE2b-256 6d02b22b678d2da0db389c35ca8911a16ead56d45aeb05587f49452a86ff63fb

See more details on using hashes here.

Provenance

The following attestation bundles were made for panhygiene-0.1.0.tar.gz:

Publisher: publish.yml on BelalMou/panhygiene

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file panhygiene-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: panhygiene-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 37.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for panhygiene-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 616ac49b65f59b89c227200de755d96389f862de2e1b4d9aa031a3420d7f793b
MD5 0f539fc9495ae143a0e39b45e60a67b9
BLAKE2b-256 5e263c0eb3a549c36dca30ade305901f010312331b280faaee74c4174f423d35

See more details on using hashes here.

Provenance

The following attestation bundles were made for panhygiene-0.1.0-py3-none-any.whl:

Publisher: publish.yml on BelalMou/panhygiene

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page