fix(fortigate): one VPN brute force alert per source address - #2790
Merged
kryonsx merged 1 commit intoOct 1, 2026
Merged
Conversation
"FortiGate VPN Authentication Brute Force" grouped by source address and user name, so a password spraying run opened a new top-level alert for every user name it tried after the source crossed the threshold (10 failures within 15 minutes), and stored a child for every repeat. The rule now raises one alert per firewall, VDOM and source address and drops repeats for seven days (deduplicateBy dataSource, devid, vd, adversary.ip). Its condition and history threshold are unchanged. fortigate_alert_volume_test.go pins the keys and that two user names from one address share them; the contract test now also checks deduplicateBy keys. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
"FortiGate VPN Authentication Brute Force" grouped by source address and user name. Once a source crosses the threshold (10 SSL-VPN login failures within 15 minutes), a password spraying run opens a new top-level alert for every user name it tries and stores a child alert for every repeat. On one v11 deployment a spraying run from two addresses produced 10 alerts in one hour on 2026-09-28, the first day the filter's
vpnAuthFailuremarker (which the history search counts) was deployed.Noise is reduced here only by de-duplication. The condition and the history threshold are unchanged.
What changes
groupByadversary.ip, adversary.user, lastEvent.log.devid, lastEvent.log.vddeduplicateBydataSource, lastEvent.log.devid, lastEvent.log.vd, adversary.ip: one alert per firewall, VDOM and source address, repeats dropped for seven daysThe user names a source tried are in the alert's events and in the firewall logs.
Expected volume
Replaying 30 days of real SSL-VPN login failures from the eight v11 deployments that send FortiGate logs with the unchanged 10-in-15-minutes history:
All 26 sources over the threshold were on one deployment (10,637 of the failures); the failures on the other deployments stayed below it.
Tests
plugins/alerts/fortigate_alert_volume_test.go(new) runs fabricated SSL-VPN failure lines through the step-by-step FortiGate filter model and the pinned go-sdk v1.1.36 CEL. It pins the de-duplication keys, checks that they resolve, and checks that two user names from one address produce the same key.deduplicateBykeys;TestFortiGateSDKHistorystill pins the 10-in-15-minutes history.go test ./...inplugins/alertspasses on this branch, which is based on currentv11(89cd26c4).8a3ade7, go-sdk v1.1.36, the production events and alerts plugins, OpenSearch 2.19.1, this branch's FortiGate filter from currentv11) with fabricated SSL-VPN failure lines in three runs: 10 failures each from two spraying addresses (one per user name) and 1 from a third address; then a new user name from the first address and a second one from the third; then another user name from the first address and the first attempt from the second spraying address. Every run processed all of its events, and each alert carried its 10 earlier failures from the history search.🤖 Generated with Claude Code