agentsclimarketplace

Parsing fortinet configs

Skill fastrevmd-lab/fwskillsshare/skills/parsing-fortinet-configs

Agent skills for firewall work — parsing, auditing, converting, and running SRX. Works with Claude Code, Codex, and Hermes so far.

Install
npx -y skills add fastrevmd-lab/fwskillsshare --skill parsing-fortinet-configs

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Parse FortiGate and FortiOS full-configuration or backup exports into the shared firewall schema. Use when input contains config/edit/set/next/end blocks, VDOM, firewall policy or address, srcintf, dstintf, UTM profiles, or VIPs, including audit, conversion, diff, summary, and explanation tasks.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

27.0 KB, as published. Nobody here has run it

Parsing Fortinet FortiGate Configurations

Overview

Use this skill to parse Fortinet FortiGate / FortiOS backup or show full-configuration output into the shared vendor-neutral firewall intermediate schema. It focuses on nested config / edit / set / next / end blocks, including VDOMs, interfaces, zones, firewall addresses and services, policies, central SNAT, VIPs, routes, VPN, HA, profiles, and system settings.

FortiOS behavior is version- and feature-dependent. Preserve unknown blocks in residual_raw, capture VDOM context, and flag policy/NAT/profile constructs that cannot be mapped cleanly to the intermediate schema.

Scope and routing

Use only for FortiGate or FortiOS block syntax. Hand off ASA/FTD access-list, nameif, or object network input to parsing-cisco-configs, PAN-OS XML or set deviceconfig to parsing-palo-configs, and Junos hierarchy or set security to parsing-srx-configs. Verify production-bound results against current device documentation and output. Downstream consumers are the audit, conversion, and diff skills.

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.

Input Format

FortiOS configs use a hierarchical block format:

config <section>
    edit <name-or-id>
        set <key> <value>
        set <key> <value>
        config <sub-section>
            edit <sub-name>
                set <key> <value>
            next
        end
    next
    edit <name-or-id>
        ...
    next
end

Key syntax rules:

  • Values with spaces are quoted: set comment "Allow web traffic"
  • Multi-value fields use space-separated values: set srcaddr "addr1" "addr2"
  • Full block-syntax rules: references/config-format.md

Building the Config Tree

Parse the block format into a nested object tree:

  1. Track a stack of current context (section path + edit name)
  2. config <X> → push section name
  3. edit <N> → push entry name (strip quotes)
  4. set <key> <value> → store key-value at current depth
  5. next → pop entry
  6. end → pop section

Extraction Pipeline

1. Zones and Interfaces

Zones: config system zoneedit <name> with set interface <list> Interfaces: config system interfaceedit <name> with set vdom, set ip, set type, set vlanid Additional interface fields to extract:

  • set ip6 <addr/prefix> (inside nested config ipv6 sub-block) — IPv6 address
  • set mtu <value> — MTU
  • set type aggregate → type: "lag" (with set member for LAG members)
  • set type loopback → type: "loopback"
  • set type tunnel → type: "tunnel"
  • set mode dhcp — DHCP client (no static IP)
  • set dhcp-relay-ip <ips> — DHCP relay server IPs
  • set allowaccess <list> — management access protocols
  • set fortilink enable — FortiLink interface (exclude from output along with child interfaces)
  • set description <text>
  • Subinterface detection: dot-notation (port1.100 → parent=port1) or VLAN-bound (set interface <parent> + set vlanid <id>)
  • Management interface detection: names matching /^(mgmt\d*|management\d*)/iis_mgmt: true
  • After all interfaces parsed, back-populate lag_members on aggregate interfaces

Critical: Interface-as-Zone Merging FortiGate can use interface names directly as zones in policies (via srcintf/dstintf). If a policy references an interface name not in any zone, treat that interface as its own zone. Merge zones and interfaces: create a zone entry for each interface used as a zone.

Zone Building Priority 3: Unzoned Interfaces After explicit zones and policy-referenced interfaces, create auto-zones for any interface that has an IP address (or is a DHCP client) but was not yet assigned to a zone.

Allowaccess Classification: Classify allowaccess values into management services (ssh, https, http, telnet, ping, snmp, netconf, fgfm, fmg-access, ftm, radius-acct, security-fabric, fabric, capwap, speed-test) and routing protocols (ospf, bgp, rip, isis, bfd). Attach to zones as host_inbound data.

2. Address Objects

Path: config firewall addressedit <name>

Types — detect from set type or infer from fields:

  • set type ipmask + set subnet <ip> <mask> → type: "subnet" (convert mask to CIDR); promote a /32 (or IPv6 /128) result to type: "host"
  • set type iprange + set start-ip / set end-ip → type: "range", value: "start-end"
  • set type fqdn + set fqdn <domain> → type: "fqdn"
  • set type geography + set country <code> → type: "geo" (warn: limited cross-platform support)
  • set type wildcard + set wildcard <ip> <mask> → type: "wildcard" (preserve the <ip> <mask> wildcard value; "network" is NOT a valid schema type) with info warning
  • set type wildcard-fqdn + set wildcard-fqdn <pattern> → type: "fqdn" (convert from wildcard-fqdn with info warning)

Also extract: set comment, set associated-interface. Convert subnet mask notation (255.255.255.0) to CIDR (/24). Auto-detect IP version.

IPv6 Address Objects: config firewall address6edit <name>

  • set ip6 <ipv6prefix/len> → type: "subnet" (or "host" if /128) IPv6 Address Groups: config firewall addrgrp6edit <name>
  • set member <list>

3. Address Groups

Path: config firewall addrgrpedit <name> Extract: set member <list> (space-separated quoted names)

4. Service Objects

Path: config firewall service customedit <name> Extract from:

  • set protocol TCP/UDP/SCTP + set tcp-portrange <range> / set udp-portrange <range> / set sctp-portrange <range>
  • set protocol ICMP + set icmptype / set icmpcode
  • set protocol IP + set protocol-number <n> → preserve the IP protocol number <n> (e.g. GRE=47, ESP=50) as the service object's protocol (the numeric value, or via a warning). Only emit protocol: "any" when set protocol IP genuinely means all IP protocols (no protocol-number, or protocol-number 0).
  • set protocol ICMP6 → protocol: "icmpv6"
  • Port range format: 80 or 80-443 or 80:1024-65535 (dst:src)
  • Note: A single custom service can set ANY combination of tcp-portrange, udp-portrange, and sctp-portrange simultaneously. Split into separate service objects — one TCP, one UDP, and/or one SCTP — according to which ranges are present.

5. Service Groups

Path: config firewall service groupedit <name> Extract: set member <list>

6. Security Policies

Path: config firewall policyedit <id>

For each policy extract:

  • nameset name <value> (FortiGate uses numeric IDs as primary key, name is optional)
  • src_zonesset srcintf <list> (interface or zone names)
  • dst_zonesset dstintf <list>
  • src_addressesset srcaddr <list>
  • dst_addressesset dstaddr <list>
  • servicesset service <list>
  • applicationsset application <list> (application control IDs/names)
  • actionset action accept → "allow", set action deny → "deny"
  • log_startset logtraffic-start enable (start logging is controlled separately from logtraffic)
  • log_endset logtraffic all. set logtraffic utm logs UTM/security events only, NOT end-of-session traffic — map to log_end: false and note UTM-only logging in metadata.warnings
  • disabledset status disable
  • descriptionset comments <value>
  • scheduleset schedule <value>
  • source_usersset groups <list> (FSSO groups)

Policy NAT (do NOT emit a policy nat field — the policy schema has no nat field): FortiGate per-policy source NAT (set nat enable, optionally with set ippool enable + set poolname <pool>) must be translated into a nat_rules[] source-NAT entry, NOT a flag on the policy:

  • set nat enable alone → source-NAT (interface/egress overload) on the policy's dstintf, scoped to the policy's srcaddr/dstaddr.
  • set nat enable + set ippool enable + set poolname <pool> → source-NAT using the named IP pool <pool> (cross-reference config firewall ippool).
  • If the source-NAT intent cannot be resolved to a concrete nat_rules[] entry, preserve it as a metadata.warnings / residual_raw note rather than inventing a policy field.

Default values when fields are omitted from config:

  • action defaults to accept (→ "allow")
  • logtraffic defaults to utm on accept policies (→ log_end: false; UTM-event logging only)
  • status defaults to enable (→ disabled: false)

UTM / Security Profiles — when set utm-status enable:

  • set av-profile <name> → antivirus
  • set webfilter-profile <name> → URL filtering
  • set ips-sensor <name> → IPS/IDP
  • set application-list <name> → application control (do NOT emit an application-list security-profile key — it is not a schema-supported profile key; signal app-control presence via security_services.app_id at device level and/or store the profile reference in security_profile_objects / metadata.warnings)
  • set ssl-ssh-profile <name> → SSL inspection
  • set dnsfilter-profile <name> → DNS filtering
  • set emailfilter-profile <name> → email filtering
  • set dlp-profile <name> → DLP
  • set profile-group <name> → profile group (overrides individual profiles)

7. NAT Rules

Source NAT (IP Pools): config firewall ippooledit <name> Extract: set startip, set endip, set type (overload, one-to-one, fixed-port-range), set associated-interface <name> — binds pool to specific egress interface

Central SNAT: config firewall central-snat-mapedit <id> Extract the full FortiOS central-SNAT field set so real rules are not missed:

  • set srcintf <list> — source interface(s)
  • set dstintf <list> — destination/egress interface(s)
  • set orig-addr <list> — original (pre-NAT) source addresses
  • set dst-addr <list> — destination addresses the rule matches
  • set nat enable|disable — whether this entry performs NAT (a disabled entry = no-NAT exemption; preserve it)
  • set nat-ippool <list> (also set natippool variant) — translated source pool; absent → egress-interface overload
  • set protocol <n> and set orig-port / set nat-port where present — protocol/port scoping Map to a nat_rules[] source-NAT entry (or a no-NAT exemption when nat disable).

Destination NAT (VIPs): config firewall vipedit <name> Extract: set extip (original dest), set mappedip (translated dest), set extintf, set portforward enable + set extport / set mappedport Note: VIPs are referenced in policies via set dstaddr <vip-name>

8. Schedules

Recurring: config firewall schedule recurringedit <name> Extract: set day, set start, set end One-time: config firewall schedule onetimeedit <name> Extract: set start, set end Group: config firewall schedule groupedit <name> Extract: set member

9. Application Mapping (L7 → Canonical)

FortiGate supports L7 application control via set application <list> on policies. These reference FortiOS application IDs or names from the application control database.

Extracting application references from policies:

  • set application <list> — space-separated application IDs or names
  • set application-list <name> — references an application control list profile (separate from direct app match)

Resolving FortiOS application names to canonical:

FortiOS NameCanonical AppCategory
HTTPShttpsweb
HTTPhttpweb
SSHsshremote-access
RDPrdpremote-access
DNSdnsnetwork-mgmt
SMTPsmtpemail
NTPntpnetwork-mgmt
SNMPsnmpnetwork-mgmt
FTPftpfile-transfer
TFTPtftpfile-transfer
SIPsipvoip
LDAPldapauth
Kerberoskerberosauth
SMBsmbfile-transfer
MySQLmysqldatabase
MSSQLmssqldatabase
PostgreSQLpostgresqldatabase
MongoDBmongodbdatabase
Zoomzoomcollaboration
Microsoft.Teamsms-teamscollaboration
Slackslackcollaboration
YouTubeyoutubestreaming
Netflixnetflixstreaming

On policy output: When set application values are resolved, populate the policy's apps array with { vendor_name: "HTTPS", canonical: "https", confidence: 1.0, category: "web" }. The services array keeps any set service matches separately.

Application control list profiles (config application list) define grouped app-control policies. These do not map 1:1 to application groups — they are UTM profiles that filter applications by category, risk, or specific app ID. The schema has no application-list profile key — represent app-control presence via security_services.app_id (device-level) and store the profile reference in security_profile_objects and/or metadata.warnings. Do not try to decompose the list into individual apps.

Unresolvable apps: FortiOS numeric app IDs without a known name mapping → set confidence: 0.0, preserve the ID as vendor_name, and warn.

9b. Application Groups

FortiOS does not have explicit application groups in the same way PAN-OS does. The closest equivalent is config application group which groups application control signatures.

Path: config application groupedit <name> Extract: set application <list> — member application IDs/names. Resolve each member to canonical. Store in application_groups array.

If a group contains a mix of L7 apps and port-based services, split them appropriately.

10. Security Profile Definitions

Parse full profile objects for reference:

  • config antivirus profile
  • config webfilter profile
  • config ips sensor
  • config application list
  • config firewall ssl-ssh-profile

11. Routing

  • Static routes (IPv4): config router staticedit <id> with set dst, set gateway, set device, set distance
  • Static routes (IPv6): config router static6edit <id> with same fields using IPv6 prefixes
  • BGP: config router bgp — extract:
    • set as, set router-id, global set keepalive-timer/set holdtime-timer
    • Per-neighbor (in config neighbor): remote-as, description, update-source, password (record presence only — redact the value, never emit it), per-neighbor timers (override global), next-hop-self, soft-reconfiguration, route-reflector-client, status enable|disable
    • config network entries (prefix advertisements)
    • config redistribute with set status enable|disable
    • Warn: route-map/prefix-list references are not converted
  • OSPF: config router ospf — extract:
    • set router-id, set auto-cost-reference-bandwidth
    • config area: area ID, type (stub/nssa with no-summary), default-cost, authentication
    • config ospf-interface: area assignment, passive flag, cost, priority, hello/dead intervals, network-type (point-to-point/broadcast), MD5 authentication with key ID (record key presence only — redact the key value)
    • config redistribute: source, status, metric, metric-type
    • Warn: MD5 keys in cleartext in source config (key values are never emitted in output)
  • OSPFv3: config router ospf6 — same structure but uses config ospf6-interface (not ospf-interface)
  • Policy routing: config router policy → PBF rules

12. Infrastructure

  • Version: Extract from #config-version=<version>: comment line at top of config

  • System Global: config system global → extract set hostname

  • DNS: config system dns → extract set primary, set secondary, set domain

  • NTP: config system ntp with nested config ntpserver → extract server entries

  • Admin Users: config system admin → extract access profile (super_admin→super-admin, prof_admin→admin), SSH public keys (ssh-public-key1/2/3). Warn when users lack SSH keys.

  • HA: config system haset mode (a-p/a-a), set group-id, set priority, set hbdev, set monitor

  • Screen/DoS: config firewall DoS-policy + IPS sensor definitions

  • Syslog: config log syslogd setting

  • DHCP Server: config system dhcp server — extract top-level fields (set default-gateway, set netmask, set interface, set domain, set lease-time, set dns-server1/dns-server2) plus nested config ip-range (start-ip/end-ip) and config reserved-address (mac, ip, description). Derive network CIDR from gateway + netmask. FortiOS compound proposal parsing for VPN: FortiOS encodes IKE/IPsec proposals as compound strings. Do NOT assume every proposal is a simple encryption-integrity pair — parse encryption / integrity / prf separately:

  • Simple enc-integrity: aes256-sha256 → encryption (aes256 → canonical aes-256) + integrity (sha256).

  • AEAD GCM (no separate integrity): aes256gcm, aes128gcm, chacha20poly1305 carry their own authentication — integrity is N/A. Phase2 GCM is the bare token (e.g. aes256gcm, no integrity suffix).

  • IKEv2 phase1 PRF suffix (phase1-only): aes256gcm-prfsha384 → encryption aes256gcm, prf sha384, no separate integrity. The prf* suffix appears only on phase1 proposals, never phase2.

  • null: null encryption (no confidentiality) and null integrity also exist — flag as weak. Multiple proposals are space-separated: aes256-sha256 aes128-sha1 aes256gcm. Tokenize each on -, then classify each segment as encryption, integrity, or prf rather than assuming a fixed two-part split.

  • VPN IPsec:

    • config vpn ipsec phase1-interface: IKE version, remote-gw, proposal (compound strings — see "FortiOS compound proposal parsing" above), DH group, key lifetime, PSK presence (mask the value as "****" per the schema — never emit the raw key), certificate auth detection
    • config vpn ipsec phase2-interface: phase1name reference, proposal, PFS, DH group, key lifetime
    • Phase1 name auto-creates a tunnel interface of the same name
    • Resolve tunnel IP from matching interface, associate static routes through tunnel interfaces
    • Flag weak algorithms (DES/3DES, MD5, DH group ≤ 5)
    • Warn: certificate-based auth not fully converted

13. Tokenizer

Handle quoted multi-value lines correctly: set member "HOST_A" "HOST_B"['set', 'member', 'HOST_A', 'HOST_B']. Strip quotes during tokenization. Values containing spaces must be kept as single tokens when quoted.

14. Residual Config Capture

Capture unrecognized config sections verbatim with depth tracking. Categorize into: VPN/IPsec, Routing Protocols, AAA/Users, PKI/Certificates, Wireless, Switching, DNS, NTP, SNMP, Other. Store in residual_raw for manual review.

15. Multi-VDOM

Detect VDOM context: config vdom / edit <vdom-name>. Each interface has set vdom <name>. Parse per-VDOM, tag items, merge.

16. Implicit Rules

After parsing all explicit policies, append:

  • Per-zone Intra-zone rules ONLY for explicit config system zone entries carrying set intrazone deny|allow. Do NOT fabricate intra-zone rules for raw interface auto-zones (interfaces never declared in config system zone).
  • Implicit: Default Deny — action: "deny", all any, _implicit: true

Implicit-rule name values (e.g. "default-deny", "Implicit: Default Deny") are free-form labels; consumers must match implicit rules on _implicit: true, never on the name.

Output Format

Present results in the intermediate schema format documented in references/intermediate-schema.md.

Note: schema sections not yet populated by this pipeline (e.g., security_profile_objects, routing_contexts) are emitted empty ([]/{}); any unhandled source constructs are captured in residual_raw rather than dropped.

Full intermediate-schema emission is optional for single live-device work. The complete JSON schema exists primarily for cross-vendor conversion and multi-config diffing. When interpreting or auditing a single live device pulled via SSH/API for an ops/audit task, it is fine to reason directly from the raw FortiOS config and skip full schema emission — extract the sections relevant to the question. Emit the full schema when the parse will feed firewall-config-conversion, firewall-config-diff, or another config for comparison.

Parser Quality Gates

Before returning a parse, run these common quality gates and include the results in the response:

  1. Format and scope detection — report detected vendor, platform family, config format, version clues, virtual context names (VDOM/vsys/logical-system/routing-instance), and whether input appears complete or partial.
  2. Schema conformance — emit the vendor-neutral JSON sections defined in references/intermediate-schema.md; use empty arrays/objects for absent sections rather than omitting expected top-level keys.
  3. Object counts — summarize counts for zones, interfaces, address objects/groups, service/application objects/groups, policies, NAT rules, routes, VPNs, HA, admin users, and residual blocks.
  4. Reference resolution — list unresolved object, service/application, zone/interface, profile, route, VPN, and NAT references with source rule/context where possible.
  5. Ordering preservation — preserve security policy order, NAT order, route order when relevant, and inherited/pre/post/global ordering metadata with _rule_index or a vendor-specific context field.
  6. State preservation — preserve disabled/inactive objects and rules, comments/descriptions, tags, schedules/time-ranges, negation flags, logging settings, and profile attachments.
  7. Residual capture — put unsupported or ambiguous source lines/blocks into residual_raw with enough context for manual review. Do not silently drop unknown syntax.
  8. Warnings and assumptions — populate metadata.warnings with parser limitations, partial-input assumptions, ambiguous conversions, and version-specific caveats.
  9. Conversion readiness — if the user asks for migration/conversion, explicitly separate parsed facts from proposed target-platform design choices and call out non-isomorphic features.

A high-quality parse is not just valid JSON: it must make uncertainty visible. Prefer a complete parse with warnings and residuals over a clean-looking parse that hides unsupported constructs.

Analysis Checks

After extraction, run these checks and report findings:

  1. Unused objects — addresses/services not referenced by any policy
  2. Shadowed policies — rules fully covered by earlier rules
  3. Overly permissive — rules with "all" in src+dst addresses and services
  4. Missing loggingset logtraffic disable or set logtraffic utm on permit rules
  5. Disabled policiesset status disable
  6. Duplicate objects — same value, different names
  7. Empty groups — groups with no members
  8. VIP references — VIPs used as dst addresses (flag for NAT review)
  9. Geography objects — limited cross-platform support

Reference Files

  • references/config-format.md — FortiOS config block syntax reference
  • references/intermediate-schema.md — Output schema specification
  • references/parsing-patterns.md — Edge cases, mask conversion, application mapping
  • references/example-sample-parse.md — Worked end-to-end example (input config → parsed JSON)
  • references/fixture-minimal-input.md — Minimal parser fixture input
  • references/fixture-expected-output.json — Expected high-level intermediate-schema output for the minimal fixture

Secret Handling

Never emit secrets raw. IKE/VPN pre-shared keys, routing-protocol authentication keys (BGP/OSPF), and user password hashes must be masked as "****" (or reduced to a presence flag) with a metadata.warnings entry noting the redaction — matching the shared-schema convention ("psk": "****").

Common Pitfalls

  1. Do not assume every policy interface is a zone; FortiGate policies can reference zones or raw interfaces.
  2. VIP objects often imply destination NAT when referenced by policies; preserve both policy and NAT intent.
  3. Central SNAT and per-policy NAT are different models; detect both and warn when both are present.
  4. Quoted multi-value fields require tokenizer-aware parsing; whitespace splitting corrupts names with spaces.
  5. Profile groups and individual UTM profiles must be expanded while preserving unmapped FortiGate-specific profile fields.
  6. Never emit raw secrets: mask VPN PSKs as "****" and represent BGP neighbor passwords and OSPF MD5 keys as presence flags plus a metadata.warnings note — shared secrets, passwords, and keys must never appear verbatim in parse output.

Verification Checklist

  • Input vendor/platform and config format were detected correctly
  • All major object counts are reported: zones, interfaces, addresses, services/applications, policies, NAT, routes, VPN, HA, and system settings
  • Output conforms to references/intermediate-schema.md
  • Disabled/inactive rules and objects are preserved with explicit state
  • Unresolved references, unsupported blocks, and parser assumptions are listed in metadata.warnings and/or residual_raw
  • Rule order and NAT order are preserved with _rule_index or equivalent ordering metadata
  • Cross-vendor conversion caveats are called out before suggesting target-platform config
  • No raw secrets in output — PSKs masked as "****", BGP/OSPF passwords and keys reduced to presence flags with warnings

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.