Metadata-Version: 2.4
Name: panhygiene
Version: 0.1.0
Summary: Offline hygiene checker for Palo Alto Panorama and PAN-OS configs: unused objects, duplicates, shadowed rules. Your config never leaves your machine.
Project-URL: Homepage, https://github.com/BelalMou/panhygiene
Project-URL: Issues, https://github.com/BelalMou/panhygiene/issues
Author: Belal Hatim Moussa
License: MIT
License-File: LICENSE
Keywords: firewall,network-security,palo-alto,pan-os,panorama,security-automation
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: System :: Networking :: Firewalls
Requires-Python: >=3.9
Requires-Dist: rich>=12.0
Provides-Extra: dev
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Provides-Extra: lxml
Requires-Dist: lxml>=4.6; extra == 'lxml'
Description-Content-Type: text/markdown

# 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.**

```text
$ 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](https://live.paloaltonetworks.com/t5/expedition-articles/important-update-end-of-life-announcement-for-palo-alto-networks/ta-p/589642),
the free tool many engineers relied on for config cleanup. The built-in
[Policy Optimizer](https://docs.paloaltonetworks.com/pan-os/10-2/pan-os-admin/app-id/security-policy-rule-optimization)
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](https://github.com/BelalMou/panhygiene/issues) 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

```bash
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:

```bash
panhygiene running-config.xml
```

Useful variants:

```bash
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

```yaml
# 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](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.
