Firewall Best-Practices Audit
Overview
Use this skill to audit a firewall or NGFW rulebase for security and operational hygiene, vendor-neutrally, over the parsing-* intermediate JSON schema. The audit reads normalized security_policies, nat_rules, address/service objects and groups, zones, vpn_tunnels, system, and admin_users, then emits prioritized findings across the security and operational check families (full catalog: references/check-catalog.md). Each finding carries a severity, a confidence, the affected references, why it matters, and remediation.
This skill is deliberately framework-agnostic. It answers "is this rulebase hardened by general best practice" rather than mapping findings to a compliance framework. It never cites control IDs and never claims an environment is compliant.
The audit reports findings, not verdicts. It never claims a config is "secure" — at best it reports "no in-scope findings" with caveats about what could not be checked (for example, hit-count or last-used data that the static schema does not carry). The output is decision-grade: a short posture line, a severity tally, the list of checks that were skipped for missing data, and a prioritized list of top fixes.
Scope and routing
Operate on the normalized parsing-* schema; do not hand-audit raw vendor text when a parser exists. Treat framework-control mapping as out of scope, route migrations to firewall-config-conversion, and route parity or drift comparisons to firewall-config-diff.
Runtime intake
Before starting the workflow, inspect the request, supplied artifacts, and
available approved read-only evidence. If unresolved facts could materially
change safety, scope, correctness, confidence, or the requested output, read
references/runtime-intake.md.
For each unresolved material fact whose catalog condition is true, invoke Claude AskUserQuestion or Codex request_user_input before continuing or issuing an open-ended request.
Ask at most three single-select catalog questions per round. After each response, ask another round whenever any unresolved material catalog condition remains true; continue only when none remain. Do not repeat answered questions or show the full catalog.
Without a native tool, present each selected catalog question with its 2-3 labeled choices and a free-text Other path in concise plain text; do not substitute a generic checklist.
Never request secrets or unredacted customer data. Treat intake answers as task
context, not approval for a live change; obtain separate explicit approval
before configuration, commit, upgrade, reboot, delete, or failover actions.
Policy population contract
Parser output can include a vendor-default policy marked _implicit: true.
Partition security_policies once before running any rule-scoped check:
explicit_rules — every rule whose _implicit value is not true;
enabled_explicit_rules — explicit_rules whose disabled value is not
true;
disabled_explicit_rules — explicit_rules whose disabled value is
true; and
implicit_rules — rules marked _implicit: true.
All active-risk, order, logging, exposure, shadow, redundancy, and overlap
checks operate on enabled_explicit_rules. Cleanup/state checks may inspect
explicit_rules or disabled_explicit_rules, but never implicit_rules.
Reference and object-usage checks must likewise exclude implicit_rules and
state whether disabled explicit rules count as references. Never emit a finding
against an implicit rule or compare one with an explicit rule.
SEC-EMPTY-POLICYSET fires when explicit_rules is empty, even if a parser
appended an implicit default.
Evaluation context and phase
Before SEC-SHADOW, SEC-REDUNDANT, SEC-OVERLAP, or OPS-CONSOLIDATE,
partition enabled_explicit_rules into a vendor evaluation population and sort
each population by numeric _rule_index. Never compare rules across these
populations:
- SRX: partition first by root,
_logical_system, or _tenant; within that
container, keep each concrete source-zone/destination-zone pair separate and
keep from-zone any to-zone any global policy in a later, separate phase.
- PAN-OS: partition by
_vsys and use the parser's fully merged Panorama/
local order. Zones are match fields inside that population, not rulebase
containers.
- FortiGate: partition by
_vdom; zones remain match fields within the
ordered VDOM rulebase.
- Cisco Firepower (FMC/FDM): partition by access control policy — a device is
assigned exactly one, so the policy is the evaluation population. Use the
parser's merged Prefilter-plus-ACP order via
_rule_index. A MONITOR rule is
non-terminal: it logs and continues. Shadowing and SEC-NO-DENY-ALL
conclusions are unreliable across one, so report them as qualified rather than
confirmed when the parser warned about a MONITOR rule.
- Cisco ASA: only use a concrete parser-derived inbound binding/source zone
as the available rulebase boundary. The shared schema has no ACL name or
direction field, so an absent or ambiguous binding is not safe to flatten.
The shared schema has no general policy-phase or origin field. If the applicable
container, binding, merged-order provenance, _rule_index, or object resolution
is absent, duplicated, or ambiguous, skip/downgrade the dependent comparison
instead of inventing an order, and put an evidence-gap warning in the
skipped-check summary.
SEC-NO-DENY-ALL evaluates each applicable context using _rule_index, not
array order. A qualifying explicit terminal rule uses one of deny or drop
(the normalized silent-drop values), match-all source, destination, and
application/service criteria, no restricting schedule/user/negation, and start
or end logging. reset-both is a reject action, not a deny alias. On SRX, a
global logged deny can be a fallback only when the relevant zone-pair phase has
no earlier catch-all rule that makes global policy unreachable. An implicit
default can explain effective enforcement, but never supplies explicit log
visibility.
SEC-ZONES-NAT-NO-POLICY requires evidence that an enabled allow rule can
carry at least part of the intended NAT flow in the same vendor container: its
source/destination zones and resolved source/destination addresses must overlap
the NAT rule. A deny-only rule, disabled permit, unrelated zone, or mere textual
reference does not supply coverage. If address objects cannot be resolved,
skip/downgrade the conclusion and record the evidence gap.
Input Handling
Route on what you were given:
- Parsed intermediate schema (the vendor-neutral JSON produced by any
parsing-* skill; the schema definition lives in the parsing-srx-configs skill) — audit directly. Read metadata.source_vendor to drive vendor-specific remediation snippets. metadata.source_vendor is the canonical schema field; for robustness also accept a legacy metadata.vendor key if a non-conformant parse provides one — read whichever is present.
- Raw config — identify the vendor from the syntax, run the matching
parsing-* skill (parsing-cisco-configs, parsing-fortinet-configs, parsing-palo-configs, parsing-srx-configs) to produce the intermediate schema, then audit the result. Never re-implement parsing in this skill. For a single live device pulled via NETCONF/MCP, parsing-srx-configs permits auditing from the extracted hierarchical sections without full schema emission — still route the extraction through that skill; never hand-parse raw text ad hoc.
- Unsupported vendor with no parser — say so plainly: there is no parser for this vendor, so a structured audit cannot be produced; offer to audit a manually-normalized schema or to reason about pasted excerpts without finding IDs.
As of v1.1 the audit also reads system.ssh, system.auth, system.control_plane_protection, zones[].screen, and security_services to cover device-plane hardening (SSH/management hardening, password/lockout policy, screen presence, and unreferenced security services); any of these fields that is absent simply skips its dependent check.
As of v1.2 the audit reads two further fields, both added after a live-device
run produced wrong verdicts without them:
security_policies[].dynamic_applications — runtime application-identity
matches (SRX match dynamic-application, PAN-OS App-ID members). It is a
narrowing field: treat a rule as unrestricted only when applications,
services, dynamic_applications, url_categories, app_groups,
source_users, and schedule are all any/empty/absent. Include it in the
match tuple SEC-REDUNDANT compares on, and in the tail test behind
SEC-NO-DENY-ALL. Dropping it makes an AppID-scoped deny read as a terminal
deny-all and makes two rules differing only by AppID scope look identical.
address_objects[].type == "dynamic" — GeoIP, feed-backed, and tag-based
objects whose membership resolves at runtime. They are defined objects:
SEC-ORPHAN-REF must count them as resolved. Their members are unknowable
from config, so any check needing concrete addresses (overlap, shadow,
containment) degrades to heuristic or skips.
If the parser populated neither field, say so in the skipped-checks list rather
than reporting the resulting findings as definitive.
Graceful degradation: the schema is static, so some checks have no input (for example, anything depending on hit counts or last-used timestamps, and any check needing a field the source parser did not populate). When a field is missing, skip the dependent check rather than guessing, and record every skipped check in the audit summary so the gap is visible.
Severity & Confidence
| Severity |
One-line criterion |
| Critical |
Direct, exploitable exposure — any-any-allow across a trust boundary, dangerous service open to untrust, plaintext admin reachable externally, broken/keyless VPN. |
| High |
Strong weakness — overly permissive allow, shadowed rules hiding later policy, weak VPN/IKE crypto (DES/3DES/MD5/SHA1/DH<14), no logging on permit-to-untrust. |
| Medium |
Material hygiene gap — missing explicit deny-all, redundant/overlapping rules, oversized any in one field, missing logging on internal allows. |
| Low |
Minor hardening or correctness nit — overlapping objects, weak naming, narrow over-broad object. |
| Info |
Operational cleanup with no direct risk — unused/duplicate objects, oversized groups, missing descriptions, consolidation opportunities, disabled rules. |
This table is the general scale with context-adjusted examples; the per-check default severities in references/check-catalog.md are the authoritative defaults (a check's catalog severity wins over any example here, since real severity is adjusted by logging, scope, and trust-boundary context).
Confidence is one of two values:
- definitive — the schema fully supports the conclusion (e.g.
src_addresses and dst_addresses both contain any with action: allow).
- heuristic — the conclusion depends on something the schema does not fully guarantee. Downgrade to heuristic when rule order is incomplete or ambiguous (shadow/redundancy claims), when object references are unresolved (orphan/unused claims), or when NAT could change real exposure. Label these as heuristic so reviewers verify before acting.
Audit Workflow
- Establish input + vendor. Confirm you have the intermediate schema (parse first if raw). Read
metadata.source_vendor and metadata counts to size the audit.
- Resolve objects and zones. Expand
address_groups, service_groups, and application_groups to members; map security_policies src_zones/dst_zones to zones and their interfaces; note nat_rules that change effective exposure. Flag references that do not resolve (drives heuristic confidence later).
- Run the security checks. Permissiveness/any-any, shadow/redundancy/overlap, missing deny-all and logging, dangerous exposed services, plaintext management, weak VPN/IKE/IPsec crypto. Also run the v1.1 device-plane families: SSH/management hardening (
SEC-SSH-ROOT-LOGIN), security-services-unreferenced (SEC-SERVICES-UNREFERENCED), zone/NAT-without-policy (SEC-ZONES-NAT-NO-POLICY) plus the empty-policyset coverage warning (SEC-EMPTY-POLICYSET), host-inbound exposure (SEC-HOST-INBOUND-EXPOSURE), screen presence (SEC-NO-SCREEN), auth hardening (SEC-AUTH-HARDENING), control-plane/RE-filter protection (SEC-NO-CONTROL-PLANE-PROTECTION), IPv6 posture (SEC-IPV6-POSTURE), and remote-log completeness (OPS-LOG-COMPLETENESS). See references/check-catalog.md for the full check definitions, schema fields, and severity rationale.
- Run the operational checks. Unused/duplicate objects, oversized groups, naming/description gaps, rule consolidation, disabled-rule cleanup. Also from
references/check-catalog.md.
- Assign severity + confidence. Apply the rubric above; downgrade to heuristic where rule order or object resolution is incomplete or NAT may alter exposure.
- Attach remediation. Give vendor-neutral guidance plus a concrete snippet in the source vendor's syntax. See
references/remediation-patterns.md for per-vendor fix patterns keyed by finding category.
- Produce the output. Emit each finding with the Finding template, then the Audit Summary with the posture line, severity tally, skipped checks, and prioritized top fixes.
Reference Material (load on demand)
references/check-catalog.md — full catalog of security + operational checks (id, what it detects, schema fields, severity rationale).
references/remediation-patterns.md — per-vendor (Cisco/Palo/FortiGate/SRX) fix snippets keyed by finding category.
references/example-audit.md — a worked audit against a parsing-* fixture.
Output Templates
Finding
[<id>] <CRITICAL|HIGH|MEDIUM|LOW|INFO> (<definitive|heuristic>) — <title>
Category: <security|operational> / <subcategory>
Affected: <rule names/IDs, object names, zones>
Why it matters: <1–3 lines>
Remediation: <vendor-neutral guidance>
Fix (<source-vendor>):
<concrete snippet in the source vendor's syntax>
Audit Summary
Posture: <one line; never "secure" — say "no in-scope findings" with caveats>
Findings: Critical <n> High <n> Medium <n> Low <n> Info <n>
Checks skipped (no data): <e.g. hit-count-dependent checks>
Top fixes (prioritized):
1. <id> — <one line>
2. <id> — <one line>
Common Pitfalls
- Claiming a config is "secure" or "compliant" — only report "no in-scope findings" with caveats about what could not be checked.
- Mapping findings to compliance frameworks — stay framework-agnostic and cite no control IDs.
- Re-implementing parsing instead of delegating to the matching
parsing-* skill.
- Reporting heuristic findings (possible shadow) as definitive when rule order or object references are incomplete.
- Auditing only the internet edge — include internal, management, VPN, and inter-zone rules.
- Ignoring NAT when reasoning about real exposure — a translated address can change what a rule actually permits.
- Flagging disabled rules as active risk — note them as cleanup, not exposure.
- Treating an unreferenced object as a finding without confirming the schema captured all references.
- Putting real secrets or PSKs in example output — always redact.
- Silently dropping checks when a schema field is missing — list them as skipped in the summary.
Verification Checklist
1---2name: firewall-best-practices-audit3description: Audit normalized Cisco, Fortinet, Palo Alto, and Juniper firewall rulebases for security hygiene. Use when finding any-any, shadowed, redundant, or orphaned rules, missing deny or logging, exposed management, weak VPN crypto, hardening gaps, or unused objects. Parse raw configs first.4license: MIT5---67# Firewall Best-Practices Audit89## Overview1011Use this skill to audit a firewall or NGFW rulebase for security and operational hygiene, vendor-neutrally, over the `parsing-*` intermediate JSON schema. The audit reads normalized `security_policies`, `nat_rules`, address/service objects and groups, `zones`, `vpn_tunnels`, `system`, and `admin_users`, then emits prioritized findings across the security and operational check families (full catalog: `references/check-catalog.md`). Each finding carries a severity, a confidence, the affected references, why it matters, and remediation.1213This skill is deliberately framework-agnostic. It answers "is this rulebase hardened by general best practice" rather than mapping findings to a compliance framework. It never cites control IDs and never claims an environment is compliant.1415The audit reports findings, not verdicts. It never claims a config is "secure" — at best it reports "no in-scope findings" with caveats about what could not be checked (for example, hit-count or last-used data that the static schema does not carry). The output is decision-grade: a short posture line, a severity tally, the list of checks that were skipped for missing data, and a prioritized list of top fixes.1617## Scope and routing1819Operate on the normalized `parsing-*` schema; do not hand-audit raw vendor text when a parser exists. Treat framework-control mapping as out of scope, route migrations to `firewall-config-conversion`, and route parity or drift comparisons to `firewall-config-diff`.2021## Runtime intake2223Before starting the workflow, inspect the request, supplied artifacts, and24available approved read-only evidence. If unresolved facts could materially25change safety, scope, correctness, confidence, or the requested output, read26`references/runtime-intake.md`.2728For each unresolved material fact whose catalog condition is true, invoke Claude `AskUserQuestion` or Codex `request_user_input` before continuing or issuing an open-ended request.29Ask at most three single-select catalog questions per round. After each response, ask another round whenever any unresolved material catalog condition remains true; continue only when none remain. Do not repeat answered questions or show the full catalog.30Without a native tool, present each selected catalog question with its 2-3 labeled choices and a free-text `Other` path in concise plain text; do not substitute a generic checklist.3132Never request secrets or unredacted customer data. Treat intake answers as task33context, not approval for a live change; obtain separate explicit approval34before configuration, commit, upgrade, reboot, delete, or failover actions.3536## Policy population contract3738Parser output can include a vendor-default policy marked `_implicit: true`.39Partition `security_policies` once before running any rule-scoped check:4041- `explicit_rules` — every rule whose `_implicit` value is not `true`;42- `enabled_explicit_rules` — `explicit_rules` whose `disabled` value is not43 `true`;44- `disabled_explicit_rules` — `explicit_rules` whose `disabled` value is45 `true`; and46- `implicit_rules` — rules marked `_implicit: true`.4748All active-risk, order, logging, exposure, shadow, redundancy, and overlap49checks operate on `enabled_explicit_rules`. Cleanup/state checks may inspect50`explicit_rules` or `disabled_explicit_rules`, but never `implicit_rules`.51Reference and object-usage checks must likewise exclude `implicit_rules` and52state whether disabled explicit rules count as references. Never emit a finding53against an implicit rule or compare one with an explicit rule.5455`SEC-EMPTY-POLICYSET` fires when `explicit_rules` is empty, even if a parser56appended an implicit default.5758### Evaluation context and phase5960Before `SEC-SHADOW`, `SEC-REDUNDANT`, `SEC-OVERLAP`, or `OPS-CONSOLIDATE`,61partition `enabled_explicit_rules` into a vendor evaluation population and sort62each population by numeric `_rule_index`. Never compare rules across these63populations:6465- **SRX:** partition first by root, `_logical_system`, or `_tenant`; within that66 container, keep each concrete source-zone/destination-zone pair separate and67 keep `from-zone any to-zone any` global policy in a later, separate phase.68- **PAN-OS:** partition by `_vsys` and use the parser's fully merged Panorama/69 local order. Zones are match fields inside that population, not rulebase70 containers.71- **FortiGate:** partition by `_vdom`; zones remain match fields within the72 ordered VDOM rulebase.73- **Cisco Firepower (FMC/FDM):** partition by access control policy — a device is74 assigned exactly one, so the policy is the evaluation population. Use the75 parser's merged Prefilter-plus-ACP order via `_rule_index`. A `MONITOR` rule is76 **non-terminal**: it logs and continues. Shadowing and `SEC-NO-DENY-ALL`77 conclusions are unreliable across one, so report them as qualified rather than78 confirmed when the parser warned about a MONITOR rule.79- **Cisco ASA:** only use a concrete parser-derived inbound binding/source zone80 as the available rulebase boundary. The shared schema has no ACL name or81 direction field, so an absent or ambiguous binding is not safe to flatten.8283The shared schema has no general policy-phase or origin field. If the applicable84container, binding, merged-order provenance, `_rule_index`, or object resolution85is absent, duplicated, or ambiguous, skip/downgrade the dependent comparison86instead of inventing an order, and put an **evidence-gap warning** in the87skipped-check summary.8889`SEC-NO-DENY-ALL` evaluates each applicable context using `_rule_index`, not90array order. A qualifying explicit terminal rule uses one of `deny` or `drop`91(the normalized silent-drop values), match-all source, destination, and92application/service criteria, no restricting schedule/user/negation, and start93or end logging. `reset-both` is a reject action, not a deny alias. On SRX, a94global logged deny can be a fallback only when the relevant zone-pair phase has95no earlier catch-all rule that makes global policy unreachable. An implicit96default can explain effective enforcement, but never supplies explicit log97visibility.9899`SEC-ZONES-NAT-NO-POLICY` requires evidence that an enabled `allow` rule can100carry at least part of the intended NAT flow in the same vendor container: its101source/destination zones and resolved source/destination addresses must overlap102the NAT rule. A deny-only rule, disabled permit, unrelated zone, or mere textual103reference does not supply coverage. If address objects cannot be resolved,104skip/downgrade the conclusion and record the evidence gap.105106## Input Handling107108Route on what you were given:109110- **Parsed intermediate schema** (the vendor-neutral JSON produced by any `parsing-*` skill; the schema definition lives in the `parsing-srx-configs` skill) — audit directly. Read `metadata.source_vendor` to drive vendor-specific remediation snippets. `metadata.source_vendor` is the canonical schema field; for robustness also accept a legacy `metadata.vendor` key if a non-conformant parse provides one — read whichever is present.111- **Raw config** — identify the vendor from the syntax, run the matching `parsing-*` skill (`parsing-cisco-configs`, `parsing-fortinet-configs`, `parsing-palo-configs`, `parsing-srx-configs`) to produce the intermediate schema, then audit the result. Never re-implement parsing in this skill. For a single live device pulled via NETCONF/MCP, parsing-srx-configs permits auditing from the extracted hierarchical sections without full schema emission — still route the extraction through that skill; never hand-parse raw text ad hoc.112- **Unsupported vendor with no parser** — say so plainly: there is no parser for this vendor, so a structured audit cannot be produced; offer to audit a manually-normalized schema or to reason about pasted excerpts without finding IDs.113114As of v1.1 the audit also reads `system.ssh`, `system.auth`, `system.control_plane_protection`, `zones[].screen`, and `security_services` to cover device-plane hardening (SSH/management hardening, password/lockout policy, screen presence, and unreferenced security services); any of these fields that is absent simply skips its dependent check.115116As of v1.2 the audit reads two further fields, both added after a live-device117run produced wrong verdicts without them:118119- **`security_policies[].dynamic_applications`** — runtime application-identity120 matches (SRX `match dynamic-application`, PAN-OS App-ID members). It is a121 **narrowing field**: treat a rule as unrestricted only when `applications`,122 `services`, `dynamic_applications`, `url_categories`, `app_groups`,123 `source_users`, and `schedule` are *all* any/empty/absent. Include it in the124 match tuple `SEC-REDUNDANT` compares on, and in the tail test behind125 `SEC-NO-DENY-ALL`. Dropping it makes an AppID-scoped deny read as a terminal126 deny-all and makes two rules differing only by AppID scope look identical.127- **`address_objects[].type == "dynamic"`** — GeoIP, feed-backed, and tag-based128 objects whose membership resolves at runtime. They are **defined objects**:129 `SEC-ORPHAN-REF` must count them as resolved. Their members are unknowable130 from config, so any check needing concrete addresses (overlap, shadow,131 containment) degrades to heuristic or skips.132133If the parser populated neither field, say so in the skipped-checks list rather134than reporting the resulting findings as definitive.135136Graceful degradation: the schema is static, so some checks have no input (for example, anything depending on hit counts or last-used timestamps, and any check needing a field the source parser did not populate). When a field is missing, skip the dependent check rather than guessing, and record every skipped check in the audit summary so the gap is visible.137138## Severity & Confidence139140| Severity | One-line criterion |141|----------|--------------------|142| Critical | Direct, exploitable exposure — any-any-allow across a trust boundary, dangerous service open to untrust, plaintext admin reachable externally, broken/keyless VPN. |143| High | Strong weakness — overly permissive allow, shadowed rules hiding later policy, weak VPN/IKE crypto (DES/3DES/MD5/SHA1/DH<14), no logging on permit-to-untrust. |144| Medium | Material hygiene gap — missing explicit deny-all, redundant/overlapping rules, oversized any in one field, missing logging on internal allows. |145| Low | Minor hardening or correctness nit — overlapping objects, weak naming, narrow over-broad object. |146| Info | Operational cleanup with no direct risk — unused/duplicate objects, oversized groups, missing descriptions, consolidation opportunities, disabled rules. |147148This table is the general scale with context-adjusted examples; the per-check default severities in `references/check-catalog.md` are the authoritative defaults (a check's catalog severity wins over any example here, since real severity is adjusted by logging, scope, and trust-boundary context).149150Confidence is one of two values:151152- **definitive** — the schema fully supports the conclusion (e.g. `src_addresses` and `dst_addresses` both contain `any` with `action: allow`).153- **heuristic** — the conclusion depends on something the schema does not fully guarantee. Downgrade to heuristic when rule order is incomplete or ambiguous (shadow/redundancy claims), when object references are unresolved (orphan/unused claims), or when NAT could change real exposure. Label these as heuristic so reviewers verify before acting.154155## Audit Workflow1561571. **Establish input + vendor.** Confirm you have the intermediate schema (parse first if raw). Read `metadata.source_vendor` and `metadata` counts to size the audit.1582. **Resolve objects and zones.** Expand `address_groups`, `service_groups`, and `application_groups` to members; map `security_policies` `src_zones`/`dst_zones` to `zones` and their interfaces; note `nat_rules` that change effective exposure. Flag references that do not resolve (drives heuristic confidence later).1593. **Run the security checks.** Permissiveness/any-any, shadow/redundancy/overlap, missing deny-all and logging, dangerous exposed services, plaintext management, weak VPN/IKE/IPsec crypto. Also run the v1.1 device-plane families: SSH/management hardening (`SEC-SSH-ROOT-LOGIN`), security-services-unreferenced (`SEC-SERVICES-UNREFERENCED`), zone/NAT-without-policy (`SEC-ZONES-NAT-NO-POLICY`) plus the empty-policyset coverage warning (`SEC-EMPTY-POLICYSET`), host-inbound exposure (`SEC-HOST-INBOUND-EXPOSURE`), screen presence (`SEC-NO-SCREEN`), auth hardening (`SEC-AUTH-HARDENING`), control-plane/RE-filter protection (`SEC-NO-CONTROL-PLANE-PROTECTION`), IPv6 posture (`SEC-IPV6-POSTURE`), and remote-log completeness (`OPS-LOG-COMPLETENESS`). See `references/check-catalog.md` for the full check definitions, schema fields, and severity rationale.1604. **Run the operational checks.** Unused/duplicate objects, oversized groups, naming/description gaps, rule consolidation, disabled-rule cleanup. Also from `references/check-catalog.md`.1615. **Assign severity + confidence.** Apply the rubric above; downgrade to heuristic where rule order or object resolution is incomplete or NAT may alter exposure.1626. **Attach remediation.** Give vendor-neutral guidance plus a concrete snippet in the source vendor's syntax. See `references/remediation-patterns.md` for per-vendor fix patterns keyed by finding category.1637. **Produce the output.** Emit each finding with the Finding template, then the Audit Summary with the posture line, severity tally, skipped checks, and prioritized top fixes.164165## Reference Material (load on demand)166167- `references/check-catalog.md` — full catalog of security + operational checks (id, what it detects, schema fields, severity rationale).168- `references/remediation-patterns.md` — per-vendor (Cisco/Palo/FortiGate/SRX) fix snippets keyed by finding category.169- `references/example-audit.md` — a worked audit against a parsing-* fixture.170171## Output Templates172173### Finding174175```text176[<id>] <CRITICAL|HIGH|MEDIUM|LOW|INFO> (<definitive|heuristic>) — <title>177Category: <security|operational> / <subcategory>178Affected: <rule names/IDs, object names, zones>179Why it matters: <1–3 lines>180Remediation: <vendor-neutral guidance>181Fix (<source-vendor>):182 <concrete snippet in the source vendor's syntax>183```184185### Audit Summary186187```text188Posture: <one line; never "secure" — say "no in-scope findings" with caveats>189Findings: Critical <n> High <n> Medium <n> Low <n> Info <n>190Checks skipped (no data): <e.g. hit-count-dependent checks>191Top fixes (prioritized):192 1. <id> — <one line>193 2. <id> — <one line>194```195196## Common Pitfalls197198- Claiming a config is "secure" or "compliant" — only report "no in-scope findings" with caveats about what could not be checked.199- Mapping findings to compliance frameworks — stay framework-agnostic and cite no control IDs.200- Re-implementing parsing instead of delegating to the matching `parsing-*` skill.201- Reporting heuristic findings (possible shadow) as definitive when rule order or object references are incomplete.202- Auditing only the internet edge — include internal, management, VPN, and inter-zone rules.203- Ignoring NAT when reasoning about real exposure — a translated address can change what a rule actually permits.204- Flagging disabled rules as active risk — note them as cleanup, not exposure.205- Treating an unreferenced object as a finding without confirming the schema captured all references.206- Putting real secrets or PSKs in example output — always redact.207- Silently dropping checks when a schema field is missing — list them as skipped in the summary.208209## Verification Checklist210211- [ ] Confirm input type (parsed schema vs raw config) and source vendor.212- [ ] For raw config, the matching parsing-* skill was run first.213- [ ] Every finding has id, severity, confidence, affected refs, rationale, remediation.214- [ ] Remediation includes vendor-neutral guidance + a source-vendor snippet.215- [ ] Heuristic findings are labeled; definitive vs heuristic is correct.216- [ ] Skipped checks (missing schema fields) are listed in the summary.217- [ ] Output makes no "secure"/"compliant" claim and no compliance-framework mapping.218- [ ] No real secrets in any snippet.