pan-os-php Administrator & Developer Guide

Generated April 2026 from PaloAltoNetworks/pan-os-php at d87de87

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

Unofficial, AI-generated guide for a Palo Alto Networks open-source repository; not reviewed by Palo Alto Networks.

This guide covers the parts of PaloAltoNetworks/pan-os-php that the operator, architect, and contributor care about: the bootstrap order of the framework, the class hierarchy that underpins every UTIL, the XML-API protocol details, the security posture of the API keystore, the deployment patterns that scale, and the extension points for writing new actions, filters, and parsers.

If you are looking for "how do I tag every unused address object" recipes, those live in the User Guide. This document is its companion.


1. Architecture Overview

pan-os-php is a single-process PHP framework with no daemon, no database, and no inter-process state. Each invocation is a complete pipeline:

  1. The shell alias pan-os-php calls php -r "require_once 'utils/pan-os-php.php';" $@.
  2. utils/pan-os-php.php require_once's lib/pan_php_framework.php, which is the actual framework boot sequence — it fixes PHP runtime settings (memory_limit=14512M, set_time_limit(0), error_reporting(E_ALL), gc_enable(), date_default_timezone_set('UTC')), defines STDIN/STDOUT/STDERR for non-CLI contexts, registers a custom error handler that re-throws PHP notices and warnings as fatal derr() calls, registers a shutdown function that disables GC, and then require_once's the entire framework (phpseclib, git-php, the lib/misc-classes/, lib/helper-classes/, lib/container-classes/, lib/object-classes/, lib/device-and-system-classes/, lib/network-classes/, and lib/rule-classes/ trees) in dependency order.
  3. utils/pan-os-php.php then require_once's utils/lib/UTIL.php, which in turn pulls in every UTIL subclass (RULEUTIL, STATSUTIL, SECURITYPROFILEUTIL, DEVICEUTIL, NETWORKUTIL, MERGER, RULEMERGER, KEYMANGER, PREDEFINED, UPLOAD, XMLISSUE, DIFF, OVERRIDEFINDER, APPIDENABLER, CONFIGSIZE, BPAGENERATOR, XMLOPJSON, REGISTERIP, USERIDMGR, SOFTWAREREMOVE, TRAFFICLOG, SYSTEMLOG, GARPSEND, CUSTOMREPORT, SOFTWARE_DOWNLOAD, SOFTWARE_PREPARATION__, LICENSE__, CONFIG_DOWNLOAD_ALL__, SPIFFY__, CONFIG_COMMIT__, MAXMIND__, PLAYBOOK__, UTIL_GET_ACTION_FILTER, IRONSKILLET_UPDATE__, PROTOCOLL_NUMBERS__, HTMLmerger__, TSF__, XPATH, SSH_CONNECTOR__, GCP, CONVERTER, PARSER, APPIDTOOLBOX, RULE_COMPARE).
  4. PH::processCliArgs() parses the key=value pairs from $argv into PH::$args.
  5. The dispatcher inspects PH::$args['type']. If absent, it lists every supported type and exits. Otherwise it calls PH::callPANOSPHP($type, $argv, $argc, $PHP_FILE), which new's the appropriate UTIL subclass.
  6. The UTIL constructor calls utilStart() → utilInit() (CLI args, plugin load, arg_validation, listactions/listfilters, init_arguments) → utilActionFilter() (extracting_actions, createRQuery, load_config, location_filter) → location_filter_object() → time_to_process_objects() (the actual per-object iteration that runs each action) → GlobalFinishAction() → stats() → save_our_work(TRUE) (writes out= file, pushes outputformatset=, and on API mode commits via PanAPIConnector).

Confirmed components

                         +-----------------------------+
                         | Shell alias  pan-os-php     |
                         | (utils/alias.sh)            |
                         +--------------+--------------+
                                        |
                                        v
                         +-----------------------------+
                         | utils/pan-os-php.php        |
                         | (single CLI entry)          |
                         +--------------+--------------+
                                        |
                +-----------------------+-----------------------+
                |                                               |
                v                                               v
   +----------------------------+                +-----------------------------+
   | lib/pan_php_framework.php  |                | utils/lib/UTIL.php          |
   | (framework boot)           |                | (UTIL dispatcher)           |
   +-------------+--------------+                +-------------+---------------+
                 |                                             |
                 v                                             v
   +----------------------------+                +-----------------------------+
   | lib/misc-classes           |                | utils/lib/<TYPE>UTIL.php    |
   |   PH (CLI helper)          |                | RULEUTIL, STATSUTIL,        |
   |   DH (DOM helper)          |                | SECURITYPROFILEUTIL,        |
   |   PanAPIConnector          | <----.         | DEVICEUTIL, NETWORKUTIL,    |
   |   PanSaseAPIConnector      |       \        | MERGER, RULEMERGER, ...     |
   |   RQuery + RQueryContexts  |        \       +-------------+---------------+
   +-------------+--------------+         \                    |
                 |                         \                   v
                 v                          \   +-----------------------------+
   +----------------------------+            \  | utils/common/<X>CallContext |
   | lib/object-classes         |             - | + actions-<x>.php           |
   |   App, Address, Service,   |               | (action implementations)    |
   |   Tag, Schedule, Threat,   |               +-------------+---------------+
   |   *Profile, *Store         |                             |
   +-------------+--------------+                             |
                 |                                            |
                 v                                            v
   +----------------------------+              +-----------------------------+
   | lib/network-classes        |              | lib/rule-classes            |
   |   Zone, Interface*,        |              |   Rule (base),              |
   |   IKEGateway, IPsecTunnel, |              |   SecurityRule, NatRule,    |
   |   VirtualRouter, DHCP, ... |              |   DecryptionRule, PbfRule,  |
   +-------------+--------------+              |   QoSRule, DoSRule, ...     |
                 |                              +------------+----------------+
                 v                                           |
   +----------------------------+                            |
   | lib/device-and-system-     |                            |
   |   classes:  PANConf,       | <--------------------------+
   |   PanoramaConf, FawkesConf,|
   |   BuckbeakConf, DeviceGroup|
   |   Template, TemplateStack, |
   |   ManagedDevice, Snippet   |
   +-------------+--------------+
                 |
                 v
   +----------------------------+        +-----------------------------+
   | XML config (file or live)  | <----> | XML API endpoint            |
   | (DOM tree, in-memory)      |        | https://<fw>/api/?...       |
   +----------------------------+        +-----------------------------+

   +----------------------------+        +-----------------------------+
   | git-php/ (vendored)        |        | phpseclib (Math, Crypt, Net)|
   | for input file Git history |        | for SSH/RSA in SSH connector|
   +----------------------------+        +-----------------------------+

   +----------------------------+        +-----------------------------+
   | iron-skillet/panos_v*/     |        | migration/parser/<vendor>/  |
   | day-1 templates            |        | per-vendor PARSERs          |
   +----------------------------+        +-----------------------------+

Component inventory

Component Path Role
Framework boot lib/pan_php_framework.php Sets PHP runtime, registers handlers, require_once's every framework class
Single CLI entry utils/pan-os-php.php Accepts type= and dispatches
UTIL base + dispatcher table utils/lib/UTIL.php, lib/misc-classes/PH.php PH::$supportedUTILTypes, PH::callPANOSPHP()
CLI helper (PH) lib/misc-classes/PH.php PH::$args, PH::$argv, PH::frameworkVersion(), PH::print_stdout(), PH::processCliArgs()
DOM helper (DH) lib/misc-classes/DH.php XML element manipulation, XPath generation
Error reporter lib/ErrorReporter.php derr() (fatal + backtrace), mwarning() (non-fatal)
RQuery filter engine lib/misc-classes/RQuery.php + RQueryContext subclasses (Address, Application, Certificate, DHCP, Device, Interface, Routing, Rule, Schedule, SecurityProfile, SecurityProfileGroup, Service, StaticRoute, Tag, Threat, VirtualWire, Zone) Parses filter= expressions into a tree, evaluates per-object
API connector (XML API) lib/misc-classes/PanAPIConnector.php keygen flow, op/get/set/edit/delete/commit calls, ~/.panconfkeystore persistence
API connector (SASE) lib/misc-classes/PanSaseAPIConnector.php Strata Cloud Manager endpoints (partial)
Object classes lib/object-classes/ (49 files) App, AppCustom, AppFilter, AppGroup, AppStore; Address, AddressGroup, AddressStore, Region; Service, ServiceGroup, ServiceStore; Tag, TagStore; Schedule, ScheduleStore; Threat, ThreatVulnerability, ThreatSpyware, ThreatStore; *Profile and Profile base + *ProfileStore; ObjStore base
Network classes lib/network-classes/ (37 files) Zone, ZoneStore; EthernetInterface + EthernetIfStore; AggregateEthernetInterface + AggregateEthernetIfStore; LoopbackInterface + LoopbackIfStore; TunnelInterface + TunnelIfStore; VlanInterface + VlanIfStore; VirtualWire + VirtualWireStore; VirtualRouter + VirtualRouterStore; StaticRoute; IKEGateway + IKEGatewayStore; IkeCryptoProfil/IPSecCryptoProfil + their stores; IPsecTunnel + IPsecTunnelStore; GreTunnel + GreTunnelStore; DHCP + DHCPStore; Certificate + CertificateStore; TmpInterface + TmpInterfaceStore; SharedGatewayStore; InterfaceContainer, VirtualRouterContainer, NetworkPropertiesContainer
Rule classes lib/rule-classes/ (18 files) Rule base; RuleWithUserID, RuleWithGroupTag, RuleWithSchedule; SecurityRule, NatRule, DecryptionRule, AppOverrideRule, CaptivePortalRule, AuthenticationRule, PbfRule, QoSRule, DoSRule, TunnelInspectionRule, SDWanRule, NetworkPacketBrokerRule, DefaultSecurityRule; RuleStore
Device/system classes lib/device-and-system-classes/ (16 files) PANConf (firewall), PanoramaConf, FawkesConf, BuckbeakConf; DeviceGroup, Template, TemplateStack, ManagedDevice, ManagedDeviceStore, LogCollectorGroup; Container, DeviceCloud, DeviceOnPrem; Snippet, Sub; VirtualSystem
Container classes lib/container-classes/ (8 files) ObjRuleContainer base; AddressRuleContainer, AppRuleContainer, ServiceRuleContainer, TagRuleContainer, GroupTagRuleContainer, UrlCategoryRuleContainer, ZoneRuleContainer
Helper classes lib/helper-classes/ (4 files) IP4Map, cidr, ServiceDstPortMapping, ServiceSrcPortMapping
UTIL implementations utils/lib/ (47 files) One PHP class per UTIL type — see Section 8 for the full table
Action / CallContext utils/common/ (38 files) *CallContext per type + actions-*.php files defining action handlers
Vendor migration migration/parser/ (per-vendor folders) CONVERTER → PARSER → <vendor> parser hierarchy
AppID Toolbox appid-toolbox/ Self-contained sub-tool; companion type=appid-toolbox UTIL
IronSkillet bundle iron-skillet/panos_v8.1, panos_v9.1, panos_v10.0, panos_v10.1, panos_v10.2, panos_v11.0 Vendored PAN-OS day-1 hardening templates
git-php git-php/src/ Bundled fork of czproject/git-php for the git= argument
API listener utils/api/v1/tool.php Optional HTTP front-end exposing UTILs as REST routes
Web UI scaffolding utils/develop/ui/, utils/common/html/ Experimental browser UI
Bash autocompletion utils/bash_autocompletion/pan-os-php.sh (and per-tool pa_*.sh) Tab completion for the dispatcher and the legacy aliases

Technology stack

Layer Technology
Language PHP 7.4+
Required PHP extensions json, curl, dom, mbstring, bcmath, mysqli, yaml
Crypto phpseclib (vendored) — Math/BigInteger, Crypt/RSA, Net/SSH2
Git library czproject/git-php (vendored under git-php/) — used when git= is passed
Auth flow XML API (/api/?type=keygen then `?type=config
Output formats Plain text, PAN-OS set commands, JSON (BETA via shadow-json), XLSX (via python3-xlsxwriter), HTML (via HTMLmerger__)
Optional helpers Python 3 with pandas, xlsxwriter, netaddr, requests; jq
License ISC for the project; bundled git-php is BSD-3-Clause; PAN-OS schema files (lib/resources/) are © Palo Alto Networks (fair-use copy)

Data flow — single offline UTIL run

$argv  ─────►  PH::processCliArgs() ─────►  PH::$args
                                                  │
                                                  ▼
                                       PH::callPANOSPHP(type)
                                                  │
                                                  ▼
                                       new <Type>UTIL(...)
                                                  │
                                                  ▼
                                       UTIL::utilInit()
                                            ├── arg_validation()
                                            ├── listactions/listfilters
                                            └── init_arguments()
                                                  │
                                                  ▼
                                       UTIL::utilActionFilter()
                                            ├── extracting_actions()
                                            ├── createRQuery()
                                            ├── load_config()  ──►  XML on disk → DOMDocument → PANConf/PanoramaConf
                                            └── location_filter()
                                                  │
                                                  ▼
                                       location_filter_object()  ──►  scope to vsys/DG/template
                                                  │
                                                  ▼
                                       time_to_process_objects()
                                                       │
                                                       ▼
                                            for each object:
                                                  RQuery::test(object)
                                                        │ true
                                                        ▼
                                                  for each action:
                                                        apply()
                                                  │
                                                  ▼
                                       GlobalFinishAction()
                                                  │
                                                  ▼
                                       save_our_work(TRUE)
                                            ├── DOMDocument → out= file
                                            └── outputformatset=  → set commands file

Data flow — single API UTIL run

Identical to the offline flow, except:

  • load_config() calls PanAPIConnector::getRunningConfig() (and getCandidateConfig() for some operations) over HTTPS, returning XML.
  • save_our_work() invokes PanAPIConnector::edit(), PanAPIConnector::set(), or PanAPIConnector::delete() per modified node.
  • If outputformatset= is also passed, the set commands are written alongside the API push.
  • The first API call requires authentication; the connector reads ~/.panconfkeystore for cached serial@host keys, otherwise prompts for username/password and persists the resulting key (unless shadow-apikeynosave).

Note

The framework intentionally re-throws every PHP E_NOTICE/E_WARNING as a fatal via myErrorHandler → derr(). This is unconventional but correct for the use case: a config-mutation tool must never silently continue on a typo or NULL-deref. Operators reading the codebase should not "fix" warnings in user code by silencing them; the framework will end the run if the warning is a real bug.


2. System Requirements

Runtime

Requirement Minimum Recommended
PHP CLI 7.4 8.1
PHP memory_limit 14 GB (set inside framework) leave default
OS Linux (Ubuntu 20.04+, CentOS 7), macOS, Windows via Docker Ubuntu 22.04
Bash 5.0 (for autocompletion) —
Disk repo ~150 MB; large XML configs and Excel exports add MBs–GBs 5 GB free
Network HTTPS to firewall(s) and Panorama for API mode; HTTPS to GitHub for update_pan-os-php.sh; optional outbound to MaxMind, Iron-Skillet repo —

PHP extensions

The composer.json require block lists the hard requirements:

{
  "require": {
    "php": ">=7.4.0",
    "ext-json": "*",
    "ext-curl": "*",
    "ext-dom": "*",
    "ext-mbstring": "*",
    "ext-bcmath": "*",
    "ext-mysqli": "*",
    "ext-yaml": "*"
  }
}

Note that ext-mysqli is required even for offline-only use because the experimental utils/develop/ui/ and migration/parser/lib/SHAREDNEW.php features assume it is present at autoload time; the framework will error if the extension is missing.

Optional dependencies

Optional Reason
python3 + pandas + xlsxwriter + netaddr + requests XLSX export via actions=exportToExcel:..., IP arithmetic helpers
jq Bash-side parsing of shadow-json output
Docker Run via the swaschkut/pan-os-php:latest image instead of installing PHP locally

Network / firewall

  • Outbound HTTPS to each managed device or Panorama (port 443).
  • The XML API must be enabled on the device (Device > Setup > Management > XML API or its Panorama equivalent).
  • Time-skew matters: API auth tokens are time-bound — keep the jump host's NTP in sync with the firewall.

3. Configuration Reference

pan-os-php itself reads no configuration files. Configuration is supplied per-invocation via CLI arguments and resolved at runtime by PH::processCliArgs() and the per-UTIL supportedArguments tables.

Configuration files (read by the framework)

File Format Location Purpose
~/.panconfkeystore proprietary text user home Cached API keys, written by PanAPIConnector after first keygen call. Disable via shadow-apikeynosave.
utils/lib/util_action_filter.json JSON repo Generated by type=util_get-action-filter; used by utils/api/v1/tool.php and tooling that needs the action/filter catalogue.
iron-skillet/panos_v*/templates/panorama/snippets/*.xml PAN-OS template XML repo Bundled IronSkillet day-1 hardening snippets, one folder per major PAN-OS version.
lib/resources/schema/*.xml PAN-OS schema XML repo Schema files copied from the official Palo Alto product (https://[MGMT-IP]/php/utils/schema.php). Used for validation.
composer.json JSON repo root Declares PHP and extension requirements.
git-php/composer.json JSON submodule root Declares the vendored git-php library's metadata.
docker-compose.yml, docker-compose2.yml YAML repo root Optional Docker-based development environment (PHP-Apache + MySQL + phpMyAdmin); used by the experimental utils/develop/ui/.

Environment variables

Variable Read by Purpose Default
HOME utils/develop/grp-static-to-dynamic.php Locate user home for the API keystore system value
USERPROFILE lib/misc-classes/PanAPIConnector.php Windows equivalent of HOME system value
HOMEDRIVE lib/misc-classes/PanAPIConnector.php Windows path component system value
HOMEPATH lib/misc-classes/PanAPIConnector.php Windows path component system value
TERM lib/misc-classes/PH.php Decide whether to emit ANSI bold escape codes system value

There are no pan-os-php-specific environment variables. The framework deliberately does not hide config in the environment — it must all be in the CLI invocation, so playbooks are self-describing.

Universal CLI flags (all UTILs)

These are wired into UTIL::supportedArguments() and apply to nearly every type. Type describes the value's shape; Default is the value used when the flag is absent.

Flag Type Default Description
in= filename or api://... — (required for most UTILs) Source: file or live API endpoint
out= filename — Output file (offline mode); ignored in API mode
location= shared, any, vsys name, DG name, <DG>:includechilddgs, <DG>:excludeMaindg shared (Panorama), vsys1 (firewall) Scope of the operation
template= template name any Limit to a specific Panorama template
filter= RQuery expression none Per-object filter (see Section 8 for the engine)
actions= action[:arg1[,arg2]][/action2[:...]] none Slash-separated list of actions to apply
listactions flag off Print every supported action and exit
listfilters flag off Print every supported filter operator and exit
stats flag off Print object/rule counts after run
actions=display special — Print matched objects without changing them
loadplugin= filename — Load a custom action/filter plugin
apitimeout= seconds 60 API timeout
auditcomment= string PAN-OS-PHP $actions $time Audit comment for API commits
outputformatset[=file] flag or filename off / no file Emit PAN-OS set commands
projectfolder= dir — Output directory root
loadpanoramapushedconfig flag off Pull Panorama-pushed objects when loading from a managed firewall
cycleconnectedfirewalls flag off Iterate Panorama-managed devices
git flag off Wrap input file in a Git repo + auto-commit
expedition flag off Internal marker that the run is from the Expedition tool
debugapi flag off Print every API call
debugloadtime flag off Print phase timings
help flag off Print usage and exit

shadow- flags (advanced behaviours)

Flag Default Description
shadow-disableoutputformatting off Skip DOMDocument output normalisation
shadow-enablexmlduplicatesdeletion off When duplicate object names exist, keep one and drop the rest
shadow-ignoreinvalidaddressobjects off Tolerate address objects with no value/type
shadow-apikeyhidden off POST API key (PAN-OS 9.0+) instead of putting it in URL
shadow-apikeynohidden off Force API key in URL (PAN-OS < 9.0)
shadow-apikeynosave off Do not persist API key to ~/.panconfkeystore
shadow-displaycurlrequest off Print raw curl command for every API call
shadow-reducexml off Strip whitespace from output XML
shadow-json off BETA: emit stdout as a single JSON document
shadow-nojson off Force human text even if shadow-json was set
shadow-displayxmlnode off Include matched XML elements in actions=display output
shadow-saseapiqa off Use SASE QA endpoints

Important

Treat ~/.panconfkeystore as a secret file. It contains long-lived PAN-OS API keys, each of which carries the full RBAC of the user that ran keygen. Keep its mode at 0600, never commit it, and rotate keys (type=key-manager actions=delete:host) when staff leave or when a host is decommissioned. If you cannot accept the cache, run every API invocation with shadow-apikeynosave and pipe credentials in interactively — but expect each run to require fresh auth.

Per-UTIL specifics

Each UTIL extends UTIL::supportedArguments() with its own flags. To enumerate them:

pan-os-php type=<X> help

A few of the high-impact ones:

Type Notable extra arguments
rule-merger panoramaPreRules, panoramaPostRules, mergeAdjacentOnly, dupAlgorithm
rule dryrun, plus all rule-specific filter operators
address-merger mergeByValue, mergeByName, dupAlgorithm
bpa-generator cycleconnectedfirewalls (mandatory for Panorama-driven runs)
traffic-log, system-log Log filter/time-window arguments
software-download PAN-OS version selector
userid-mgr record=user,IP[,VSYS]
register-ip-mgr actions=register/unregister:ip,tag
xml-issue actions=display, actions=fix
playbook playbook=path/to/playbook.json
vendor-migration vendor=ciscoasa|netscreen|sonicwall|sophos|ciscoswitch|ciscoisr|fortinet|srx|cp-r80|cp|cp-beta|huawei|stonesoft|sidewinder|pfsense

4. Deployment & Scaling

pan-os-php is a per-invocation CLI; "deployment" is shaping how and where it is invoked.

Deployment patterns

Pattern Use when
Single jump host (Ubuntu 22.04 with the install script) Small team, ad-hoc runs, full Bash/Tab completion, easy SSH access
Per-engineer laptop (macOS install script, or Docker) Lots of one-off exploration, no shared state needed
Docker per run (swaschkut/pan-os-php:latest) CI pipelines, ephemeral compute, hosts you do not own
Managed centrally (Ansible / Salt installs the repo into /tools/pan-os-php) Compliance-controlled environments where every change must be source-tracked
REST front (utils/api/v1/tool.php) behind nginx + PHP-FPM When other systems need to call UTIL operations as HTTP — exposes /utils/api/v1/tool.php/<type>

Scaling considerations

  • Memory. Loading a multi-thousand-rule, multi-hundred-DG Panorama XML routinely consumes 10–25 GB. The framework hard-codes memory_limit=14512M (14 GB) — for the largest customers you may need to override. There is no streaming parser; every config is held in DOMDocument at once.
  • CPU. Mostly DOM walking, regex, and string compares. Single-threaded. Multi-runs in parallel are fine if each has its own memory headroom.
  • API rate. PAN-OS XML API does not impose a rate limit on small calls but commit operations serialise at the device. Avoid calling config-commit from many concurrent runs against the same device. The connector has retry logic for transient HTTPS failures.
  • Network. API mode pulls the full running config on every invocation (or running + candidate). For very large Panoramas a single getRunningConfig can be 50–200 MB.

Update strategy

  • Pin via Git. update_pan-os-php.sh pulls origin main on its host. For change control, fork the repo and pin a release tag; only run update_pan-os-php.sh after CI validation against your config corpus.
  • Pin the Docker image by digest. Use swaschkut/pan-os-php@sha256:... rather than :latest in CI.
  • Watch the upstream CHANGELOG.txt. Action names occasionally change (replaceWithObject ↔ replace-With-Object between releases, for example).

CI / automation patterns

Read pan-os-php type=<X> listactions and listfilters for every type at CI start and store the JSON output as build metadata. This catches the "the action you depend on no longer exists" failure mode early.

docker run --rm -v $PWD:/share swaschkut/pan-os-php:latest \
  type=util_get-action-filter projectfolder=/share/build-metadata

For idempotent operations, run with actions=display first and assert on the count; only proceed to the destructive action if the count matches expectations.


5. Operations

Day-to-day operator runbooks

Rotate API key cache

pan-os-php type=key-manager actions=display
pan-os-php type=key-manager actions=delete:<hostOrSerial>

type=key-manager is your direct interface to ~/.panconfkeystore. The next API call to that host will re-prompt for credentials.

Validate a config XML before processing

pan-os-php type=xml-issue in=panorama.xml actions=display

Lists empty entries, missing required attributes, and duplicate names. actions=fix repairs many of them in place.

Snapshot a live device to disk

pan-os-php type=stats in=api://10.0.0.5 out=fw-$(date -u +%Y%m%dT%H%M%SZ).xml

stats is read-only; the side effect is out= writes the running config locally. This is the safest base for any subsequent destructive offline work.

Generate a complete change preview

pan-os-php type=rule \
  in=api://10.0.0.5 \
  'filter=(action eq deny)' \
  actions=tag-Add:Reviewed-2026Q2 \
  outputformatset=preview-2026Q2.set \
  shadow-json

Combines a set-format file (for human review) with JSON output (for tool review).

Bulk download every running and candidate config from a Panorama

pan-os-php type=config-download-all in=api://panorama.local projectfolder=panorama-snapshot/

Useful before a major upgrade or as a pre-change snapshot.

Trigger a commit via API

pan-os-php type=config-commit in=api://panorama.local

type=config-commit issues a commit (/api/?type=commit&cmd=<commit/>) and waits for completion. Combine with cycleconnectedfirewalls to push to managed devices.

Note

config-commit honours apitimeout=. Panoramas with hundreds of devices can take 20+ minutes to fully push; set apitimeout=1800 for those runs. The connector polls the job status until it completes or the timeout expires.

Tip

Wrap the whole change in git= to get an automatic before/after commit on the input file. The dispatcher initialises a Git repo around the input filename, commits the original, runs the actions, and commits the result with the action and timestamp in the message.

Logging

The framework writes to STDOUT (human report) and STDERR (errors and warnings only). There is no logfile written by default. To capture a full run:

pan-os-php type=rule in=fw.xml ... 2>&1 | tee run.log

When shadow-json is set, stdout is a JSON document. The log field contains the human-text equivalent for archival.

The utils/lib/logWriter.php class is used internally by some long-running UTILs (appid-toolbox, config-download-all) to write structured per-step logs into the project folder.

Health checks

There is no daemon to check, so health is "does a known-good run still work":

pan-os-php version
 - PAN-OS-PHP version: 2.1.20 [UNIX]
 - /tools/pan-os-php/utils
 - PHP version: 8.1.2

A non-zero exit, an empty version line, or a *** ERROR *** indicates a broken install. For an end-to-end smoke test, use a tiny saved XML and a no-op:

pan-os-php type=stats in=tests/input/firewall-test.xml

Backup & restore

pan-os-php itself stores no state apart from ~/.panconfkeystore. To back up:

  • Source tree: standard Git pull (or repo mirror).
  • Keystore: cp ~/.panconfkeystore ~/.panconfkeystore.bak (treat as a secret).
  • Generated artefacts (set files, snapshots, Excel exports): whatever lives under projectfolder= is yours to back up.

Note

outputformatset=, out=, and projectfolder= outputs are deterministic snapshots of a moment in time. There is no built-in "rollback to previous run" — keep prior snapshots if you need that, or use git= to keep history alongside the input file.


6. Performance

Memory

memory_limit is set to 14 GB by lib/pan_php_framework.php. For very large Panoramas this is a soft floor; if you see Allowed memory size of N bytes exhausted, increase via php -d memory_limit=32G ... at the shell, or edit the framework. There is no streaming parser; the full DOM is in memory. memory_and_gc('end') is called by some UTILs and example scripts to display before/after consumption.

CPU profile

Most invocations are CPU-bound on:

  • DOM traversal (DH::dom_to_xml, DH::elementToPanXPath, the per-rule iteration in RuleStore)
  • Regex evaluation when the filter uses regex operators
  • String compares and array reflows during merger runs (MERGER, RULEMERGER)

The RULEMERGER algorithm is O(n^2) over the rule count for adjacency-aware merges. On 50k-rule Panoramas, expect tens of minutes. mergeAdjacentOnly reduces this to nearly linear at the cost of merging only adjacent shadow rules.

I/O

  • Offline mode: input parsed once via DOMDocument::load, modifications stay in memory, single write at end.
  • API mode: one HTTP request per push (PanAPIConnector::edit, PanAPIConnector::set, etc.) — for a UTIL that mutates 1000 objects, expect 1000+ requests. The connector reuses the underlying TLS connection where possible.
  • outputformatset= writes line-by-line as actions execute, so the file is complete even if the process is killed mid-run.

Tuning knobs

Flag Effect
apitimeout=N Per-call API timeout, default 60 — raise for slow Panoramas
shadow-reducexml Strip whitespace from output XML (smaller files, slightly faster I/O)
shadow-disableoutputformatting Skip DOM normalisation on output, save CPU
Run a bpa-generator outside business hours The combined cycleconnectedfirewalls walk hammers the API
Increase PHP memory_limit for huge Panoramas php -d memory_limit=64G
Use Docker to keep PHP/extension versions stable Avoids "works on Ubuntu 22 but not 20" drift

Benchmarks (qualitative)

  • type=address actions=display filter=(object is.unused) on a 30k-object Panorama: ~3–8 minutes wall-clock, ~12 GB memory.
  • type=rule-merger actions=mergeAdjacent on a 5k-rule Panorama with panoramaPreRules: ~5–15 minutes wall-clock, ~6 GB memory.
  • type=stats in=api://... against a Panorama with 30 managed firewalls: 2–5 minutes for the running config download alone.

7. Security

Threat model

pan-os-php is a privileged operations tool: it has read/write access to firewall and Panorama configurations, often via long-lived API keys cached on the operator's host. Treat any host that runs it as a tier-zero administrative system.

Secrets handling

Secret Where it lives Lifetime How to revoke
PAN-OS API key ~/.panconfkeystore (user home) Until firewall admin revokes it or pan-os-php type=key-manager actions=delete:host is run Delete via key-manager or rotate at the device
Username/password (during interactive auth) Process memory, never written Single invocation Process exit
LDAP credentials (if user-check-ldap is used) Read from the loaded config; not persisted by pan-os-php — Rotate at the LDAP server / firewall

PanAPIConnector writes the keystore at file mode 0600 if the umask is reasonable; verify after first run:

ls -l ~/.panconfkeystore
-rw------- 1 alice alice 384 Apr 28 12:00 /home/alice/.panconfkeystore

Caution

A leaked ~/.panconfkeystore is equivalent to a leaked credential set for every device it covers. Never copy it to a shared host, never include it in tarballs or backups that leave the operator's account, and rotate any key whose host has been suspected of compromise. Use shadow-apikeynosave on shared workstations to avoid persisting the key at all.

Exported configuration files are sensitive

pan-os-php works on exported configuration XML (in=config.xml). An export, whether copied off a firewall, saved with show config or taken as a Panorama named snapshot, is a full copy of the device configuration: it can include administrator password hashes, certificates and keys, and the credentials of downstream services (LDAP, RADIUS, SNMP, syslog). Handle it as credential material.

Action Cadence Notes
Keep exports encrypted at rest and limited to the operator role Continuous Retain them only as long as change management requires
Rotate the API keys in ~/.panconfkeystore On a defined cadence, and after an export has been handled outside the operator's host pan-os-php type=key-manager actions=delete:host, then a fresh run that re-prompts for credentials
Treat a Panorama configuration backup like the firewall configs it manages Continuous It aggregates the configuration of every managed device
Remove sensitive values before sharing an export (vendor, support case, migration) Per share Follow your data-handling policy

For how PAN-OS protects stored secrets (including the master key), use the official PAN-OS administrator documentation as the reference, not this guide.

Network security

  • TLS verification. PanAPIConnector defaults to verifying the device's TLS certificate. The bundled install and update scripts set their own Git options, which are separate from API TLS; review them before running them on your hosts. Do not disable TLS verification for API mode without a deliberate decision.
  • API mode endpoints. Always direct API connections at the management plane IP. The framework supports api://[email removed] to tunnel through Panorama, which is the preferred path for firewalls without direct management-plane reachability.
  • Inbound REST mode. If you deploy utils/api/v1/tool.php behind nginx, put it on a dedicated VLAN with mTLS or an authenticating reverse proxy in front. The endpoint executes UTILs server-side with whatever credentials the PHP process has — there is no built-in auth on the tool endpoint.

Input validation

  • The framework refuses to load on PHP < 5.5 (lib/pan_php_framework.php:22) and historically also imposed an upper bound (<=8.1) — the upper-bound check is currently commented out in pan_php_framework.php.
  • myErrorHandler (lib/pan_php_framework.php:275) re-raises any E_USER_NOTICE, E_USER_WARNING, E_NOTICE, or E_WARNING as a fatal derr(); this prevents subtle PHP coercion bugs from corrupting a config silently.
  • xml-issue runs a structural sanity pass; treat XMLs from untrusted sources with xml-issue first.

XSS / SQL / deserialization

  • No HTML output is generated by the CLI itself. The optional utils/develop/ui/ web UI is explicitly experimental and ships with sample test accounts (see utils/develop/ui/test/README.md). Do not deploy that UI without replacing those values and disabling sample DBs.
  • mysqli is loaded at framework boot to support utils/develop/ui/ and migration/parser/lib/SHAREDNEW.php. The CLI flow does not connect to any database.
  • No unserialize() of attacker-controlled data; XML is parsed via DOMDocument only.

Audit trail

  • auditcomment= sets the audit comment Panorama records for every API change. Default is PAN-OS-PHP $actions $time. Set this to a JIRA / change ticket reference for compliance environments.
  • git= produces commits in the wrapping Git repo with the action + timestamp in the message.
  • Combine with outputformatset= files retained alongside Git history for a tamper-evident change log.

Warning

Several known issues affect destructive operations. Always preview with actions=display and outputformatset= before running mergers (#793, #798), bulk renames (#764), or zone manipulations (#786). When in doubt, snapshot first (type=stats out=snap.xml) and iterate offline.

Hardening checklist

  • [ ] ~/.panconfkeystore mode 0600, owned by the operator user
  • [ ] shadow-apikeynosave on shared / multi-user hosts
  • [ ] apitimeout= set sensibly to detect dead devices instead of waiting forever
  • [ ] auditcomment= integrated into the operator's playbook (mapped to ticket IDs)
  • [ ] Source tree pinned to a known commit / tag rather than tracking main
  • [ ] No public network exposure of utils/api/v1/tool.php without authenticating reverse proxy
  • [ ] utils/develop/ui/ disabled in production installs
  • [ ] Operator hosts on the same NTP source as the firewalls (API tokens are time-bound)
  • [ ] Config exports stored under restricted ACLs (treat as secret material, not as backups)
  • [ ] Device API keys rotated on a defined post-export cadence (pan-os-php type=key-manager actions=delete:host + fresh auth)
  • [ ] Downstream credentials (LDAP / RADIUS / SNMP / syslog) rotated when reachable from any config export

8. Developer Internals

Repository structure

pan-os-php/
├── composer.json                # PHP + extension requirements
├── docker/                      # Dockerfile-main_amd, Dockerfile-API_amd, etc.
├── docker-compose.yml           # Production image compose
├── docker-compose2.yml          # PHP-Apache + MySQL + phpMyAdmin (dev)
├── create_all_docker_container.sh
├── create_docker_manifest.sh
├── install_script_centos7.sh
├── install_script_centos7_old.sh
├── install_script_ubuntu20.sh
├── install_script_ubuntu22.sh
├── mac_set_path_variable.sh
├── remove_path_variable.sh
├── set_alias_usage.sh
├── update_pan-os-php.sh
├── lib/
│   ├── pan_php_framework.php    # Framework boot
│   ├── ErrorReporter.php        # derr() static
│   ├── autoloader.php           # myAutoloader (exists but unused)
│   ├── classes/taskManagement/  # Task reporters (FileTaskReporter, ExpeditionTaskReporter)
│   ├── container-classes/       # ObjRuleContainer + 7 subclasses
│   ├── object-classes/          # 49 object classes (Address, Service, App, *Profile, *Store)
│   ├── network-classes/         # 37 network classes (Zone, *Interface, *IfStore, IKEGateway, ...)
│   ├── rule-classes/            # 18 rule classes (Rule + Security/NAT/Decryption/...)
│   ├── device-and-system-classes/  # 16 (PANConf, PanoramaConf, FawkesConf, BuckbeakConf, DeviceGroup, Template, Snippet, ...)
│   ├── helper-classes/          # IP4Map, cidr, ServiceDstPortMapping, ServiceSrcPortMapping
│   ├── misc-classes/            # PH, DH, RQuery + RQueryContexts, PanAPIConnector, PanSaseAPIConnector, CsvParser
│   └── resources/               # PAN-OS schema XML, IP protocol map, GeoIP data, License notices
├── utils/
│   ├── pan-os-php.php           # Single CLI entry
│   ├── alias.sh                 # Shell aliases
│   ├── lib/                     # 47 UTIL implementations
│   ├── common/                  # 38 CallContext + actions-*.php files
│   ├── api/v1/                  # Optional REST front-end (tool.php)
│   ├── bash_autocompletion/     # pan-os-php.sh + per-tool autocomplete
│   ├── develop/                 # In-progress UTILs and the experimental Web UI
│   ├── doc/                     # Markdown documentation drafts
│   └── common/html/             # JS/HTML for the experimental UI
├── examples/                    # 13 standalone PHP examples + sample-configs/
├── git-php/                     # Vendored fork of czproject/git-php
├── iron-skillet/                # Bundled Palo Alto IronSkillet templates per PAN-OS version
├── migration/                   # Vendor migration framework + per-vendor parsers
├── appid-toolbox/               # Self-contained App-ID activation sub-tool
├── projects/                    # Empty by default; output of `projectfolder=` runs lands here
├── log/                         # Runtime log root
├── tests/                       # Tests + sample configs
├── doc/                         # Auto-generated phpdoc HTML
├── README.md / READMEcustomScripting.md / READMEdocker.md / READMEutil.md
├── CHANGELOG.txt
└── SUPPORT.md

Architecture deep dive

The PH and DH static helpers

PH (lib/misc-classes/PH.php) is the global CLI helper — it owns PH::$args, PH::$argv, PH::$basedir, PH::$useExceptions, PH::$shadow_json, and the JSON output buffers PH::$JSON_OUT / PH::$JSON_OUTlog / PH::$JSON_TMP. Static methods include PH::frameworkVersion() (composes library_version_major.sub.bugfix, currently 2.1.20), PH::frameworkInstalledOS() (returns WIN or UNIX), PH::print_stdout() (the unified print wrapper that respects shadow-json), PH::processCliArgs() (parses argv into args), PH::$supportedUTILTypes (the master list), and PH::callPANOSPHP($type, ...) (the type → class dispatch table).

DH (lib/misc-classes/DH.php) is the DOM helper — DH::elementToPanXPath() produces the canonical PAN-OS XPath for a given element, DH::dom_to_xml() serialises with depth control, DH::firstChildElement() / DH::nextChildElement() walk past whitespace text nodes.

Together these are imported by every class file via require_once's in lib/pan_php_framework.php; you do not need to autoload them yourself.

The RQuery filter engine

lib/misc-classes/RQuery.php is the parser/evaluator for filter= expressions. It tokenises the expression, builds an AST of RQueryNodes, and walks the tree per object during time_to_process_objects(). Each leaf node references a RQueryContext subclass (AddressRQueryContext, RuleRQueryContext, ServiceRQueryContext, ZoneRQueryContext, InterfaceRQueryContext, etc.) that knows the per-type field/operator catalogue.

To add a new filter operator for, say, address:

  1. Open lib/misc-classes/AddressRQueryContext.php.
  2. Add an entry to RQuery::$defaultFilters['address'] (kept in RQuery.php initialisation): ['name' => 'is.in.region', 'operators' => ['Asia' => [...], 'Europe' => [...], ...]].
  3. Implement the evaluator function the operator points to.

listfilters will pick the new operator up automatically because it iterates RQuery::$defaultFilters[<type>].

The CallContext + Action pattern

Every UTIL type that mutates objects has an associated *CallContext class (AddressCallContext, RuleCallContext, ServiceCallContext, ZoneCallContext, TagCallContext, ScheduleCallContext, SecurityProfileCallContext, SecurityProfileGroupCallContext, ApplicationCallContext, ThreatCallContext, DeviceCallContext, VsysCallContext, VirtualWireCallContext, RoutingCallContext, InterfaceCallContext, DHCPCallContext, CertificateCallContext, StaticRouteCallContext). The CallContext holds the per-action invocation state (the matched object, the supplied arguments, the parent UTIL).

Each context has a $supportedActions static array. Entries look like:

array(
    'name' => 'tag-Add',
    'MainFunction' => function (RuleCallContext $context) { ... },
    'args' => array('tagName' => array('type' => 'string', 'default' => '*nodefault*')),
)

utils/common/actions-rule.php, actions-address.php, actions-service.php, actions-zone.php, actions-routing.php, actions-securityprofile.php, actions-securityprofilegroup.php, actions-tag.php, actions-threat.php, actions-schedule.php, actions-device.php, etc., are the files that add entries to those arrays.

To add a new action:

  1. Open the relevant actions-*.php file under utils/common/.
  2. Append a new entry to XCallContext::$supportedActions with name, MainFunction, GlobalInitFunction (optional), GlobalFinishFunction (optional), and args (each with type, default, and optional choices).
  3. The MainFunction receives a *CallContext $context and reads $context->object (the matched object) and $context->arguments (resolved CLI args).
  4. Inside, mutate $context->object via the public methods on the object class. Avoid touching the underlying DOM element directly except via DH:: helpers.
  5. pan-os-php type=<X> listactions picks the new entry up automatically.

Plugin loading via loadplugin=

UTIL::loadplugin() accepts a path and require_once's the file early in utilInit. The plugin is plain PHP; it can append entries to any *CallContext::$supportedActions array or to RQuery::$defaultFilters. See examples/example_plugin_rule_display_source_destination.php for the canonical example.

Object class invariants

  • Every object class extends ReferenceableObject (trait) so that uses are tracked; bidirectional reference graph is essential for addObjectWhereUsed, removeWhereUsed, replaceWithObject, etc.
  • Every object lives inside a *Store (AddressStore, ServiceStore, TagStore, RuleStore, ZoneStore, IKEGatewayStore, ...) which extends ObjStore.
  • findOrCreate is the canonical write path: never new Address() directly inside an action — go through the store.
  • name(), setName(), xmlroot, owner are the universal members.
  • display() prints the object; display_referencedFromText() shows where it is used.

Rule class hierarchy

Rule (base)
├── RuleWithUserID (mixin) → SecurityRule, DecryptionRule, AuthenticationRule, DoSRule, PbfRule, QoSRule, SDWanRule, NetworkPacketBrokerRule, TunnelInspectionRule
├── NatRule
├── AppOverrideRule
├── CaptivePortalRule
└── DefaultSecurityRule

Every rule type has its own subclass under lib/rule-classes/, with the trait-based RulewithLogging, RuleWithSchedule, and NegatableRule mixed in where appropriate. RuleStore is the single owner; it knows whether it is pre-rulebase, post-rulebase, or local.

Device/Panorama configuration classes

  • PANConf — a single firewall.
  • PanoramaConf — a Panorama, owns DeviceGroups and Templates.
  • FawkesConf — Fawkes (early SCM) backend.
  • BuckbeakConf — Buckbeak (another SCM iteration) backend.
  • DeviceCloud and DeviceOnPrem — used for Cloud-Managed (Strata Cloud Manager) topologies.
  • Container, Snippet, Sub — units for the cloud-managed config model.

When loading XML, the framework auto-detects which root element is present and instantiates the matching *Conf. Passing loadpanoramapushedconfig makes the firewall load the inherited template + DG-pushed objects.

Vendor migration framework

UTIL → CONVERTER → PARSER → <vendor> (ASA, NetScreen, SonicWall, ...)

Each <vendor>/<vendor>.php file extends PARSER and implements vendor-specific tokenisation. The CONVERTER coordinates the migration: load source, parse, build a PANConf/PanoramaConf, write XML. migration/parser/lib/SHAREDNEW.php is the reusable cross-vendor logic. The set of supported vendor codes is declared in utils/bash_autocompletion/pa_docker-migration-parser.sh and matches the parsers under migration/parser/.

appid-toolbox

A self-contained Application-ID activation pipeline in appid-toolbox/:

  • Entry scripts (report-generator.php, rule-activation.php, rule-cleaner.php, rule-cloner.php, rule-marker.php).
  • Library (appid-toolbox/lib/common.php, timezone.php, plus the lib/old/ and lib/trait/ sources for the older + newer implementations).
  • Tests under appid-toolbox/tests/ with sample XML for input, comparison, and preparation stages.
  • Companion UTIL: pan-os-php type=appid-toolbox drives the same logic from the unified entry point (the dispatcher routes appid-toolbox to the AppIDToolbox UTIL class).

Module reference (HIGH-priority)

Folder Files Purpose
lib/object-classes/ 49 Every PAN-OS object type (Address, Service, App, Tag, Schedule, Threat, Profile family) and their stores
lib/network-classes/ 37 Zones, interfaces, virtual routers, IKE/IPsec, DHCP, certificates, virtual wires
lib/rule-classes/ 18 Rule base + per-rule-type classes; RuleStore
lib/device-and-system-classes/ 16 PANConf, PanoramaConf, FawkesConf, BuckbeakConf, DeviceGroup, Template, TemplateStack, ManagedDevice, LogCollectorGroup, Container, DeviceCloud, DeviceOnPrem, Snippet, Sub, VirtualSystem
lib/misc-classes/ 24 PH, DH, RQuery + per-type RQueryContexts, PanAPIConnector, PanSaseAPIConnector, CsvParser
lib/container-classes/ 8 ObjRuleContainer and per-field rule containers (Address, App, Service, Tag, GroupTag, UrlCategory, Zone)
lib/helper-classes/ 4 IP arithmetic + service port mappings
utils/lib/ 47 One UTIL class per type= (RULEUTIL, STATSUTIL, MERGER, RULEMERGER, KEYMANGER, BPAGENERATOR, ...)
utils/common/ 38 *CallContext, actions-*.php, CallContext.php, RuleCallContext.php etc.
utils/develop/ 21 Experimental / in-progress UTILs (grp-static-to-dynamic.php, ike.php, wildcard_calculator.php, csv-import.php, checkpoint-exclude.php, reset-config.php, pan_get_user_info.php, migration/*.php for bluecoat/f5/vmware-vrni)
utils/develop/migration/ 5 One-off migration helpers (Bluecoat, F5 BIG-IP, VMware vRNI, ...)
utils/develop/ui/ 8 Experimental browser UI (do not deploy to production)
utils/api/ 1 utils/api/v1/tool.php REST front-end
utils/bash_autocompletion/ 3 Bash completion for pan-os-php and the legacy pa_* aliases
migration/parser/ per-vendor folders One PARSER subclass per supported vendor
migration/parser/lib/ 3 CONVERTER.php, PARSER.php, SHAREDNEW.php
appid-toolbox/ 5 entry scripts + lib + tests App-ID activation toolset
examples/ 13 End-to-end demonstrations of using the library directly (without the UTIL dispatcher)
iron-skillet/panos_v8.1 … panos_v11.0 snippets Bundled IronSkillet templates
git-php/src/ 9 Bundled Git wrapper

Code patterns and conventions

  • Static $supportedX arrays. The convention across the framework is to declare a static array on a class — PH::$supportedUTILTypes, RuleCallContext::$supportedActions, App::$_supportedCharacteristics, RQuery::$defaultFilters — and use it both as a runtime dispatch table and as the source of truth for listactions/listfilters / help output.
  • derr for fatal, mwarning for non-fatal. derr always exits 1 with a backtrace; mwarning prints to STDERR (or the JSON warning array) and continues. Use derr whenever the program cannot meaningfully proceed.
  • PH::print_stdout() over echo. All textual output goes through PH::print_stdout(). This routes to STDOUT in human mode or into the JSON buffer when shadow-json is set.
  • require_once over use. The framework predates strict PSR-4 layout; classes are loaded centrally via lib/pan_php_framework.php. The companion lib/autoloader.php exists but is not active (the central require_once block runs first).
  • Trait-based mixins. ReferenceableObject, XmlConvertible, ObjectWithDescription, PathableName, NegatableRule, RulewithLogging, RuleWithSchedule, RuleWithUserID are traits applied where the role is shared.
  • The find / findOrCreate / getAll triplet is implemented across every store. Use them; do not reach into $store->o[] directly.

Testing strategy

  • Unit / integration. tests/test_actions.php reads utils/lib/util_action_filter.json, iterates every action, and exercises it against tests/input/panorama-8.0.xml (or whatever in= overrides). It is essentially a per-action smoke test; failures show which actions regressed.
  • appid-toolbox/tests/. Stage-based test fixtures (stage00.xml … stage20.xml) for the AppID activation pipeline.
  • git-php/tests/. Tests for the bundled Git wrapper (nette/tester based — git-php/composer.json declares nette/tester ^2.0 as a dev dep).
  • Sample configs. examples/sample-configs/ contains policy-best-practices.xml, panorama-example.xml, panorama-8.0.xml. Example scripts under examples/ consume these.

When adding an action, also add a smoke entry to util_action_filter.json (type=util_get-action-filter regenerates the file) and verify tests/test_actions.php covers it.

Potential improvements

  • Streaming XML parsing. DOMDocument-only loading caps the practical config size at host-RAM. A streaming XMLReader path for read-only UTILs (stats, xml-issue, display-only filters) would let a 16 GB jump host handle Panoramas it currently cannot.
  • Concurrent API push. PanAPIConnector is single-threaded; large mutate-many runs serialise the HTTP latency. A bounded worker pool (e.g., pthreads or curl multi) for set/edit calls would dramatically cut wall-clock for high-N operations.
  • Action removal of obsolete patterns. Some actions exist twice with -FastAPI suffixes (enabled-Set vs enabled-Set-FastAPI, securityProfile-Group-Set vs securityProfile-Group-Set-FastAPI); document or consolidate.
  • Better SASE coverage. PanSaseAPIConnector returns "do not yet support this UTIL type" for many UTILs. Tracking a per-UTIL support matrix (and surfacing it in help output) would set expectations.
  • Replace mysqli requirement with optional load. composer.json requires ext-mysqli for everyone, even though only utils/develop/ui/ and one migration helper actually use it. Move to runtime feature detection.
  • Audit utils/develop/ui/ defaults. The experimental UI ships sample test accounts. Gate it behind a build flag or replace them with placeholders that error on first use.
  • Issue triage. Several open bugs (#791, #798, #552, #557, #696, #588) point at filter and action edge cases that would benefit from regression tests added to tests/test_actions.php.

Contributing

Contributions are accepted via pull request to main. The repository is licensed under the ISC License (see composer.json and LICENSE); the bundled git-php/ is BSD-3-Clause; bundled PAN-OS schema XML under lib/resources/ is © Palo Alto Networks under fair-use copy. By contributing you agree your changes are released under ISC.

Practical guidance:

  • New action: add to the relevant *CallContext::$supportedActions (utils/common/actions-*.php), regenerate util_action_filter.json with type=util_get-action-filter, and add a smoke test to tests/test_actions.php.
  • New filter operator: extend RQuery::$defaultFilters[<type>] and the corresponding RQueryContext class.
  • New UTIL type: add a class under utils/lib/, add the type name to PH::$supportedUTILTypes, wire it into the PH::callPANOSPHP() switch.
  • New vendor parser: add migration/parser/<VENDOR>/<VENDOR>.php extending PARSER, register it in utils/bash_autocompletion/pa_docker-migration-parser.sh.
  • Run php -l <file> on every modified PHP file before pushing.
  • The update_pan-os-php.sh flow expects PRs to be mergeable via git pull origin main; rebase your branch.

See CONTRIBUTING.md (top-level) and SUPPORT.md for the official policy. Note in particular that PAN-OS-PHP is community-supported and is not covered by Palo Alto Networks TAC.


9. Integration Points

pan-os-php is designed to plug into existing operational pipelines. The integration points exposed by the framework:

CLI

The single CLI is pan-os-php (alias) → php utils/pan-os-php.php. Every UTIL is reachable through it. STDOUT is human text by default and JSON when shadow-json is set; STDERR carries warnings and fatal traces. Exit code 0 is success, 1 is derr() exit, 2 is unhandled PHP fatal.

Library mode

Drop into a custom PHP script:

require_once '/tools/pan-os-php/lib/pan_php_framework.php';

$pan = new PanoramaConf();
$pan->load_from_file('panorama.xml');

foreach( $pan->securityRules->rules() as $rule )
{
    PH::print_stdout("Rule '" . $rule->name() . "'");
}

$pan->save_to_file('panorama-modified.xml');

Every public method on PANConf, PanoramaConf, FawkesConf, the rule stores, the object stores, and the helper classes is fair game. See examples/ for thirteen worked examples of this style — including example-add-security-profile-to-all-rules-panorama.php, example-basics-panos.php, and example_plugin_rule_display_source_destination.php.

REST front-end

utils/api/v1/tool.php exposes every UTIL as GET /utils/api/v1/tool.php/<type>?.... Mount behind nginx + PHP-FPM. The endpoint validates the <type> against PH::$supportedUTILTypes and returns:

  • 400 on unknown endpoint with ["error" => ..., "endpoint" => [<list of supported types>], "example" => ...].
  • The UTIL's normal output (JSON if shadow-json is in the query, human text otherwise) on success.
curl 'http://localhost:8082/utils/api/v1/tool.php/address?shadow-json'

This endpoint has no built-in auth. Always front it with an authenticating proxy or a service mesh in any deployment beyond localhost.

Docker image

docker pull swaschkut/pan-os-php:latest
docker pull swaschkut/pan-os-php-cli:latest
docker pull swaschkut/pan-os-php-api:latest

Three image variants exist for amd64 and arm64 (per create_all_docker_container.sh):

  • swaschkut/pan-os-php — interactive CLI
  • swaschkut/pan-os-php-cli — non-interactive CLI for CI
  • swaschkut/pan-os-php-api — bundled REST front-end

The develop tag tracks the development branch. For production CI, pin by digest.

Plugin system

loadplugin=path/to/file.php require_once's the file during UTIL::utilInit(). The plugin can append to any *CallContext::$supportedActions array, register new RQuery operators, or simply define helper functions used by an existing action. See examples/example_plugin_rule_display_source_destination.php for the working example.

Output integrations

Output Format Consumer
out=after.xml PAN-OS XML Re-import into Panorama / firewall
outputformatset=changes.set PAN-OS CLI set commands Paste into firewall CLI; review tool
actions=exportToExcel:file.xlsx XLSX (via python3-xlsxwriter) Excel / Google Sheets review
shadow-json on stdout JSON jq / scripting / ingest into observability
git= Git history around in= file Standard Git tooling
actions=display Human text on stdout Ad-hoc inspection
type=html-merger Merged HTML report Browser review

Inputs accepted

  • PAN-OS firewall running config XML (<config>...</config>).
  • Panorama running config XML.
  • Cloud-managed (Fawkes/Buckbeak) config XML.
  • Live device via api://IP, api://serial@panorama, or sase-api://....
  • Vendor configs via type=vendor-migration vendor=<X> for: ciscoasa, ciscoisr, ciscoswitch, netscreen, sonicwall, sophos, fortinet, srx (Juniper SRX), cp-r80 (CheckPoint R80), cp (CheckPoint), cp-beta, huawei, stonesoft, sidewinder, pfsense.

External services

  • MaxMind GeoIP. type=maxmind-update refreshes lib/resources/geoip/ from MaxMind's downloads; requires the operator-supplied license key (set in MaxMind account, passed via the UTIL's command line).
  • IronSkillet upstream. type=ironskillet-update pulls from the public IronSkillet GitHub repository.
  • MaxMind / IANA for protocol-number refresh via type=protocoll-number-download.
  • Expedition integration is supported via the expedition flag and lib/classes/taskManagement/ExpeditionTaskReporter.php; pan-os-php was historically the engine inside Palo Alto's Expedition migration tool and retains the hooks.
  • GCP integration helper at type=gcp for Google Cloud-managed Palo Alto deployments.
  • SSH (type=ssh-connector) uses bundled phpseclib for SSH/RSA — the device must allow CLI SSH from the operator host.

Embedding pan-os-php in other tools

Common patterns:

  • Ansible. Drop a wrapper module that shells out to pan-os-php type=... and parses shadow-json output.
  • GitOps. Store firewall configs as XML in a Git repo, run pan-os-php in CI to validate (type=xml-issue), generate diffs (type=diff), and push (type=upload) on merge.
  • Change-management gates. Run actions=display inside CI on every PR to surface what each merge would actually change in production. Block merges with unexpected diffs.
  • Self-service portals. Expose utils/api/v1/tool.php behind an authenticating proxy and offer pre-canned UTIL invocations (display unused objects, list rules with broad source) as ticket-creating buttons.

Note

The framework is intentionally synchronous and CLI-shaped. There is no built-in webhook, no event stream, no scheduler. When something happens in PAN-OS, it is up to the surrounding system (cron, Ansible, your CI, Panorama's own scheduler) to trigger pan-os-php; pan-os-php itself only runs when invoked.

Logging and observability

There is no built-in metrics emission. To get observability:

  • Wrap each invocation in a shell that records start/end time, exit code, and stdout to your log aggregator.
  • Use shadow-json and parse the resulting structure for action counts, warnings, errors.
  • Combine outputformatset= with version-controlled storage so each change is auditable independently of the surrounding pipeline.

Compatibility matrix

External system Compatible Notes
PAN-OS 8.1 Yes IronSkillet 8.1 templates bundled
PAN-OS 9.1 Yes IronSkillet 9.1 templates bundled
PAN-OS 10.0, 10.1, 10.2 Yes IronSkillet templates per-version
PAN-OS 11.0 Yes IronSkillet 11.0 templates bundled
PAN-OS 11.1+ / 12.x Best-effort Update via update_pan-os-php.sh; new XML elements can break specific actions until the framework is updated
Panorama Yes First-class — most operations are designed for Panorama scale
Cloud-managed (Strata Cloud Manager, formerly Fawkes/Buckbeak) Partial Classes exist (FawkesConf, BuckbeakConf, DeviceCloud, DeviceOnPrem); sase-api:// connector is partial
Prisma Access Best-effort Treated as a Panorama with managed devices; not all features mapped
Expedition Yes Hooks via expedition flag and ExpeditionTaskReporter
Python automation Indirect Run via subprocess and parse shadow-json; or use the REST front-end
Ansible Indirect Wrap as a custom module