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.
npx -y skills add fastrevmd-lab/fwskillsshare --skill parsing-fortinet-configsAssembled 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:
- Track a stack of current context (section path + edit name)
config <X>→ push section nameedit <N>→ push entry name (strip quotes)set <key> <value>→ store key-value at current depthnext→ pop entryend→ 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:
set ip6 <addr/prefix>(inside nestedconfig ipv6sub-block) — IPv6 addressset mtu <value>— MTUset type aggregate→ type: "lag" (withset memberfor 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 IPsset allowaccess <list>— management access protocolsset 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*)/i→is_mgmt: true - After all interfaces parsed, back-populate
lag_memberson 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 warningset 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 address6 → edit <name>
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 icmpcodeset 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" whenset protocol IPgenuinely means all IP protocols (noprotocol-number, orprotocol-number 0).set protocol ICMP6→ protocol: "icmpv6"- Port range format:
80or80-443or80:1024-65535(dst:src) - Note: A single custom service can set ANY combination of
tcp-portrange,udp-portrange, andsctp-portrangesimultaneously. 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 fromlogtraffic) - log_end —
set logtraffic all.set logtraffic utmlogs UTM/security events only, NOT end-of-session traffic — map to log_end: false and note UTM-only logging inmetadata.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 enablealone → source-NAT (interface/egress overload) on the policy'sdstintf, scoped to the policy'ssrcaddr/dstaddr.set nat enable+set ippool enable+set poolname <pool>→ source-NAT using the named IP pool<pool>(cross-referenceconfig firewall ippool).- If the source-NAT intent cannot be resolved to a concrete
nat_rules[]entry, preserve it as ametadata.warnings/residual_rawnote rather than inventing a policy field.
Default values when fields are omitted from config:
actiondefaults toaccept(→ "allow")logtrafficdefaults toutmon accept policies (→ log_end: false; UTM-event logging only)statusdefaults toenable(→ disabled: false)
UTM / Security Profiles — when set utm-status enable:
set av-profile <name>→ antivirusset webfilter-profile <name>→ URL filteringset ips-sensor <name>→ IPS/IDPset application-list <name>→ application control (do NOT emit anapplication-listsecurity-profile key — it is not a schema-supported profile key; signal app-control presence viasecurity_services.app_idat device level and/or store the profile reference insecurity_profile_objects/metadata.warnings)set ssl-ssh-profile <name>→ SSL inspectionset dnsfilter-profile <name>→ DNS filteringset emailfilter-profile <name>→ email filteringset dlp-profile <name>→ DLPset 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 addressesset dst-addr <list>— destination addresses the rule matchesset nat enable|disable— whether this entry performs NAT (a disabled entry = no-NAT exemption; preserve it)set nat-ippool <list>(alsoset natippoolvariant) — translated source pool; absent → egress-interface overloadset protocol <n>andset orig-port/set nat-portwhere present — protocol/port scoping Map to anat_rules[]source-NAT entry (or a no-NAT exemption whennat 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 end
One-time: config firewall schedule onetime → edit <name>
Extract: set start, set end
Group: 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 namesset 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 | |
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 profileconfig webfilter profileconfig ips sensorconfig application listconfig firewall ssl-ssh-profile
11. Routing
- Static routes (IPv4):
config router static→edit <id>withset 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, globalset 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 networkentries (prefix advertisements)config redistributewithset status enable|disable- Warn: route-map/prefix-list references are not converted
- OSPF:
config router ospf— extract:set router-id,set auto-cost-reference-bandwidthconfig area: area ID, type (stub/nssa with no-summary), default-cost, authenticationconfig 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 usesconfig ospf6-interface(notospf-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→ extractset hostname -
DNS:
config system dns→ extractset primary,set secondary,set domain -
NTP:
config system ntpwith nestedconfig 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 ha—set 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 nestedconfig ip-range(start-ip/end-ip) andconfig 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 simpleencryption-integritypair — parse encryption / integrity / prf separately: -
Simple enc-integrity:
aes256-sha256→ encryption (aes256→ canonicalaes-256) + integrity (sha256). -
AEAD GCM (no separate integrity):
aes256gcm,aes128gcm,chacha20poly1305carry 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→ encryptionaes256gcm, prfsha384, no separate integrity. Theprf*suffix appears only on phase1 proposals, never phase2. -
null:
nullencryption (no confidentiality) andnullintegrity 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 detectionconfig 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 zoneentries carryingset intrazone deny|allow. Do NOT fabricate intra-zone rules for raw interface auto-zones (interfaces never declared inconfig 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_indexor 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_rawwith enough context for manual review. Do not silently drop unknown syntax. - Warnings and assumptions — populate
metadata.warningswith 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 disableorset logtraffic utmon 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
Reference Files
references/config-format.md— FortiOS config block syntax referencereferences/intermediate-schema.md— Output schema specificationreferences/parsing-patterns.md— Edge cases, mask conversion, application mappingreferences/example-sample-parse.md— Worked end-to-end example (input config → parsed JSON)references/fixture-minimal-input.md— Minimal parser fixture inputreferences/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.
- Quoted multi-value fields require tokenizer-aware parsing; whitespace splitting corrupts names with spaces.
- 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 ametadata.warningsnote — 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.warningsand/orresidual_raw - Rule order and NAT order are preserved with
_rule_indexor 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