See what your FortiGate really allows.
RuleScope reads a FortiGate config backup, or the firewall itself through a read-only API token, and shows what is reachable from the internet, which rules are dead or redundant, where the device is not hardened, and what changed since the last run. It runs on your own machine with nothing but Python.
| ID | Rule | From, to | Source, destination | Service | Action | What RuleScope found |
|---|---|---|---|---|---|---|
| 10 | LAN-internet | internal to virtual-wan-link | all to all | ALL | accept | MediumInternet-bound accept rule without security profiles MediumUnrestricted outbound internet access |
| 11 | LAN-web | internal to wan1 | LAN_HALVES to all | WEB_PORTS | accept | LowInternet-bound rule missing security profiles LowRedundant rule |
| 12 | block-bad-host | LAN_ZONE to wan2 | LAN_NET to Bad_Host | ALL | deny | HighDeny rule never applies |
| 20 | web-to-files | dmz to internal | Web_Server to File_Server | SMB | accept | MediumAccept rule does not log traffic |
| 21 | dmz-deny-lan | dmz to internal | all to all | ALL | deny | Nothing |
| 22 | web-to-db | dmz to internal | Web_Server to DB_Server | MYSQL | accept | MediumAccept rule can never match |
| 30 | inbound-web | wan1 to dmz | Israel to VIP_WEB | HTTPS | accept | Nothing |
| 31 | inbound-all-to-dmz | wan1 to dmz | all to all | HTTPS | accept | MediumInternet-facing rule without IPS |
| 32 | rdp-to-fileserver | wan1 to internal | all to VIP_RDP | RDP | accept | HighRisky service reachable from the internet MediumInternet-facing rule without IPS |
| 33 | dmz-host-static-nat | wan1 to dmz | Israel to VIP_ALL | ALL | accept | MediumInbound rule exposes all ports LowInternet-facing rule without IPS |
| 40 | m365 | internal to wan1 | LAN_NET to internet service | internet service | accept | MediumInternet-bound accept rule without security profiles LowRedundant rule |
| 41 | (no name) | internal to wan1 | LAN_NET to Update_Site | HTTPS | accept (disabled) | InfoDisabled rule InfoRule has no name or comment |
| 50 | lan-ping-dmz | internal to dmz | LAN_NET to all | ALL_ICMP, WEB_RANGE | accept | Nothing |
| 51 | lan-https-dmz | internal to dmz | File_Server to Web_Server | PING, HTTPS | accept | LowRedundant rule |
| 60 | promo-access | internal to wan1 | LAN_NET to Update_Site | HTTPS | accept | MediumInternet-bound accept rule without security profiles LowRule uses an expired schedule LowRedundant rule |
Open the sample output
Everything linked here is real RuleScope 0.4.2 output, rebuilt every night from the current code. The three firewalls are fictional, so nothing on this site describes a real network.
- Fleet dashboard
Three firewalls on one page: score, internet exposure, firmware support status and what changed since the previous run.
- Audit report for FGT-HQ
The report you hand to a client or keep for an audit: summary, priority actions, quick wins, exposure map, the 37-practice baseline with evidence, and a compliance appendix. Light and dark, five palettes, prints cleanly.
- Change report for FGT-BR-SOUTH
Two runs, and between them someone changed the firewall. RuleScope raised these alerts:
- Management access added on internet-facing interface wan2: ssh
- New admin account: contractor
- Remediation script
FortiOS commands for the findings. Anything that changes traffic, VPN peers or management access is commented out until you have reviewed it.
- Findings as CSV
One row per finding with severity, effort and compliance references, for Excel or a ticket system. The baseline is a CSV too, and everything is available as JSON.
What it checks
- Internet exposure
- Every service reachable from outside: port forwards, static NAT, routed inbound rules, admin GUI or SSH on WAN, the SSL-VPN portal. Risky services such as RDP, SMB and databases are called out, and severity depends on who can reach them.
- Rule base
- Deny rules that never apply, accept rules that can never match, redundant rules with the covering rule named, any-to-any, unrestricted outbound, zero-hit and stale rules, expired schedules.
- Device hardening
- Telnet and HTTP management, trusted hosts, admins without MFA, the default admin account, lockout and password policy, TLS versions, strong-crypto, SNMP, NTP, HA.
- Firmware
- FortiOS end-of-support dates, and the CVEs that affect the exact version from the NIST NVD, flagged when CISA lists them as exploited.
- Logging and inspection
- Accept rules without logging, no central logging, internet-bound rules without IPS, antivirus, DNS filter or application control, expired subscriptions.
- VPN
- Weak IPsec proposals and DH groups, aggressive mode with a pre-shared key, phase 2 without PFS, SSL-VPN on old TLS or the factory certificate.
- Objects
- Addresses, groups, services and virtual IPs that nothing references.
On top of the checks, a baseline of 37 practices from Fortinet's hardening guide and the CIS FortiGate benchmark is marked pass, partial, fail or not assessed, with the evidence from your configuration. A setting RuleScope could not read never counts as a pass.
Run it on your own firewall
You need Python 3.9 or later and nothing else.
Get RuleScope
git clone https://github.com/mrpash89/rulescope-fortigate.git cd rulescope-fortigate
Audit a config backup
Works offline, on a backup you downloaded or one a client sent you.
python3 -m rulescope audit --config backup.conf --html report.html --cli fix.txt --csv findings.csv
Or read the firewall directly
With a read-only API token you also get hit counts, licenses and the change log. The certificate fingerprint is checked before the token is sent.
python3 -m rulescope cert --host 192.0.2.1 # prints the SHA-256 fingerprint python3 -m rulescope audit --host 192.0.2.1 --token-file ~/.rulescope-token \ --pin-sha256 FINGERPRINT --html report.html
The README covers the read-only API user, fleet mode, scheduled change tracking and alerts.
Built to run next to production
It never changes a firewall
RuleScope only reads. Fixes come as a script that you review and paste yourself.
Your configuration stays with you
No cloud service, no account, no telemetry. The only optional outbound request is the CVE lookup at the NIST NVD, and it sends a FortiOS version number.
Nothing to install
Python standard library only, with no third-party packages to vet or update.
Configs are treated as hostile
A backup can come from anyone. Names and comments in it cannot become script commands, spreadsheet formulas or markup, and randomized tests check that on every change.
Open source
Apache-2.0. Read the code, run the tests, send a pull request.