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.
Installation
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
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.
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.
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:
Track a stack of current context (section path + edit name)
config <X> → push section name
edit <N> → push entry name (strip quotes)
set <key> <value> → store key-value at current depth
next → pop entry
end → pop section
Extraction Pipeline
1. Zones and Interfaces
Zones:config system zone → edit <name> with set interface <list>Interfaces:config system interface → edit <name> with set vdom, set ip, set type, set vlanid
Additional interface fields to extract:
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 address → edit <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.
set ip6 <ipv6prefix/len> → type: "subnet" (or "host" if /128)
IPv6 Address Groups:config firewall addrgrp6 → edit <name>
set member <list>
3. Address Groups
Path: config firewall addrgrp → edit <name>
Extract: set member <list> (space-separated quoted names)
4. Service Objects
Path: config firewall service custom → edit <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 group → edit <name>
Extract: set member <list>
6. Security Policies
Path: config firewall policy → edit <id>
For each policy extract:
name — set name <value> (FortiGate uses numeric IDs as primary key, name is optional)
src_zones — set srcintf <list> (interface or zone names)
dst_zones — set dstintf <list>
src_addresses — set srcaddr <list>
dst_addresses — set dstaddr <list>
services — set service <list>
applications — set application <list> (application control IDs/names)
action — set action accept → "allow", set action deny → "deny"
log_start — set logtraffic-start enable (start logging is controlled separately from logtraffic)
log_end — set 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
disabled — set status disable
description — set comments <value>
schedule — set schedule <value>
source_users — set 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 ippool → edit <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-map → edit <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 vip → edit <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 recurring → edit <name>
Extract: set day, set start, set endOne-time:config firewall schedule onetime → edit <name>
Extract: set start, set endGroup:config firewall schedule group → edit <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 Name
Canonical App
Category
HTTPS
https
web
HTTP
http
web
SSH
ssh
remote-access
RDP
rdp
remote-access
DNS
dns
network-mgmt
SMTP
smtp
email
NTP
ntp
network-mgmt
SNMP
snmp
network-mgmt
FTP
ftp
file-transfer
TFTP
tftp
file-transfer
SIP
sip
voip
LDAP
ldap
auth
Kerberos
kerberos
auth
SMB
smb
file-transfer
MySQL
mysql
database
MSSQL
mssql
database
PostgreSQL
postgresql
database
MongoDB
mongodb
database
Zoom
zoom
collaboration
Microsoft.Teams
ms-teams
collaboration
Slack
slack
collaboration
YouTube
youtube
streaming
Netflix
netflix
streaming
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 group → edit <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 static → edit <id> with set dst, set gateway, set device, set distance
Static routes (IPv6):config router static6 → edit <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)
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:
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
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:
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.
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.
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.
Reference resolution — list unresolved object, service/application, zone/interface, profile, route, VPN, and NAT references with source rule/context where possible.
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.
State preservation — preserve disabled/inactive objects and rules, comments/descriptions, tags, schedules/time-ranges, negation flags, logging settings, and profile attachments.
Residual capture — put unsupported or ambiguous source lines/blocks into residual_raw with enough context for manual review. Do not silently drop unknown syntax.
Warnings and assumptions — populate metadata.warnings with parser limitations, partial-input assumptions, ambiguous conversions, and version-specific caveats.
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:
Unused objects — addresses/services not referenced by any policy
Shadowed policies — rules fully covered by earlier rules
Overly permissive — rules with "all" in src+dst addresses and services
Missing logging — set logtraffic disable or set logtraffic utm on permit rules
Disabled policies — set status disable
Duplicate objects — same value, different names
Empty groups — groups with no members
VIP references — VIPs used as dst addresses (flag for NAT review)
Geography objects — limited cross-platform support
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
Do not assume every policy interface is a zone; FortiGate policies can reference zones or raw interfaces.
VIP objects often imply destination NAT when referenced by policies; preserve both policy and NAT intent.
Central SNAT and per-policy NAT are different models; detect both and warn when both are present.
Profile groups and individual UTM profiles must be expanded while preserving unmapped FortiGate-specific profile fields.
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