Skip to content

Action result: align every filter with the values the engine expects (success, failed, denied) - #2769

Merged
kryonsx merged 33 commits into
utmstack:v11from
kryonsx:codex/action-result-all-filters-20260928
Sep 29, 2026
Merged

kryonsx merged 33 commits into
utmstack:v11from
kryonsx:codex/action-result-all-filters-20260928

Conversation

@kryonsx

@kryonsx kryonsx commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Action result: align every filter with the values the engine expects

This pull request adjusts how every filter evaluates actionResult, so that it holds only the
words the EventProcessor recognizes: success, failed or denied, or no value when the record
states no final outcome.

Why

The EventProcessor's threat-intelligence analysis reads actionResult before it looks up an
event's addresses, domains and hosts in the threat-intelligence lists. It skips that lookup only
when the value is exactly failed, denied or blocked. Every other value, including an empty
one, is looked up, and a listed address raises "Known Malicious IP Detected".

The v11 filters did not match that:

  • 23 filters wrote failure (the word in the go-sdk wiki at the time). The engine does not know
    failure, so failed sign-ins and failed attempts from listed addresses would raise
    threat-intelligence alerts. Today's deployed filters write failed, which is skipped.
  • The Cisco ASA (51 steps) and Firepower (45 steps) filters wrote accepted on drops, failed
    logins, detections and completed sign-ins alike.
  • The Cisco switch filter wrote blocked.
  • Many records that state a block or a failure got no value at all, so the engine looked them up.

The rule every filter now follows

Value Meaning
success The action completed: sign-in succeeded, connection allowed and answered, request served, tunnel came up.
failed Attempted and did not complete: wrong password, locked account, error, timeout, failed negotiation.
denied A control refused it: firewall deny, drop or reject, access or policy denial, security block, quarantine.
no value The record states no final outcome: session start, detection without a verdict, notice.

Filters write denied, never blocked (the engine reads both the same way), so rules test one
word. Vendor words are translated, never copied. Where a change adds an address to a record, the
same record also gets its outcome, so no new record is looked up without one.

What changes, by filter

Data type Main changes
antivirus-bitdefender-gz failed for "still present" and failed tasks; deleted or disinfected malware and exploit-mitigation kills are denied; Exchange quarantine is denied; fixes TestBitdefenderActionResultRaw
antivirus-esmc-eset failed wording; refused console logins failed; blocked URLs, blocked files, cut downloads and cleaned or quarantined detections denied
antivirus-kaspersky failed wording; Security Center events that state a block are denied
antivirus-sentinel-one failed wording; failed mitigations failed, completed kills and quarantines denied; console user and role changes success
aws failed wording; IAM Identity Center sign-in results; console SwitchRole; Grafana calls use their own status instead of the HTTP code
azure failed wording; conditional-access refusals 53000 to 53003, 530032, 530034 and 530035 denied; web-firewall "Detected" no longer success; service-principal sign-ins; Defender logon and connection, Front Door and App Service access-restriction records get outcomes (address only with an outcome)
cisco-switch blocked becomes denied; SSH, login, 802.1X, MAB, privilege and access-list outcomes
crowdstrike failed wording; API calls answered 401 or 403 and detections whose prevention ran are denied
deceptive-bytes the outcome step now reads the CEF act key (any letter case); the attacker address is mapped only when the record carries its action
firewall-cisco-asa every accepted mapped per message id: drops denied, failed logins failed, sign-ins and tunnels success, Built/teardown records and notices no value; firewall denies and shuns denied
firewall-cisco-firepower the same mapping for the LINA messages; security events: Block actions and blocked intrusions denied, allowed connections success only when the responder answered; IPsec sequence numbers parse
firewall-fortigate-traffic failed wording; IPS and DoS resets and DNS-filter redirects denied; answered resets and timeouts success; security-profile pass success; SSL VPN tunnel-up success with the client address
firewall-fortiweb Return_403_error denied; traffic logs take the outcome from the HTTP return code; admin logins success or failed with the administrator's address
firewall-meraki failed wording; numeric flow patterns (1 deny, 0 allow, per Meraki's syslog documentation); site-to-site and 802.1X failures; content-filtering and switch packet-guard blocks; VPN connects; "Cellular connection up" no longer success
firewall-mikrotik failed wording; firewall lines from drop or allow rules; successful logins; login failures keep their address
firewall-paloalto failed wording; GlobalProtect pre-login and notice records no longer success; drop-packet and drop-icmp denied; IKE failures failed with the peer address
firewall-pfsense sshd, web console, sshguard and OpenVPN outcomes (filterlog unchanged)
firewall-sonicwall failed wording; step order success, failed, denied so a stated failure or block always wins; unmapped IKE failures, drops, geo-blocks and scan drops get their value; IKE and XAUTH successes and management requests success
firewall-sophos-xg failed wording; allowed web requests success whatever the site answered; web-content and web-firewall blocks denied; quoted text can no longer overwrite parsed fields
github failed wording; jobs, check runs, check suites, deployment and commit statuses get their outcome on completed deliveries
google failed wording; firewall rule logs denied or success with their addresses
ibm-aix failed wording; sshd, su and sudo outcomes; Oracle account lockout failed, privilege refusals denied; the Oracle return code is read wherever it sits
ibm-as400 failed wording; records with a numeric prefix now parse; CPIAD02 connections success with the client address
linux failed wording; USER_AUTH and USER_ACCT, account management, AppArmor and SELinux denials, journald copies of audit records, kernel firewall blocks, sshd and sudo lines (address only with an outcome)
macos authorization, directory authentication, privacy (TCC), sandbox, screen-lock and launchd outcomes
netflow answered TCP flows success, reset-only flows failed; SYN-only and SYN+ACK-only flows stay empty
o365 failed wording (including the final UserLoginFailed step); conditional-access refusals denied; Safe Links reads both key spellings; Teams, SharePoint and OneDrive operations without a status success; Power BI results; blocked mail threats denied; compliance alert detections no longer success
sophos-central failed wording; AMSI and IPS inbound blocks denied (the IPS sender becomes origin.ip); update, cleanup and restore failures failed
suricata drops and reject verdicts denied; a TCP flow is success only after a completed handshake and a refused one is failed; Suricata 8 accept; HTTP by answer code, completed TLS and file transfers success
syslog CEF and LEEF: failed wording; attempted actions and detections no longer success; session-end words no longer denied; the CEF outcome key wins; Trend Vision One results
utmstack backend login success and bad credentials
vmware-esxi failed wording; "Cannot login" and SSH failures failed, SSH sign-ins success
wineventlog failed wording for 4625, 4771 and failed 4776; Kerberos 4768/4769 by status, 4770 success; Security Audit Failure records; SQL Server logins; Defender attack-surface blocks denied; completed account and group changes

json-input and generic write no outcome and are unchanged.

Rules. The 15 rules that read failure now read failed; the Office 365 rules that read
blocked now read denied, and the password-spraying and guessing rules count failed or
denied so conditional-access refusals still count. Every rule of each data type matches the
same records before and after except where noted in the per-filter details.

Tests. Every plugins/alerts test that pinned a changed value is updated, with new cases for
each new outcome; several tests now reject any value other than the three words.

How it was tested

For each data type, in the local EventProcessor playground (engine main 8a3ade7, go-sdk v1.1.36):

  • the same real records, public vendor examples and fabricated records built from the vendors'
    documented formats (documentation address ranges only) were replayed through the old and the
    new filter;
  • a record-by-record comparison showed that only the intended records changed, only in the
    intended fields, and that no value outside success, failed and denied remains;
  • every rule of the data type was evaluated on both outputs.

On this branch as a whole:

  • go test ./... in plugins/alerts passes (on unchanged v11 TestBitdefenderActionResultRaw
    fails; the Bitdefender change fixes it);
  • a contract check over all 35 data types finds no actionResult value outside the three words;
  • each filter file is byte-identical to the one validated for its data type.

For reviewers to decide

  • Positive word. Filters keep success, which the v11 rules (154 of them) and stored data use;
    the engine treats every positive word the same. The go-sdk wiki was changed on 2026-09-28 to list
    succeeded and allowed. If the team prefers those words, that is a follow-up that also changes
    the rules. See also feat(plugins): define the action result values the engine recognizes threatwinds/go-sdk#29.
  • Cisco ASA message meanings. Please confirm 716001 (success), 719019 (kept denied) and
    113005 ("authentication Rejected" failed, "authorization Rejected" denied) against Cisco's
    syslog message guide; the official page could not be fetched during this work.
  • Firepower and ASA alignment. 302034, 316002, 113035/113038 and 716007 are failed in ASA and
    stay denied in Firepower; both are skipped by threat intelligence.
  • Left out, needing a decision: FortiWeb attack-log Alert and Erase, Google VPC flow logs, AWS
    VPC endpoint events, Azure web-firewall and access-restriction "Allowed" rows, CrowdStrike
    firewall match events (the action codes are undocumented), and the ESXi vpxuser password line.
  • Kept on purpose: Entra sign-in interrupts and Key Vault 401 challenges stay failed and
    denied; making them empty now would make the engine look them up.
  • Noted: Meraki ip_flow_end records now carry addresses without a value (their start record
    carries success); Deceptive Bytes detections without a block carry the attacker address without
    a value; syslog timeout is failed.

Found along the way, not changed here: the MikroTik ssh_brute_force_attempts rule compares
protocol with tcp while the filter writes TCP; the Office 365 safe_links_click_patterns
rule looks for ClickedSafeLink while real records use TIUrlClickData; the Palo Alto DNS
security rule does not list drop-packet.

The per-filter details (every class changed, every item left out, and the counts) are in the
comments below.

🤖 Generated with Claude Code

kryonsx and others added 30 commits September 28, 2026 14:22
Filter filters/antivirus/bitdefender_gz.yml (3.3.0 -> 3.3.1):
- Antimalware 'still present' and failed task-status records now write
  failed instead of failure.
- Antimalware and Advanced Threat Control records whose threat was
  deleted or disinfected now write denied instead of success: the
  product's control removed the threat, as with quarantined.
  'restored' keeps success.
- Exploit Mitigation act=kill (and killed) is denied, like other
  blocking actions.
- Exchange malware is denied when every detection in the msg array was
  quarantined or disinfected; arrays with any other action (for example
  ignore) stay empty.

Rules:
- quarantine_failure_detection.yml tests only failed (v3.0.1).
- high_severity_threat_detection.yml description names the new values
  (v3.0.1).

Tests (plugins/alerts):
- Bitdefender fixtures and the outcome predicate use failed; deleted and
  disinfected expect denied; new cases for kill, restored, Exchange
  quarantine, disinfect and mixed arrays, and the still present rule.
- The status-key case now expects the vendor names with underscores
  (log.avc_status and friends) next to the names the rules read;
  go-sdk v1.1.36 keeps underscores, so the old absence check failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 1417d7843d4f2867c9ebc3274378e1ed1632ed5d)
Filter filters/antivirus/esmc-eset.yml (3.2.0 -> 3.2.1):
- Audit failure results and a stated action_error now write failed
  instead of failure; the console-abuse and quarantine-failure markers
  test failed.
- A console "Login attempt" refused with "Access denied" writes failed;
  other Audit records with "Access denied" write denied.
- "Blocked URL" and "Blocked and cleaned" are denied like "blocked".
- Handled Threat_Event records that ESET cut (connection terminated) or
  removed or quarantined (cleaned, cleaned by deleting, deleted,
  quarantined, contained infected files) write denied. Retained,
  unhandled records and pure detections stay empty; action_error still
  wins with failed.

Rules:
- eset_console_abuse.yml and eset_quarantine_failures.yml test failed
  (v1.0.1).

Tests (plugins/alerts/testdata/eset_raw.json):
- Pinned failure values are failed; the documented deletion and the
  heuristic remediation cases expect denied; 15 new cases cover each
  outcome class, the negatives, and the exploit and PowerShell rules
  that now also see cleaned detections as blocked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 49ef4ac0d63b5835f6dab6419909940d7324b15f)
…ionResult

- CEF act fail, failed or failure now writes failed instead of failure.
- Security Center events that state a block in the event type (object
  blocked, virus found and blocked, application launch denied, in native
  syslog or KasperskyLab CEF) now write denied; detection-only, task,
  audit and health events stay empty.
- lolbins_abuse reads failed instead of failure (v1.1.1).
- Tests: failed outcome, new block-type cases, lolbins positive and
  negative with the new word; predicates reject failure and blocked.
- Filter version v3.2.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9b474f18de3fa4541742033e781281e11a519aa9)
- Failed mitigation or operation status now writes failed instead of failure.
- CEF:0 mitigation records (they carry fileHash) state their result only in
  the event name: "<action> failed" writes failed; "Kill performed
  successfully" and "Quarantine performed successfully" write denied (the
  threat was stopped). "Kill pending to reboot" and detections stay empty.
- Console account and role changes (activityType 23 user added, 25 user
  deleted, 37 role assigned) are logged once done and write success.
- Filter version 3.2.1. The outcome contract test checks the three words and
  covers the new classes; no rule reads actionResult for this data type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit abf35b7c39fa51f0dacc1dbdf99965d32b6a02f1)
- Vocabulary: failed API calls, failed console sign-ins (ConsoleLogin,
  GetSigninToken, CheckMfa) now write "failed" instead of "failure".
- IAM Identity Center: UserAuthentication Success is "success";
  CredentialVerification or UserAuthentication Failure is "failed";
  CredentialChallenge and a successful CredentialVerification stay empty.
- Console SwitchRole: Success is "success", Failure is "failed".
- Amazon Managed Grafana: a 2xx errorCode (Grafana puts the HTTP status
  there) is no longer an error; result.statusType decides success or failed.
- Grafana workspace events with punctuated names (login-auth.sso) are now
  recognised as CloudTrail, so they get their stated outcome.
- Filter version 1.1.2; the two correlation markers and the two rules that
  read "failure" (root console brute force, malformed trust-policy
  updates) now read "failed".
- Tests: aws_action_result_test.go and testdata/aws_raw.json pin "failed";
  new outcome cases and two contract fixtures pin the new classes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit b1988e3398b66fd8945215b06c181038f917fd2a)
- Write failed instead of failure (failed sign-ins, failure words, HTTP 4xx/5xx,
  Event Grid failures); the password-spray marker and rule now read failed.
- Conditional-access refusals 53000, 53001 and 53002 are denied, like 53003.
- Application Gateway WAF "Detected" rows no longer get success (a detection,
  not a completed request); "Allowed" is unchanged.
- MicrosoftServicePrincipalSignInLogs uses the sign-in outcome rules.
- New classes with outcome and address together: Defender DeviceLogonEvents
  (LogonSuccess/LogonFailed), DeviceNetworkEvents (ConnectionSuccess,
  InboundConnectionAccepted, ConnectionFailed; sides by direction), Front Door
  access log (HTTP status), Front Door WAF blocks in prevention mode (denied),
  App Service access-restriction denials (denied, client from CIp).
- Defender CloudAuditEvents with a Kubernetes audit event route the response
  code to statusCode and use the kube-audit outcome rule (no address mapped).
- Tests: failure -> failed pins, WAF Detected pin, new cases for each class.
- Filter version 2.3.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e2cb359a7b5ff0774a5199657d8e3fd9c10ad163)
The threat-intelligence stage skips indicator lookups only for failed and
denied, so the switch filter now writes exactly success, failed or denied,
or nothing when the message states no final outcome (filter 3.2.0).

- SW_DAI DHCP_SNOOPING_DENY, INVALID_ARP, ACL_DENY: blocked becomes denied.
- SSH2_UNEXPECTED_MSG, and SSH_CLOSE/SSH2_CLOSE for user '': failed. These
  already carry the client address, so a listed scanner raised a false
  "Known Malicious IP" alert.
- SEC_LOGIN/SECLOGIN LOGIN_SUCCESS/LOGIN_FAILED, SSH2_USERAUTH and
  SSH2_SESSION Succeeded/Failed, SYS PRIV_AUTH_PASS/FAIL, DMI AUTH_PASSED/
  AUTHENTICATION_FAILED, MAB and AUTHMGR SUCCESS/FAIL: success or failed.
- TCP BADAUTH: failed. DHCP snooping drops, port-security violations, BPDU
  guard and login quiet mode: denied.
- Access lists: IOS XE forwarding-plane (FMANFP) and standard-list
  (IPACCESSLOGS) lines now get success/denied; IOS XR deny/permit lines are
  recognized from log.msg. DOT1X and classic access lists are unchanged.
- No address is added or moved; no rule reads actionResult.
- Tests: SW_DAI expectations move to denied, 35 fabricated lines and 40
  cases added, new TestCiscoSwitchActionResultWords; expected.json
  re-recorded with EventProcessor 8a3ade7. Audit note D-10 marked done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit c0cbd89eb9d1f48c6249b16c0c39a0afc92fcca3)
- Failed console sign-ins (Success false) and API calls answered 4xx/5xx
  now write "failed" instead of "failure".
- API calls answered 401 or 403 now write "denied" (the request was
  refused for missing or insufficient rights).
- DetectionSummaryEvent and EppDetectionSummaryEvent whose prevention
  really ran (description starts with "Prevention", a block, kill or
  quarantine flag is set, and neither PolicyDisabled nor KillActionFailed
  is set) now write "denied". "Would have been blocked" detections and
  plain detections keep no value.
- Filter version 1.3.1.
- crowdstrike_action_result_test.go and its test data: expect "failed",
  add 401/403 and prevention detection cases, and check that every value
  is success, failed or denied.

No rule reads actionResult for this data type, so no rule changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 2f4d27ddc2351668cb8ae1d3baf85ee860f54d65)
…esult

- The denied step now also reads the CEF device action (act), which the
  product's CEF records use instead of action, and matches blocked, block,
  prevented, denied or deny in any letter case.
- CEF src becomes origin.ip when it is a usable address and the record also
  carries its action, so an address never reaches threat intelligence without
  its outcome (a record whose action key could not be read keeps src under log).
- Filter version 3.0.5; the audit note describes both steps. New test checks
  the outcome and address conditions with the SDK CEL; no rule reads
  actionResult for this data type.

No real Deceptive Bytes record was available; validated with fabricated CEF
fixtures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5a875f90efdb536ee5e8f7817f211f45516d8e23)
The threat-intelligence lookup skips only failed, denied and blocked. The ASA filter
wrote the non-standard word accepted in 51 steps and left firewall drops empty, so
refused traffic was looked up. Each message id is now mapped on its own meaning in
Cisco's ASA syslog guide (filter version 3.2.0):

- success: completed sign-ins and sessions 113004, 113008, 113012, 113039, 716001,
  716038, 719020, 719022, 611310, 713253; 109201-109213 when the text says Succeeded.
- failed: rejected sign-ins 113005 (authentication), 113015, 113016, 113017, 605004,
  716039, 719023, 719024, 611311 (were denied); errors 109102, 109103, 113035, 113038,
  716007, 302034, 316002; 109201-109213 when the text says Failed.
- denied: new for 106001, 106017, 113042 and 733102; was accepted on 402114-402120,
  209003 and 716006. 113005 authorization and 719019 stay denied.
- no value: 106102/106103 permitted hits (the captured verb is deleted), all Built
  and teardown records, the H.323 pre-allocation notices and informational notices
  (109101, 113009, 113019, 201003, 603109, 609002, 611307, 611309, 611314, 611315,
  617100, 716002, 733101).

No rule reads actionResult for this data type. The ASA Go test and its expected
fixture values follow the new mapping, and a new test runs every actionResult step
in filter order on Cisco-format message texts. The audit notes record the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 14938f71387e863eb9aab170a30a125a394bcbcb)
…n actionResult

The filter wrote 'accepted' in 45 steps and no outcome on the 4300xx
security events. Map every value per message id (filter version 3.2.0):

- 430002/430003 Block, Block with reset, Interactive Block (with reset)
  and 430001 InlineResult Block, Blocked or Dropped: denied.
- 430003 Allow, Trust or Fastpath: success only when responder bytes or
  packets are above zero; 430002 starts and unanswered ends stay empty.
- denied: IPsec drops 402114-402120, 209003, 716006, and (new) 106001,
  106017, 113042, 733102. The 402114/402116/402118/402119/402120 grok
  now reads a hexadecimal sequence number, so these keep their addresses.
- failed: 109102, 109103, UAUTH 109201-109213 "Failed", and failed
  sign-ins that said denied: 113005 authentication Rejected,
  113015/113017, 113016, 605004, 716039, 719023, 719024, 611311.
- success: 113004, 113008, 113012, 113039, 716001, 716038,
  719020/719022, 611310, 713253, UAUTH 109201-109213 "Succeeded".
- no value: Built and teardown records, H.323 pre-allocation notices,
  106102/106103 "permitted" (the verb is deleted), 109101, 113009,
  113019, 201003, 609002, 611307, 611309, 611314, 611315, 716002, 733101.

Tests: cisco_firepower_filter_test.go pins the new values, checks the
106102 delete and the UAUTH text steps, and fails on any actionResult
outside success, failed and denied; 21 fabricated lines added to
testdata/cisco-firepower, expected.json recorded from the playground.
No rule reads actionResult for this data type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit b7a09b2b237d285c797c3e01516968f910cc58a8)
The FortiGate filter now writes only the three outcome words the
threat-intelligence gate reads, and gives an outcome to records that state
one but had none.

- Failed SSL VPN and admin sign-ins and failed connections (log id 11):
  failure becomes failed.
- IPS and DoS sensor reset, reset_client, reset_server, drop_session and
  clear_session: denied.
- DNS filter redirect to the block portal: denied.
- Traffic client-rst, server-rst and timeout with received packets: success.
  Without received packets they keep no outcome.
- Security-profile pass, passthrough and permit: success.
- SSL VPN tunnel-up: success, and the client address (remip) becomes
  origin.ip as it already does for SSL VPN login failures.
- The denied step stays last, so a security-profile block still wins.
- Filter version 3.4.0.

Tests: fortigate_raw.json pins failed instead of failure in four cases and
adds twelve cases for the new outcomes. No rule reads failure; the IPS
critical rule now also counts critical and high IPS resets, which are blocks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 53d1a4e426540b27e0cc62aea3896cb4b9ec65cc)
The threat-intelligence gate skips indicator lookups only for failed and
denied, so the FortiWeb filter now gives an outcome to every record class
that states one, using only success, failed or denied.

- Attack logs: Return_403_error (the request was answered with 403 and not
  forwarded) is denied. Alert and Erase keep no value.
- Traffic logs: the HTTP return code decides (http_retcode, or HTTP_retcode
  on older firmware, quoted or not): 2xx success, 401 and 403 denied, other
  4xx and 5xx failed, 1xx, 3xx or no code no value.
- Event logs by an administrator (user not system or daemon): status
  success is success, failure is failed. Daemon and system notices get no
  value.
- On those administrator records the client address inside msg
  ("from GUI(<ip>)", "from GUI->HTTPS(<ip>)") becomes origin.ip when it is
  a valid address; the last such block wins, because a failed login's
  user name comes first.
- The blocking-action step stays last. Filter version 2.4.0.

Tests: fortiweb_raw.json adds 19 raw cases for the new outcomes and the
negative controls. No FortiWeb rule reads actionResult.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 035325cc0a1e7ede46d70282005b58ade3f18d5c)
The threat-intelligence check skips indicator lookups only for failed and
denied, so the Meraki filter now writes only success, failed or denied, or
no value when a record states no final outcome.

- AnyConnect authentication failures write failed instead of failure.
- RADIUS-form AnyConnect authentication lines now keep the peer address
  (the pattern skipped them because the RADIUS server is named first).
- Numeric inbound rule patterns follow Meraki's documented meaning:
  "1 ..." is denied and "0 ..." is success (native and legacy flows).
- Pre-MX 15.12 site-to-site negotiation failures write failed.
- "Cellular connection up" is a health notice and no longer claims
  success (legacy and native steps). Site-to-site tunnels that come up
  keep success.
- ip_flow_start, ip_flow_end and bridge_anyconnect_client_vpn_firewall
  (MX18.101 and newer) are now parsed. ip_flow_start writes success as the
  fallback for a forwarded flow, because ip_flow_end carries no byte
  counts; ip_flow_end keeps no value; the bridge group follows its
  allow/deny pattern.
- client_vpn_connect and anyconnect_vpn_connect write success and map
  the user, the remote address and the assigned tunnel address.
- 802.1X EAP success and failure write success and failed; content
  filtering blocks and switch ARP/RA/DHCP packet guards write denied.
- The vendor "blocked" step stays the last actionResult step.

No rule reads actionResult for this data type. The contract fixtures
pin the new values, and a new test checks the filter writes only the
three words with the blocked step last.

Filter version 3.2.0 to 3.3.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d83da6ca377526ba177db0439c37d14aae19f2fc)
- Login failures now write "failed" instead of "failure", the word the
  threat-intelligence check skips.
- Firewall lines: the rule's log prefix decides the outcome. action:drop,
  action:reject, action:tarpit or a block word in the prefix (drop, deny,
  block, reject, tarpit, as a whole word) gives "denied"; action:accept or
  an allow word (accept, allow, permit) gives "success". A block word wins.
  RouterOS logs nothing after an allowed packet, so the allow prefix is the
  only proof of the outcome. Lines without such a prefix keep no value.
- Successful logins ("user X logged in from ADDRESS via SERVICE") get
  "success", origin.ip, origin.user and log.service. Logouts stay unchanged.
- Login failures with an empty user name or a name with spaces keep their
  address, service and outcome.
- A /system logging prefix between the topics and a login message
  ("mk_R1: login failure ...") is split into log.loggingPrefix, so these
  lines get their address and outcome too.
- Rule "RouterOS Multiple Authentication Failures" counts "failed" in its
  follow-up search (v1.1.1).
- Filter version 3.2.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e77027679d7891a35695e921ed843252500dadc4)
The threat-intelligence gate reads only success, failed and denied (and
the older blocked). The Palo Alto filter wrote "failure", so failed
sign-ins were looked up as if nothing had been refused, and it wrote
"success" on GlobalProtect records that do not report a finished sign-in.

- Vocabulary: CONFIG result Failed, SYSTEM auth-fail, GLOBALPROTECT
  status failure and TRAFFIC session end resources-unavailable now write
  "failed" instead of "failure". The brute-force marker
  log.correlationCandidate.paloalto.authFailure now checks "failed".
- GlobalProtect portal-prelogin, gateway-prelogin, gateway-tunnel-latency,
  gateway-agent-msg and gateway-tunnel-notify no longer get "success": a
  pre-login answer and a tunnel notice are not a completed sign-in. The
  portal-auth and gateway-auth records still carry the sign-in result.
- THREAT and TRAFFIC actions drop-packet and drop-icmp are blocking
  actions and now write "denied", like drop and reset.
- SYSTEM vpn IKE negotiation failures (ike-*-fail, ikev2-*-fail) write
  "failed". When the object column holds the peer as address[port], the
  peer address becomes origin.ip and the port log.paPeerPort.
- Filter version 3.1.3.
- Rule panos_admin_brute_force (v2.0.1) now tests actionResult "failed".
- plugins/alerts: the Palo Alto raw fixtures pin "failed" instead of
  "failure", and 14 new fixtures cover the pre-login and notice records,
  drop-packet, drop-icmp and IKE failures and successes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 190dfe33aa737403a2bac055802b997668d0ed68)
filterlog pass and block already write success and denied; they are
unchanged. Sign-in lines outside filterlog had no outcome and no address.
Each of them now gets both from the same line, never an address alone:

- sshd "Accepted <method> for USER from ADDRESS port N" gives "success";
  "Failed <method> for [invalid user ]USER from ADDRESS port N" gives
  "failed". Both set origin.ip, origin.port, origin.user, log.authEvent
  and log.authMethod.
- webConfigurator (php-fpm) "Successful login for user 'X' from: ADDRESS"
  gives "success"; "webConfigurator authentication error for user 'X'
  from: ADDRESS" gives "failed", with origin.ip and origin.user. These
  php-fpm lines get their own header step, because the program name has
  a hyphen that the general step does not read.
- sshguard 'Blocking "ADDRESS/32" for N secs' gives "denied" with the
  blocked address in origin.ip. sshguard "Attack from" lines are
  detections without an outcome and stay without an address.
- OpenVPN "user 'X' authenticated" gives "success" and "user 'X' could
  not authenticate." gives "failed", with origin.user (the line names no
  address).
- Filter version 3.2.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7f7223a44cf142ac724f9461520f8bbb8cb49249)
- Login, authentication and IKE failures now write "failed" instead of
  "failure", the word the threat-intelligence check skips.
- Order the steps success, then failed, then denied, and drop the
  "first value wins" guards, so a stated failure or block overrides a
  broad success (fw_action forward or mgmt). The fw_action drop step
  moves into the final denied step.
- failed: add the unmapped IKE negotiation failures (401, 403, 405, 414,
  545, 616, 914, 916, 919, 971, 981, 983), 1222 invalid SNMPv3 user and
  1226 HTTPS handshake failure. Retransmit notices 931 and 972 stay empty.
- denied: add 27 land attack, 267 Xmas tree and 1475 country block,
  809 gateway anti-virus alerts that end "blocked.", scan detections
  whose note starts "Pkt is dropped" (82, 83, 177), and older SonicOS
  lines without fw_action for 14 and 38.
- success: fw_action mgmt (526 management request allowed), 89 and 357
  IKE negotiation complete, 139 XAUTH succeeded, 1794 allowed login.
- Rule "SonicWall Management Interface Authentication Failures" reads
  "failed" in its condition and its follow-up search (v2.2.1).
- SonicWall contract test fixtures expect "failed" and cover the new
  ordering, the management success, the dropped scan, an IKE failure and
  a retransmit notice that keeps no outcome.
- Filter version 4.2.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit edb0e00d70b9ae3cc8e7306aebd5b7501240b8ff)
- Write failed instead of failure for status Failed/Error records
  (authentication, admin, IPSec) and for other 4xx/5xx web answers.
- Content Filtering Allowed is success whatever HTTP status the site
  returned: the firewall let the request through and the site answered.
- Content Filtering SSL Error (server did not complete the TLS
  handshake) is failed.
- Web Content Policy records with action Deny/Block are denied.
- WAF records that name a block reason (reason present and not "-")
  are denied; this replaces a reason list that never matched real
  values. WAF requests passed to the server keep the server answer
  (401/403 denied, other 4xx/5xx failed).
- Remove the camelCase names the filter builds itself right after the
  KV step, so key=value text inside quoted values (user agent, mail
  subject) can no longer set statusCode, subType, origin.host,
  target.domain or deviceTime.
- Rules sophos_password_guessing_on_administrator_account and
  sophos_xg_vpn_auth_failures, and the history markers in the filter,
  test failed instead of failure.
- Contract fixtures: pinned values updated; new fixtures for each class.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7b400917dee9c211207b4fc390b2e071b629192f)
- workflow_run conclusions failure, timed_out and startup_failure now
  write failed instead of failure.
- workflow_job, check_run and check_suite conclusions now set the outcome
  too (success, or failed for failure, timed_out, startup_failure), and
  only on the completed delivery: rerequested and in_progress deliveries
  can repeat an earlier conclusion and set none.
- deployment_status and commit status state success writes success;
  failure and error write failed; pending, queued, in_progress and
  inactive set none.
- Tests: github action result cases use failed, the workflow job case is
  mapped, 22 new fabricated cases, and expected values are limited to
  success, failed, denied or empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d1516dffdc07e188b8bb04feb3c7660be9ebf686)
The threat-intelligence gate in the EventProcessor skips indicator
lookups only for failed, denied and blocked. The GCP filter wrote
"failure", which the gate does not recognize.

- Cloud Audit Logs with a status code other than 0, 7 and 16: failed
  (was failure).
- Workspace loginFailure and SamlLoginFailed: failed (was failure).
- Cloud DNS error response codes other than REFUSED: failed (was
  failure).
- HTTP request logs with 4xx or 5xx other than 401 and 403: failed
  (was failure).
- Firewall Rules Logging: disposition DENIED gives denied, ALLOWED
  gives success (the source writes nothing after the allow decision,
  so the allow record is the fallback). The connection addresses,
  ports, protocol, rule reference and direction are now kept for these
  records only; before, the final delete removed them. VPC Flow Logs
  are unchanged.
- Filter version 2.3.1.
- google_action_result_test.go and its test data test failed instead
  of failure and add firewall and VPC flow cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0cd2aa6bde8511cd27963dc9af24e1cc5411c254)
- Oracle audit: non-zero RETURNCODE writes failed instead of failure;
  privilege refusals 1031, 1035 and 1045 write denied. RETURNCODE is read
  wherever it sits, so TERMINAL or COMMENT$TEXT no longer hide it.
- sshd Accepted/Failed lines (also sshd-session) write success/failed and
  keep the account in origin.user and the method in log.authMethod.
- AIX "ssh: failed login attempt" and "Login restricted ... 3004-303"
  (account locked) write failed.
- su "from X to Y" writes success, "BAD SU" writes failed.
- sudo command lines write success, "incorrect password attempts" failed,
  "NOT in sudoers", "NOT authorized on host" and "command not allowed" denied.
- Steps run success, then failed, then denied. Version 3.2.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit da30739aaf9ebaf538123ffc2a9c3c373351ead3)
- CPF2234 (password not correct) now writes failed instead of failure.
- CPIAD02 (user from client connected to server) writes success and the
  client address, like CPIAD09.
- Collector records with digits before the JSON object (observed 48{...})
  are parsed like unprefixed records, so their CPIAD09 connections keep
  success and the client address; text that is not a whole JSON object
  still falls back to log.message.
- Filter version 4.1.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 4d0aaca757a4e191a810b49a3d401f239f96268e)
The threat-intelligence gate skips lookups only for failed, denied and
blocked. The filter wrote failure, so failed SSH logins that now carry
the peer address would be looked up.

- Vocabulary: failed systemd jobs, failed audit SYSCALLs and failed
  USER_LOGIN records now write failed instead of failure.
- Native audit USER_AUTH (success/failed) and USER_ACCT (success/denied)
  get an outcome and their valid peer address in origin.ip.
- Account and group management records (ADD_USER, DEL_USER, ADD_GROUP,
  DEL_GROUP, USER_MGMT, GRP_MGMT, USER_CHAUTHTOK, ACCT_LOCK, ACCT_UNLOCK)
  get success or failed; standalone AVC records get denied for AppArmor
  DENIED or SELinux denied with permissive=0.
- Journald copies of audit records (_TRANSPORT=audit) get the same
  outcomes as the native records (no address promoted); both key
  spellings are read.
- Kernel netfilter lines whose prefix names a refusal (UFW BLOCK, CSF
  Blocked, firewalld REJECT, DROP and similar) get denied plus addresses,
  ports and protocol; ALLOW, AUDIT and unknown prefixes stay empty.
- sshd, PAM, unix_chkpwd and sudo lines in journald get their outcome;
  the peer becomes origin.ip only on lines that state one.
- Tests: expect failed instead of failure; add cases for every new class.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 4d0681618bfd65176666614eef767132f8b1a9b9)
The macOS filter wrote no outcome, although several unified-log classes
state one. It now writes success, failed or denied for those classes,
keyed on the subsystem or process and the start of the message, never
on the log level. macOS records carry no remote address, so threat
intelligence is not affected.

- success: authd "Succeeded authorizing right", TCC AUTHREQ_RESULT
  with authValue=2.
- failed: opendirectoryd "Authentication failed for" and "Failed
  SecureToken authentication for", authd "Failed to authorize right",
  loginwindow screen-lock "authFailWithMessage" with INCORRECT password.
- denied: TCC authValue=0 and "returning denied", kernel and
  com.apple.sandbox.reporting "Sandbox:" / "System Policy:" deny reports
  (including "N duplicate report(s) for" and the legacy /kernel form),
  kernel "sandboxd rejected approval request", launchd "denied lookup".
- Everything else stays empty (TCC authValue=3, sandbox allow reports,
  report fragments, XProtect detections). Filter version 3.2.0.

Tests: macos_raw.json adds 24 raw cases (72 subtests across the three
filter orders) for each class and the negative controls. No macOS rule
reads actionResult.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit da9c163ec78e3b5d3e2e01a599e88fa1645aa163)
The filter wrote no outcome, so every flow was looked up by threat
intelligence, including unanswered and refused attempts.

- TCP flows whose cumulative flags include ACK are success, except flows
  whose only flags are SYN and ACK (with or without URG, ECE or CWR):
  18, 50, 82, 114, 146, 178, 210, 242.
- TCP flows with a reset and no SYN, FIN or PSH are failed.
- SYN-only flows, flows without TCP flags (UDP, ICMP) and NetFlow v5
  flows (no protocol or flags parsed today) keep no value.
- Filter version 3.1.2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit fdcace813388a52b4353c7fda2873ed8669a5eeb)
The threat-intelligence gate skips indicator lookups only for failed,
denied and blocked; it looks up every other value. The filter wrote
"failure", so failed sign-ins were looked up and a listed address raised
"Known Malicious IP Detected" for a sign-in that did not work.

Filter (version 1.4.2):
- Every failure step now writes failed (ResultStatus Failure/Failed,
  Exchange admin False, sign-in error codes, Planner failures, and the
  final UserLoginFailed step).
- Sign-ins refused by conditional access or security defaults (Entra
  53000, 53001, 53002, 53003, 530032, 530034, 530035) are denied, after
  the UserLoginFailed step.
- Safe Links reads both UrlClickAction and URLClickAction (2 denied,
  4 and 5 success).
- Mail threat records with DeliveryAction Blocked are denied.
- Power BI and Fabric records use IsSuccess (true success, false failed).
- Teams TeamsSessionStarted and completed SharePoint/OneDrive operations
  without ResultStatus are success; detections, DLP matches, blocks,
  denials and partial operations stay without a value.
- Yammer ResultStatus TRUE is success; compliance cmdlet Error is failed.
- Compliance alert detections (AlertTriggered, AlertEntityGenerated) no
  longer claim success.

Rules: the six Office 365 rules that also accepted "failure" or
"blocked" now read one word per outcome. The two sign-in rules read
failed or denied so conditional-access refusals still count.

Tests: o365 action-result, rule and awareness fixtures pin failed
instead of failure, with new cases for every changed class.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 189579c43027673233d39f3a535eee1034c43d33)
…sult

- ZTNA and endpoint authentication failures now write failed instead of
  failure.
- CoreAmsiBlocked ("AMSI Protection blocked a threat") writes denied.
- IpsInboundDetection whose name says "traffic blocked" writes denied, and its
  remote sender (ips_threat_data.remoteIp, when it is a usable address) is
  mapped to origin.ip on those records only, so the address never reaches
  threat intelligence without its outcome.
- Stated failures write failed: UpdateFailure, CoreCleanFailed ("Manual malware
  cleanup required"), CoreRestoreFailed, and NotProtected "Failed to protect".
- Contract fixtures: failure -> failed, CoreCleanFailed now failed, eight new
  outcome cases. No rule reads actionResult for this data type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit a57dcc1a824a71a97ef40d1af16256363c500b19)
- drop records (IPS) are denied.
- An alert whose verdict lists a reject (reject, reject-target up to
  Suricata 8, reject_target from 9) is denied.
- A TCP flow is success only when the server sent SYN-ACK and the client
  sent ACK; a SYN answered by a reset is failed; other TCP flows without
  a completed handshake stay empty.
- Suricata 8 firewall mode "accept" counts like "pass" for flows and
  alert verdicts.
- http and HTTP fileinfo follow the server's answer: 2xx success, 401 and
  403 denied, other 4xx and 5xx failed, 3xx empty.
- fileinfo seen whole (state CLOSED) without HTTP is success; tls with a
  server certificate, JA3S, resumed session or negotiated version is
  success.
- Steps run success, then failed, then denied, and read log.fileinfo
  before it is renamed. Filter version 1.1.1.
- suricata_action_result_test checks failed instead of failure; its
  cases grow from 18 to 43.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 141c42455891cc6c729e0908a4500b841d46ff13)
The CEF and LEEF stages of syslog-cef-leef.yml now write only the three
outcome words the threat-intelligence gate recognizes.

- CEF and LEEF act lists: success only for allow, accept, permit,
  permitted, pass, success, ok; failed for error, fail, failed, failure,
  timeout and the failed-authentication words (was failure, or denied for
  failed); denied for block, deny, drop, reject, refuse, reset, IDS:Reset,
  clear_session, prevent, quarantine, invalid and their forms.
- Attempt and detection words (login, logon, connect, start, open,
  Alerted, Detected) and session ends (closed, teardown, terminated) no
  longer get a value. invalid stays denied: firewalls use it for a
  dropped packet, and an empty value is looked up.
- The outcome key (success, failure, /Success, /Failure) is read last in
  both stages so it wins over act.
- Trend Vision One: list-form act such as ['Quarantine'] or ['Block']
  gives denied; Account Audit Log (900003) Result cn1 1 gives success and
  0 gives failed, per Trend's CEF account audit log mapping.
- Version 1.0.0 to 1.1.0. No rule or alerts test reads these values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 6e4cdf33801e42da92ecb9ceb09815078c812bab)
kryonsx and others added 3 commits September 28, 2026 14:22
The UTMStack filter wrote no outcome. The backend's panel sign-in lines
state one, so they now carry it.

- "UserJWTController.authorize: Login successfully completed for user ..."
  is success, and args.username (the account that signed in) becomes
  origin.user unless it is empty or "anonymous".
- "UserJWTController.authorize: Bad credentials" is failed. Its
  args.username is "anonymous", so no user is mapped.
- args.remoteAddr is not promoted: the backend reads it from the request,
  which behind the panel's own nginx is the proxy's address, not the
  client's (see LoginAttemptService.getClientIP).
- Every other line keeps no value. Filter version 1.0.2.

Tests: filter-contracts/core.json adds seven raw cases (two outcomes,
anonymous success, sign-in attempt, TFA challenge, another controller's
Bad credentials, and the success text inside another message). No rule
reads the utmstack data type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 8757eb85396dc425b1efc38d4637ff114a5c7035)
- "authentication failed" and "authentication of user ... failed" write
  failed instead of failure.
- hostd "Event <n> : Cannot login [user ]<user>@<address>" writes failed
  with origin.user and origin.ip; "...: no permission" writes denied.
- sshd "Accepted <method> for <user> from <address> port <n>" writes
  success with origin.user, origin.ip and log.authMethod.
- sshd "error: PAM: Authentication failure for <user> from <address>"
  writes failed with origin.user and origin.ip.
- One line per attempt carries the outcome: password checks, pam_unix and
  the vobd/hostd SSH notices keep no value and no address.
- Test: checks failed instead of failure, models log.process, adds 11
  cases for the new classes and their negatives. Version 3.2.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 862a86bdaccc2bb0e9dd128243c757eee43ef8bc)
The threat-intelligence gate skips indicator lookups only for failed,
denied and blocked. The filter wrote "failure", so failed Windows
sign-ins from listed addresses would raise "Known Malicious IP Detected".

- 4625, 4771 and 4776 with a non-zero Status: failure -> failed.
- 4768/4769: success when Status is 0, failed otherwise.
- 4770 (service ticket renewed): success.
- SQL Server 18453/18454: success; 18456: failed (provider MSSQL*).
- Defender 1121 (attack surface reduction block): denied.
- Security Audit Failure records with no value yet: denied for
  4656, 4661, 4663, 4673, 4674, 4675, 4888 and 5031, failed for the rest.
- 4729, 4731, 4733, 4735, 4737, 4738, 4741, 4742, 4743, 4781 logged as
  Audit Success: success.
- 4648 and 4740 stay empty. Filter version 3.2.1.
- Tests: the Windows action-result predicates, contract and raw fixtures
  now pin failed; new cases cover each added class. No rule reads the
  Windows actionResult.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f8ff42c50fb30463fbb6b6d20ce9cd67dd30812b)
@kryonsx

kryonsx commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

Per-filter details (1 of 4): antivirus-bitdefender-gz, antivirus-esmc-eset, antivirus-kaspersky, antivirus-sentinel-one, aws, azure, cisco-switch, crowdstrike, deceptive-bytes, firewall-cisco-asa

antivirus-bitdefender-gz

Summary

The Bitdefender GravityZone filter (filters/antivirus/bitdefender_gz.yml) now writes only
success, failed or denied to actionResult, or leaves it empty when the record states no
final outcome. These are the words the EventProcessor threat-intelligence step recognizes: it skips
indicator lookups for failed and denied and looks up every other value, including an empty one.

Changes per class

Class Before After Why
Antimalware and Advanced Threat Control, action still present failure failed Only the standard word.
Task status that failed (success flag 0 or false, or an error code without a success flag) failure failed Only the standard word.
Antimalware deleted, Advanced Threat Control Disinfected success denied The product's control removed the threat, the same as quarantined, which was already denied.
Antimalware restored success success Unchanged: the restore completed.
Exploit Mitigation act=kill (and killed) empty denied The product killed the process to stop the exploit, like other blocking actions.
Exchange malware whose detections were all quarantined or disinfected empty denied The action sits in the msg array (actionTaken). Arrays with any other action, for example ignore, stay empty.

Filter version 3.3.0 becomes 3.3.1.

Rules

  • quarantine_failure_detection.yml tests only failed (it read failure and failed). v3.0.1.
  • high_severity_threat_detection.yml: the description now names the new values. v3.0.1. Its
    condition does not read actionResult.
  • suspicious_exclusions_added.yml reads success on task status, which did not change. It is not
    edited.

Tests (plugins/alerts)

  • The Bitdefender fixtures and the outcome check use failed. The deleted and Disinfected
    cases now expect denied.
  • New cases cover:
    • Exploit Mitigation kill;
    • restored;
    • Advanced Threat Control disinfected;
    • Exchange malware: quarantined, disinfected, both, and mixed with ignore;
    • the still present rule, with a positive and a negative for quarantine_failure_detection.
  • TestBitdefenderActionResultRaw failed on v11 with "unexpected log.avc_status". go-sdk v1.1.36
    keeps underscores in field names, so the key/value step now stores log.avc_status,
    log.malware_status and log.pu_status. The filter's own extraction still writes
    log.avcstatus, log.malwarestatus and log.pustatus, which the rules read. The case now
    expects both spellings; underscores are allowed and are not stripped. The playground shows the
    same output.

Validation

  • The local EventProcessor playground ran on the latest engine build with go-sdk v1.1.36. It
    replayed the base and this change over 202 real records from five servers, 51 public vendor
    examples and 19 made-up examples. Every input produced an output and none had errors.
  • Real records: 62 records changed, and each one was an intended change.
    • 13 deleted and 9 disinfected detections went from success to denied.
    • 29 still present records and 11 failed tasks went from failure to failed.
    • No other field changed on any record.
    • The number of records without a value stayed at 15.
  • Public examples (Elastic, Sekoia and Wazuh integrations): 5 records changed.
    • One Exploit Mitigation kill, one quarantined Exchange detection and two deleted detections
      became denied.
    • One still present record became failed.
  • Made-up examples: all 19 matched their expected before and after values. They follow Bitdefender's
    syslog and push-event examples and use documentation addresses and example.test names.
  • Across all outputs, the only values left are success, failed and denied.
  • All 21 Bitdefender rules were checked against the base and the new output with the same rule
    evaluator the engine uses. Each rule matched exactly the same records before and after.
    • quarantine_failure_detection matched only the 31 still present records.
    • high_severity_threat_detection matched the same 68 records.
  • go test ./... in plugins/alerts passes. On v11 the only failure was the underscore case above.
  • The field contract check reports no actionResult value outside the three words. On v11 it
    reported two: the failure writes.

Left out

  • Copying actionTaken and malwareName out of the Exchange msg array into action and
    target.malware is extra parsing, and a mixed array has no single action. It is left for a
    follow-up.
  • Antimalware cleaned, HyperDetect deleted and Exchange delete appear in neither the samples
    nor the vendor examples, so they are not mapped.
  • Moving addresses between origin and target (web protection, ransomware mitigation source) is a
    separate change.

References: Bitdefender syslog event messages
(https://www.bitdefender.com/business/support/en/77212-237090-syslog-events.html), push event
JSON-RPC messages (https://www.bitdefender.com/business/support/en/77209-135325-push-event-json-rpc-messages.html),
Elastic integrations Bitdefender CEF sample logs.

antivirus-esmc-eset

What this changes

The ESET filter now writes only success, failed or denied to actionResult, or nothing when
the record states no final outcome. The shared threat-intelligence check skips indicator lookups
for failed and denied and looks up everything else, including failure and an empty value. So
a failed or blocked action written with the wrong word, or with no word, can raise a
"Known Malicious IP" alert for something that never got through.

Filter filters/antivirus/esmc-eset.yml (3.2.0 -> 3.2.1):

Record class Before After Why
Audit_Event with a failure result (failure, failed, error, timeout...) failure failed Only the three words.
Threat, HIPS, BlockedFiles, FilteredWebsites with action_error set failure failed Same. A stated error still wins over any block word.
Audit_Event "Login attempt" with result "Access denied" empty failed A refused console login is a failed logon.
Other Audit_Event with result "Access denied" empty denied An access refusal.
FilteredWebsites_Event "Blocked URL", BlockedFiles_Event "Blocked and cleaned" empty denied Block phrases with extra words were missed by the anchored list.
Threat_Event "connection terminated" (HTTP filter cut the download), handled empty denied The product stopped the transfer.
Threat_Event "cleaned", "cleaned by deleting", "deleted", "quarantined", "contained infected files", handled empty denied The product removed or quarantined the threat, as the Bitdefender and SentinelOne filters treat quarantine.
Threat_Event "Retained", anything with threat_handled: false, detections with no action empty empty No final outcome stated.

The two correlation markers that read the outcome (consoleAuthenticationFailure,
quarantineFailure) now test failed.

Rules and tests

  • rules/antivirus/esmc-eset/eset_console_abuse.yml and eset_quarantine_failures.yml test
    failed instead of failure (v1.0.1). The console rule can now also count refused logins
    reported as "Access denied".
  • exploit_detection_events.yml and suspicious_powershell_activity_blocked.yml are unchanged but
    read denied, so they can now also match an exploit or PowerShell detection that ESET cleaned,
    deleted or quarantined. On the public samples neither gained a match; fabricated cases pin the
    new behavior.
  • plugins/alerts/testdata/eset_raw.json: the pinned failure values are failed; the documented
    deletion case and the heuristic remediation case expect denied; 15 new cases cover every class
    above, including the negatives (Retained, unhandled deletion, unhandled connection terminated).

How it was validated

  • EventProcessor playground (main 8a3ade7) over 130 public ESET examples (ESET PROTECT JSON export
    documentation and open-source integration samples), base v11 against this branch: 130 of 130
    processed, no errors. 50 records changed, each only in actionResult (plus the console marker on
    3 refused logins) and each as intended: 36 remediations and 5 terminated downloads to denied,
    5 refused logins to failed, 2 block phrases to denied, 2 failures to failed. The other 80
    records did not change in any field. No value outside the three words.
  • 19 fabricated fixtures (documentation address ranges, ESET's documented JSON export shape) for
    classes the public set lacks: 19 of 19 match their expected values; 15 changed, all as intended.
  • Rule conditions replayed with the engine's CEL on both outputs: the two changed rules have
    positive and negative records; every other rule keeps the same matches on public data.
  • go test ./... in plugins/alerts: everything passes except TestBitdefenderActionResultRaw,
    which already fails on unchanged v11 (go-sdk v1.1.36 keeps underscores) and is fixed by the
    Bitdefender change.
  • The review contract check reports no actionResult value outside success, failed, denied
    (base v11 reported two failure values).
  • Threat-intelligence effect on the public set under today's check: records that reach the lookup
    go from 102 to 52.

Left out

  • Firewall detections with no action key (port scans, exploitation attempts) still carry no
    outcome: the record does not say ESET blocked them. Keeping them out of threat-intelligence
    lookups needs the change to the check itself, not the filter.
  • The heuristic-remediation marker's word list (not an outcome) and the parsing extras in the
    earlier review prototype (ESET Inspect alert name, account name from detail, script-scanner
    process names, CVE with a family prefix) are separate changes.
antivirus-kaspersky

fix(antivirus-kaspersky): write only success, failed or denied in actionResult

Why

actionResult takes exactly success, failed or denied, or stays empty when a record states
no final outcome. The shared threat-intelligence gate recognizes those words; failure is not one
of them. The Kaspersky filter wrote failure for a CEF act of fail, failed or failure. It also
set no outcome on Kaspersky Security Center events that state a block, because Security Center
names the block in the event type and does not send CEF act; the lateral-movement and
suspicious-network markers, which look for denied, could never see them.

What changed, per class (filters/antivirus/kaspersky.yml, v3.2.0 -> v3.2.1)

Record Before After
CEF act fail / failed / failure failure failed
CEF act allow / allowed success unchanged
CEF act block, deny, drop, reject, quarantine (and their past forms) denied unchanged
Native Security Center syslog, event type GNRL_EV_OBJECT_BLOCKED, GNRL_EV_VIRUS_FOUND_AND_BLOCKED or GNRL_EV_APPLICATION_LAUNCH_DENIED (also the 32-character message id GNRL_EV_APPLICATION_LAUNCH_DENIE used when et is missing) none denied
CEF from vendor KasperskyLab with one of those three types in the signature id none denied
Detection only (GNRL_EV_VIRUS_FOUND), task state, audit, device health and other events none none
Another vendor's CEF with the same signature text none none

The new step runs after the event type is extracted and before the filter's own correlation
markers. No address is added; these records keep only the protected device's identity.

References: public Kaspersky Security Center samples in the Splunk Connect for Syslog tests
(tests/test_kaspersky.py), the Wazuh issue 5307 CEF sample and the KSC decoders for Wazuh
readme, which show these event types in native syslog and CEF.

Rules and tests

  • rules/antivirus/kaspersky/lolbins_abuse.yml (v1.1.1): reads failed instead of failure.
    Without this the rule would stop matching failed LOLBin operations. The other four Kaspersky
    rules that read actionResult test denied or success and are unchanged.
  • plugins/alerts/kaspersky_action_result_test.go: the explicit failed operation expects
    failed; the outcome predicates also check that failure and blocked never appear; at least
    27 cases.
  • plugins/alerts/testdata/kaspersky_action_result.json: 10 new cases (native and CEF block
    types, the cut message id, a stale value replaced, detection-only, task state and another
    vendor left empty).
  • plugins/alerts/testdata/kaspersky_raw.json: 4 new cases (lolbins positive with failed,
    lolbins negative without an outcome, native and CEF block types with denied).

Validation

  • EventProcessor playground (main 8a3ade7, go-sdk v1.1.36), base v11 and this branch over the same
    inputs: 50 real server records, 19 public examples and 33 fabricated records (documentation
    addresses). Every input produced an event with no errors.
    • Real records (device health messages and scanner probes): no change.
    • Public examples: 4 of 4 block-type records denied (2 object blocked, 1 application launch
      denied, 1 found and blocked); the other 15 unchanged.
    • Fabricated: 2 failure -> failed, 7 block types -> denied, every record as expected.
    • No field other than actionResult changed; the branch writes only the three words.
  • All 19 Kaspersky rule conditions replayed with the SDK's CEL on the 102 normalized records:
    identical matches before and after (lolbins_abuse: 2 positives, 100 negatives). The old
    lolbins condition on the new output loses the failed record, which is why the rule changed.
  • go test ./... in plugins/alerts: passes except TestBitdefenderActionResultRaw, which already
    fails on unchanged v11 (go-sdk v1.1.36 keeps underscores) and is fixed by the Bitdefender change.
  • Contract check: no actionResult value outside success, failed, denied (base had one
    failure).

Left out

  • Outcomes stated only in message text (network attack blocker, web threat protection, device
    control, numeric Endpoint Security events): no samples with a result field; needs parsing work.
    If the attacker address of network-attack events is ever mapped, it must come with denied
    when the text says the attack was blocked.
  • Moving the Administration Server identity off the target side: address placement is a separate
    change.
antivirus-sentinel-one

fix(sentinel-one): write only success, failed or denied in actionResult

actionResult must hold exactly success, failed or denied, or stay empty when the record states
no final outcome. These are the words the shared threat-intelligence gate recognizes: it skips indicator
lookups for failed and denied and looks up everything else, including failure and an empty value.

The SentinelOne filter wrote failure, and its only outcome step read status keys that the CEF:0
records SentinelOne sends do not carry, so mitigation results never got an outcome.

What changed, per class

Class Before After
Explicit failed status (threatStatus=mitigation_failed, status=failed, mitigationStatus=failed/failure/mitigation_failed, no contradicting success) failure failed
CEF:0 mitigation record (carries fileHash) whose event name ends in "failed", e.g. "Quarantine failed" empty failed
CEF:0 mitigation record "Kill performed successfully" or "Quarantine performed successfully" (the threat was stopped) empty denied
"Kill pending to reboot", "New active threat", threat notices empty empty
Console account and role changes: activityType 23 (user added), 25 (user deleted), 37 (role assigned) empty success

Console changes are logged once they are done, and other sources already write success for completed
administrative changes (Office 365 audit, AWS CloudTrail). None of these records carries an address,
domain or host, so threat-intelligence lookups do not change for this source.

Filter version 3.2.0 -> 3.2.1.

Rules and tests

  • No rule reads actionResult for this data type, so no rule changed.
  • plugins/alerts/sentinel_one_action_result_test.go: the predicate check now tests success, failed,
    denied.
  • plugins/alerts/testdata/sentinel_one_action_result.json: four cases failure -> failed; the
    role-assigned case now expects success; eight new cases (quarantine failed, kill completed,
    quarantine completed, kill pending, new active threat, user added, user deleted, a console name ending
    in "failed" without a file hash).

How it was validated

  • Local EventProcessor playground (engine main 8a3ade7, go-sdk v1.1.36), base and candidate over the
    same inputs, every field compared per record:
    • 29 real server records: 14 console changes empty -> success; nothing else changed.
    • 13 public example records (Rapid7 and FortiSIEM documentation, community samples): 2 completed
      kill/quarantine empty -> denied, 1 "Quarantine failed" empty -> failed; nothing else changed.
    • 10 fabricated CEF:0 fixtures (documentation addresses, example names): all matched their expected
      value, including records that must stay empty.
    • No record outside these classes changed any field; no output value outside the three words.
  • All 19 SentinelOne rules replayed with CEL on base and candidate output: identical matches, no errors.
  • go test ./... in plugins/alerts: everything passes except TestBitdefenderActionResultRaw, which
    already fails on unchanged v11 (a Bitdefender field-name expectation) and is fixed in the Bitdefender
    change.
  • Contract check: no actionResult value outside success, failed, denied (base had failure).

Left out

  • CEF2 threat records are not parsed by this filter, so their mitigation status is not read; that is
    parsing work for a separate change.
  • Remediation and rollback "performed successfully" records stay empty (not part of this review item).
  • Mapping the endpoint's host or address to a side is not part of this change.
aws

fix(aws): write only success, failed or denied in actionResult

The AWS filter now writes only success, failed or denied to actionResult, or leaves it
empty when the record states no final outcome. These are the words the EventProcessor
threat-intelligence gate recognizes: it skips indicator lookups for failed and denied and
looks up every other value. Until now the filter wrote failure, which the gate does not
recognize, so a failed AWS console sign-in from a listed address raised a "Known Malicious IP"
alert for every attempt.

Changes per record class

Class Before After Why
Failed API calls (any non-access errorCode or errorMessage) failure failed Vocabulary
Failed ConsoleLogin, GetSigninToken, CheckMfa failure failed Vocabulary
IAM Identity Center UserAuthentication Success empty success The completed portal sign-in (Identity Center sign-in events)
IAM Identity Center CredentialVerification or UserAuthentication Failure empty failed A failed credential check ends the attempt
IAM Identity Center CredentialChallenge, successful CredentialVerification empty empty Intermediate steps, no final outcome
Console SwitchRole Success / Failure empty success / failed Outcome is in responseElements.SwitchRole (console sign-in events)
Amazon Managed Grafana events with errorCode 200 failure success Grafana puts the HTTP status in errorCode; a 2xx is not an error, and result.statusType states the outcome (Grafana CloudTrail logging)
Grafana events with a 4xx errorCode or statusType failure failure failed Vocabulary
Grafana workspace events with punctuated names (login-auth.sso) not recognised, empty recognised, outcome from result.statusType These events state their outcome; recognition is widened for grafana.amazonaws.com only

Unchanged: successful API calls and console sign-ins (success), the AccessDenied family and
VpceAccessDenied (denied), GuardDuty findings (no outcome). The filter version comment is now
1.1.2.

Rules and tests

  • rules/cloud/aws/credential_access_root_console_failure_brute_force.yml and
    rules/cloud/aws/credential_access_aws_iam_assume_role_brute_force.yml now test failed
    instead of failure; the two matching history markers in the filter changed the same way.
    The other 73 AWS rules test success and need no change.
  • plugins/alerts/aws_action_result_test.go: the two failure expectations are now failed;
    nine new cases cover SwitchRole, the Identity Center steps and Grafana 200/412/login events.
  • plugins/alerts/testdata/aws_raw.json: eight failure expectations are now failed.
  • plugins/alerts/testdata/filter-contracts/aws.json: two new fixtures pin failed for a failed
    console sign-in and success for a completed Identity Center sign-in.

Validation

  • Local EventProcessor playground (engine main 8a3ade7), base v11 filter against this change, on
    297 public CloudTrail samples (AWS documentation, public attack-simulation data, open-source
    integration test fixtures), 15 records from the Identity Center guide and a public SwitchRole
    example, and 21 fabricated fixtures built from AWS's documented record formats. 333 of 333
    records processed, none missing.
  • Every field of every record compared before and after: 78 records changed, each exactly as
    intended (56 public, 6 verify set, 16 fixtures); no other record changed any field; no value
    outside success, failed, denied or empty. Only the recognised Grafana events gained
    fields other than actionResult (action, time, user, service endpoint, account key).
  • All 82 AWS rules evaluated with the go-sdk CEL engine on the before and after output: every
    rule matches the same records before and after. The two changed rules each have positive
    records (8 public root console failures and one fixture; one fabricated malformed
    trust-policy update) and negatives.
  • go test ./... in plugins/alerts: all AWS tests pass. The only failure is
    TestBitdefenderActionResultRaw, which fails the same way on unchanged v11 and is fixed
    separately.
  • The review contract check reports no actionResult value outside success, failed, denied
    (it reported failure on v11).

Left out, and why

  • Allowed VPC endpoint calls (AwsVpceEvent without an error): left empty. AWS documents
    VpceAccessDenied as the only error code for network activity events, so a record without an
    error shows the endpoint policy let the call through, not that the service completed it. The
    same call is also logged as a management or data event that already gets success, and no
    AWS rule filters on event type, so success here would make success-based rules count each
    call twice. This needs a maintainer decision.
  • Event names with punctuation outside Grafana (for example CloudFront's versioned API names
    such as UpdateDistribution2020_05_31): still not recognised. Widening recognition for every
    service would add addresses and rule markers to classes we have no samples for; this is a
    separate recognition change.
  • Grafana 401 and 403 answers as denied: they are failed for now; a follow-up could map
    them to denied.
  • GuardDuty blocked connections: GuardDuty findings map no addresses yet, so they keep no
    outcome.
azure

fix(azure): write only success, failed or denied in actionResult

The threat-intelligence step of the EventProcessor (plugins/feeds/main.go) skips indicator
lookups only when actionResult is failed, denied or the older blocked. Every other value,
including an empty one, is looked up. The Azure Event Hub filter wrote failure, so failed
sign-ins, scanner requests answered 4xx and failed resource writes from a listed address would
raise "Known Malicious IP Detected". Some records also carried a wrong or missing outcome.

This change makes the filter write exactly success, failed or denied, or nothing when the
record states no final outcome. Filter version 2.2.0 becomes 2.3.0.

Note: v11.2.13 does not load this filter at all; v11.2.14 does, so this is the first release in
which these values reach threat intelligence.

What changed, per record class

Class Before After
Sign-in errors, audit/activity/Event Grid failure words, HTTP, gateway, Key Vault and Kubernetes 4xx/5xx failure failed
Entra sign-in 53000, 53001, 53002 (device not compliant, device not domain joined, app not approved: conditional-access refusals) failure denied, like 53003
Application Gateway WAF Detected (detection mode: rule matched, request only logged) success no value. Allowed (success), Blocked (denied) and Matched (no value) are unchanged
MicrosoftServicePrincipalSignInLogs no class, no value sign-in class, same outcome rules as the other sign-in categories
Defender DeviceLogonEvents no class, no value LogonSuccess success, LogonFailed failed, RemoteIP as origin.ip on those two. LogonAttempted gets nothing
Defender DeviceNetworkEvents no class, no value ConnectionSuccess and InboundConnectionAccepted success, ConnectionFailed failed, with LocalIP/RemoteIP placed by direction (the device is origin for connections it opened, target for connections it accepted). Other action types get no outcome and no address
Defender CloudAuditEvents carrying a Kubernetes audit event no class, no value response code routed to statusCode, same outcome rule as kube-audit (2xx at ResponseComplete success, 401/403 denied, other 4xx/5xx failed). No address is mapped
Front Door access log no class, no value, no address client address to origin.ip, httpStatusCode to statusCode, outcome like the Application Gateway access log (2xx success, 401/403 denied, other 4xx/5xx failed, 3xx nothing)
Front Door WAF log no class, no value, no address Block and JSChallengeBlock outside detection mode: denied and the client address. Log, AnomalyScoring, Allow, JSChallenge and detection-mode rows: nothing
App Service access restrictions (AppServiceIPSecAuditLogs) no class, no value, no address Denied: denied and the client address from CIp without the port (IPv4 and bracketed IPv6). Allowed: no address, no value

New classes get a client address only on records that also get their outcome, so no address
reaches threat intelligence without it.

References: Microsoft Entra authentication error codes; Microsoft Defender XDR advanced hunting
schema (DeviceLogonEvents, DeviceNetworkEvents, CloudAuditEvents) and streaming API
record format; Azure Front Door access and WAF log reference; Azure App Service
AppServiceIPSecAuditLogs reference; Application Gateway WAF log reference.

Rules and tests

  • rules/cloud/azure/azure_ad_password_spray.yml now tests failed. The filter's own
    password-spray correlation marker, which the rule's history query reads, tests failed too.
    No other azure rule reads a changed value (31 read success, 1 reads denied).
  • plugins/alerts/testdata/azure_action_result.json: 10 pins failure to failed, WAF
    Detected pinned to no value, 27 new cases (53000-53002, service principal sign-ins, each new
    class and its no-value cases, password-spray positive and negative).
  • plugins/alerts/testdata/azure_raw.json: 28 pins failure to failed, WAF Detected pinned
    absent, 11 new cases for addresses and sides (access-restriction IPv4/IPv6/allowed, outbound and
    inbound connections, connection request without address, failed logon, Front Door 404, Front
    Door WAF block and anomaly-scoring rows, Kubernetes 403 status).
  • testdata/filter-contracts/azure.json pins no outcome value and is unchanged.

Validation

  • EventProcessor playground (main 8a3ade7, go-sdk v1.1.36), base and candidate over the same
    165 inputs: 73 real server records, 50 public example records, 42 fabricated fixtures built
    from the documented formats with documentation address ranges. Every run completed with no
    missing output and no event errors.
  • Per-record comparison of every field: 79 records changed, each only in the fields its class
    allows and to the expected value; no record outside these classes changed; no value outside
    success, failed, denied.
    • Real records: failure 13 to failed; WAF Detected 1 to no value; Kubernetes-through-
      Defender 15 success and 3 no value; Front Door access 1 success and 2 denied; failed logon 1,
      successful logon 1; Front Door WAF block 1 denied; access-restriction denial 1 denied; the
      access-restriction allow and the connection requests changed only their class name; the
      service principal sign-in gained its sign-in class (its interrupt code still gives no value).
    • Fixtures: all 42 matched their expected outcome, address and side.
  • All 40 azure rules evaluated with the engine's CEL on base and candidate outputs: identical
    matches for every rule, no errors. The changed password-spray rule has 2 positives and 163
    negatives.
  • go test ./... in plugins/alerts: all azure tests pass. The package still fails
    TestBitdefenderActionResultRaw, which fails the same way on unchanged v11 and is fixed by the
    Bitdefender change.
  • Contract check: the base finding "filter writes actionResult='failure'" is gone; no
    actionResult value outside the three words.

Left out, and why

  • Key Vault 401 authentication challenges stay denied, and Entra sign-in interrupts (50074,
    50076 and similar) stay failed: making them empty would send them to threat intelligence,
    which looks up empty values.
  • Application Gateway WAF Allowed keeps success, and App Service access-restriction Allowed
    and Front Door WAF Allow get nothing: an allow decision states no answer, and whether it
    counts as success is a maintainer decision.
  • Application Gateway WAF Matched rows keep no value (a rule hit, not a final result). In
    detection mode the request passed, so they are not denied; the lookup on them is a gate
    question, not a filter one.
  • Defender ConnectionRequest and similar connection records get no address: whether an outbound
    request to a listed address with no stated result should be checked is for the engine owners.
  • Conditional-access status failure without a refusal code stays failed.
  • Follow-up parsing, not about the outcome: ports and protocol for Defender connections, account
    and device fields for Defender logons, the source address of Kubernetes-through-Defender
    events, and port, host and URL for Front Door records.
cisco-switch

fix(cisco-switch): write only success, failed or denied in actionResult

The Cisco switch filter (filters/cisco/cs_switch.yml, now version 3.2.0) writes exactly one of
success, failed or denied to actionResult, or leaves it empty when the message states no
final outcome. These are the words the EventProcessor threat-intelligence stage recognizes: it
skips indicator lookups for failed and denied and looks up every other value, including an
empty one.

What changed, per message class

Class Before After Why
SW_DAI DHCP_SNOOPING_DENY, INVALID_ARP, ACL_DENY blocked denied Dynamic ARP inspection dropped the packet; denied is the standard word for a control that refused something.
DOT1X-5-FAIL / DOT1X-5-SUCCESS failed / success unchanged Already correct.
SSH-4-SSH2_UNEXPECTED_MSG ("Terminating the connection from ...") empty failed The switch cut the connection off. The filter already maps the client address to origin.ip, so with no outcome an outside scanner on a threat list raised "Known Malicious IP Detected".
SSH-5-SSH_CLOSE / SSH2_CLOSE with for user '' empty failed The session closed before anyone signed in. A close that names a user is a normal session end and stays empty.
SEC_LOGIN/SECLOGIN LOGIN_SUCCESS / LOGIN_FAILED empty success / failed Console, vty and SSH sign-in results.
SSH-5-SSH2_USERAUTH, SSH2_SESSION ending in Succeeded / Failed empty success / failed The message ends with the result word.
SYS-5-PRIV_AUTH_PASS / PRIV_AUTH_FAIL, DMI-5-AUTH_PASSED / AUTHENTICATION_FAILED empty success / failed Privilege-level (enable) and NETCONF sign-in results.
MAB-5-SUCCESS / FAIL, AUTHMGR-5-SUCCESS / FAIL empty success / failed Final MAC-authentication-bypass and authorization results. AUTHMGR START and RESULT are intermediate steps and stay empty.
TCP-6-BADAUTH (missing or invalid MD5 digest) empty failed The segment failed authentication.
DHCP_SNOOPING messages starting "DHCP_SNOOPING drop message" empty denied DHCP snooping dropped the message. Other DHCP snooping messages stay empty.
PORT_SECURITY PSECURE_VIOLATION*, SPANTREE-2-BLOCK_BPDUGUARD, SEC_LOGIN-1-QUIET_MODE_ON empty denied Port security or BPDU guard blocked the port, or login quiet mode now blocks further sign-ins.
IOS XE forwarding-plane access lists (FMANFP-6-IPACCESSLOG*, text starts <location>: fman_fp_image:) empty success for permitted, denied for denied The verb step required list at the start of the text, so these lines never got a value. A new step reads the verb after the prefix.
Standard access lists (SEC-6-IPACCESSLOGS) empty success / denied The mnemonic was missing from the access-list list.
IOS XR access lists (ACL-IPV4_ACL-6-IPACCESSLOGP : access-list <acl> [(<seq>)] deny|permit ...) empty denied / success The digit in IPV4_ACL stops the header steps from finding the mnemonic, so the class is recognized from log.msg; IOS XR writes deny/permit.
Classic access lists (SEC-6-IPACCESSLOG*, IPV6) success / denied unchanged Already correct. A comment now records why permitted is success: IOS reports nothing after the permit decision.

No step adds or moves an address. Only actionResult changes; every other field of every record
is the same as before.

Rules and tests

  • Rules: none of the three Cisco switch rules (rules/cisco/cs_switch/*) reads actionResult, so
    no rule changes. The fabricated-line playground check still gives the same 6 alerts, all from the
    VLAN hopping rule.
  • plugins/alerts/cisco_switch_filter_test.go: the two SW_DAI expectations move from blocked
    to denied; 40 new per-class cases (positive and near-miss); a new test,
    TestCiscoSwitchActionResultWords, checks that every actionResult step and every fabricated
    line uses only success, failed or denied.
  • plugins/alerts/testdata/cisco-switch/raw.json and expected.json: 35 new fabricated lines
    (documentation addresses, locally administered MAC addresses, example names) in the text shapes
    of public Cisco IOS, IOS XE and IOS XR sample logs from the Elastic and Sekoia integrations;
    expected.json re-recorded with EventProcessor 8a3ade7 (go-sdk v1.1.36). On the 44 earlier
    lines the only differences are the intended actionResult values.
  • filters/audits/cisco-switch.md: deferred item D-10 (failed and blocked) marked done.

Validation

All runs used the EventProcessor playground at 8a3ade7 with go-sdk v1.1.36, comparing the v11
filter (dda9d45) with this change over the same inputs, record by record:

Input set Records Changed How they changed Other fields changed Values outside the three words
Real switch records from production servers 126 4 empty to failed (2 SSH2_UNEXPECTED_MSG, 2 SSH_CLOSE with an empty user) 0 0
The same servers' Firepower lines, routed to the Firepower filter as before 38 0 none 0 0
Public sample logs (Elastic, Sekoia and vendor-community examples) 143 48 blocked to denied 2, empty to success 17, to failed 13, to denied 16 0 0
Fabricated fixtures for every changed class 35 see below every line gets its intended value; near misses stay empty 0 0
  • Each changed record was checked against an independent reading of its text, and every record
    outside the listed classes is byte-for-byte unchanged apart from the envelope.
  • plugins/alerts/testdata/cisco-switch/replay.py against the 8a3ade7 binaries: 79 events, zero
    parser errors, every stored field as in expected.json, 6 alerts each from its intended rule.
  • go test ./... in plugins/alerts: 119 pass, 12 skip, 1 fail. The failure is
    TestBitdefenderActionResultRaw, which fails the same way on the unchanged base (go-sdk v1.1.36
    keeps underscores in field names) and is fixed by the separate Bitdefender change. Base: 118
    pass, 12 skip, the same 1 fail. The new switch cases fail against the unchanged filter.
  • The repository's contract check reports no actionResult value outside success, failed and
    denied (the base reported blocked). Its remaining findings, the severity words
    high/medium/low and a rule reading log.message, are unchanged and out of scope.

Left out, and why

  • Source addresses for sign-ins ([Source: ], from <ip>) and the address, port and protocol of
    access-list lines: this is parsing beyond the outcome and changes what threat intelligence looks
    up (every completed sign-in or permitted packet from a listed address would start alerting). The
    outcome is now right, so the addresses can follow as their own change.
  • The general header fix for subfacilities that contain a digit (for example IPV4_ACL or
    SW1), which still leaves such lines with a wrong log.severity and log.facilityMnemonic and
    no severity; only the IOS XR access-list outcome is handled here.
  • Firepower lines that arrive on the switch input: this is agent routing, not a filter change.
    They are replayed here unchanged.
  • The severity words (high/medium/low): a separate owner decision.
crowdstrike

fix(crowdstrike): write only success, failed or denied in actionResult

The CrowdStrike filter now writes only success, failed or denied to actionResult, or no
value when the record states no final outcome. These are the words the EventProcessor
threat-intelligence step reads: it skips indicator lookups for failed and denied and looks up
every other value, including failure. So a failed console sign-in or a refused API call written
as failure could raise a "Known Malicious IP Detected" alert for the address that tried and
failed.

Filter version: 1.3.0 to 1.3.1.

What changed, per record class

Class Before After Why
Console sign-in with Success false (AuthActivityAuditEvent) failure failed Attempted and did not complete.
API call answered 4xx or 5xx, other than 401 and 403 (APIActivityAuditEvent) failure failed Same. The response code decides, because API records carry Success true even when the request failed.
API call answered 401 or 403 failure denied The request was refused for missing or insufficient rights.
DetectionSummaryEvent and EppDetectionSummaryEvent whose prevention ran no value denied The sensor blocked, killed or quarantined. Condition: PatternDispositionDescription starts with "Prevention", one of ProcessBlocked, KillProcess, OperationBlocked, QuarantineFile, FsOperationBlocked or RegistryOperationBlocked is true, and neither PolicyDisabled nor KillActionFailed is true.
"Detection, ... would have been blocked" detections (PolicyDisabled true), failed kills, plain detections no value no value (unchanged) Nothing was stopped, so the record states no final outcome.
API 2xx, console sign-in with Success true success success (unchanged)

The new denied steps run after the failed and success steps, so denied wins.

Rules and tests

  • No rule under rules/ reads actionResult for the crowdstrike data type (checked with the
    review catalog and grep), so no rule changes. The brute-force rule reads log.eventSuccess,
    which is unchanged.
  • plugins/alerts/crowdstrike_action_result_test.go: expects failed instead of failure,
    models the detection disposition fields the new step reads (and fails if the model uses a field
    the filter never renames), and fails on any value other than success, failed or denied.
  • plugins/alerts/testdata/crowdstrike_action_result.json: 6 cases changed to failed; 10 new
    fabricated cases: API 401, API 403, API 403 with Success false (all denied); three
    prevention detections (denied); a "would have been blocked" detection, a failed kill, a
    "Prevention" description without a block flag, and a plain detection (all no value).
  • crowdstrike_review_test.go needs no change (it never asserts actionResult).

Validation

All runs use the local EventProcessor playground (EventProcessor main 8a3ade7, go-sdk v1.1.36),
base v11 dda9d45a against this change, same inputs.

  • Real server records (50): 6 API calls answered 404 changed from failure to failed; the
    other 44 records are identical in every field.
  • Public vendor example records (98, from the Elastic CrowdStrike integration test logs, the
    SEKOIA-IO intake formats and Sumo Logic documentation): 9 of 33 endpoint detections are real
    preventions and changed from no value to denied; 1 API 404 changed to failed; the 8
    "would have been blocked" detections and every other record are identical in every field.
  • Fabricated fixtures (24, documentation address ranges only, shapes taken from the public
    examples above): 401, 403 and 403-with-Success-false become denied; 404, 429, 500 and a
    failed sign-in become failed; six prevention variants become denied; would-have-been-blocked,
    PolicyDisabled, KillActionFailed, no block flag and plain detections keep no value; 302 keeps no
    value; firewall match records are unchanged.
  • Every record in every run carries only success, failed, denied or no value; no field other
    than actionResult changed; no step errors.
  • Rule conditions: all 17 CrowdStrike rules evaluated with the go-sdk CEL on base and candidate
    output of all three runs match exactly the same records.
  • go test ./... in plugins/alerts: every CrowdStrike test passes (25 outcome cases). The only
    failure is TestBitdefenderActionResultRaw, which fails the same way on unchanged v11 (go-sdk
    v1.1.36 keeps underscores in field names); it is fixed separately in the Bitdefender change.
  • The review contract check reports no actionResult value outside success, failed, denied
    (unchanged v11 reports one: failure).

Left out, and why

  • Firewall match events (FirewallMatchEvent). They map no address and no outcome today, so
    threat intelligence never looks them up. Mapping the addresses needs the outcome in the same
    change. CrowdStrike does not publish the meaning of RuleAction; the Elastic and SEKOIA-IO
    parsers both read 2 as blocked and Elastic reads 1 as allowed. But a Falcon firewall policy
    can run in monitor mode, which reports traffic that would be blocked while allowing it, and the
    record fields that would tell the two apart (Flags.Audit, Flags.Monitor) are not documented.
    Writing denied there could be false. Left for a follow-up once the codes are confirmed.
  • AuditLogV3Event copies. Whether to drop them or map them and drop the older copy is a
    maintainer decision.
  • Console user actions (UserActivityAuditEvent). They carry no success field; success would
    need CrowdStrike to confirm the record is written after the change is applied.
  • Detection addresses on the adversary side. Detections put the protected endpoint's own
    address and name in origin. Moving them to target is a separate side-placement change that
    the CrowdStrike rules using origin must follow.

References:

deceptive-bytes

fix(deceptive-bytes): write only success, failed or denied in actionResult

actionResult must hold exactly success, failed or denied, or stay empty when the record states
no final outcome. These are the words the shared threat-intelligence gate recognizes: it skips indicator
lookups for failed and denied and looks up everything else.

No real Deceptive Bytes record was available for this change: none exists on the servers we can
reach and no public example was found. The change was validated only with fabricated CEF records built
from the format UTMStack documents for this integration (CEF over syslog) and the key order of
UTMStack's earlier Logstash filter for Deceptive Bytes. The vendor's exact wording and letter case
should be confirmed with a real record.

What changed

Record Before After
CEF record with act=blocked or act=prevented empty (the step read only action, which CEF records do not use) denied
act or action with blocked, block, prevented, denied or deny in any letter case empty unless exactly blocked or prevented denied
CEF record with a usable src address and a readable action src only under log.src origin.ip
key=value record with action=blocked denied denied (unchanged)

The source address is mapped only when the record also carries its action. Without that, a record
whose first extension key is act (the current header parsing loses the first key) would get an
address but no outcome, and a blocked attack from a listed address would be looked up. A record with a
detection but no block verdict gets the address and no outcome, which is correct for a detection.

Filter version 3.0.4 -> 3.0.5. The audit note filters/audits/deceptive-bytes.md now describes both
steps.

Rules and tests

  • No rule reads actionResult for this data type, so no rule changed.
  • plugins/alerts/deceptive_bytes_filter_test.go: new TestDeceptiveBytesOutcomeAndSourceAddress
    evaluates the outcome and address conditions with the SDK's CEL (13 cases: block words in several
    letter cases, a detection, a phrase that only contains "blocked", a source without an action,
    unusable sources, key=value records).

How it was validated

  • Local EventProcessor playground (engine main 8a3ade7, go-sdk v1.1.36), base and candidate over the
    same inputs, every field compared per record:
    • 16 fabricated CEF and key=value fixtures (documentation addresses): all matched their expected value;
      7 get the source address, always with denied or a readable non-block action.
    • 5 fabricated CEF shapes from the earlier review: 1 changes to denied with the source address;
      a line whose action key is lost keeps no address.
    • 11 fabricated lines from the existing test data: no change.
    • No other field changed; no output value outside the three words.
  • All 16 Deceptive Bytes rules replayed with CEL on base and candidate output: identical matches, no errors.
  • go test ./... in plugins/alerts: everything passes except TestBitdefenderActionResultRaw, which
    already fails on unchanged v11 and is fixed in the Bitdefender change.
  • Contract check: no actionResult finding; the ten "origin.ip never produced" warnings are gone.

Left out, and why

  • Parsing the CEF header for BSD and header-less syslog, and keeping the first extension key, is larger
    parsing work for a separate change. Until then those lines get neither outcome nor address.
  • Destination, user and host keys are not mapped: their roles are not documented.
  • Severity, event time and values with spaces are separate issues.
firewall-cisco-asa

fix(cisco-asa): write only success, failed or denied in actionResult

Why

The EventProcessor threat-intelligence check skips its indicator lookup only when
actionResult is failed, denied or the older blocked. It looks up every other value,
including an empty one. The Cisco ASA filter (filters/cisco/asa.yml, version 3.1.1) wrote the
non-standard word accepted in 51 steps. It also wrote no value on several firewall drops. As a
result, addresses whose traffic the firewall refused were looked up, and could raise
"Known Malicious IP Detected". Renaming every accepted to success would also be wrong,
because that would mark packet drops, detections and failed sign-ins as completed actions. So
this change maps each message id on its meaning in Cisco's
ASA syslog messages guide.
The filter version is now 3.2.0.

What changed, by class

Value Messages
success Completed sign-ins and sessions: 113004, 113008, 113012 (AAA success), 113039 (AnyConnect session started), 716001 (WebVPN session started), 716038, 719020, 719022, 611310 (XAUTH succeeded), 713253 (tunnel created). 109201-109213 when the text says "Succeeded". These were accepted.
failed Rejected sign-ins that were denied: 113005 "authentication Rejected", 113015, 113016, 113017, 605004, 716039, 719023, 719024, 611311. Operations that could not complete: 113035, 113038, 716007, 302034, 316002 (were denied), plus 109102, 109103 and 109201-109213 when the text says "Failed" (were accepted).
denied New: 106001 (inbound connection denied), 106017 (land attack), 113042 (redirect filter), 733102 (host added to the shun list). Were accepted: 402114-402120 (IPsec packets dropped), 209003 (fragments dropped), 716006 (WebVPN not allowed for the user). Unchanged: 113005 "authorization Rejected", 719019 (authorization refused by the ACL), and the other existing denies.
no value 106102/106103 permitted access-list hits (the filter's grok wrote the vendor verb into the field; it is now deleted). Every Built and teardown connection record. The H.323 pre-allocation notices 302004, 302012 and 302033. The notices 109101, 113009, 113019, 201003, 603109, 609002, 611307, 611309, 611314, 611315, 617100, 716002 and 733101. These were accepted.

For today's check, the new denied values stop lookups on refused traffic, for example a
blocked outside scanner. Moving sign-in failures from denied to failed changes nothing
today, because both words are skipped. Under a check that looks up only success, the
completed sign-ins and sessions are now the records that get checked.

Rules and tests

  • No rule reads actionResult for firewall-cisco-asa, so no rule changed.
  • plugins/alerts/cisco_asa_filter_test.go:
    • Permitted 106102/106103 hits now expect no value.
    • The 106102/106103 check runs the add and the delete in filter order.
    • The floor on helper clauses is now 503. Thirty accepted steps were removed and eight steps were added or split.
    • New TestCiscoASAActionResultMapping. It runs every actionResult step in filter order on 63 message texts in Cisco's documented formats, and it rejects any word other than the three. It fails on 3.1.1 and passes on 3.2.0.
  • plugins/alerts/testdata/cisco-asa/expected.json: 22 accepted values are removed. The two 113015 lines are now failed. The 113042 line is now denied.
  • filters/audits/cisco-asa.md records the change and closes deferred item D08.

How it was validated

All checks ran on EventProcessor 8a3ade7 with go-sdk v1.1.36. The same inputs went through
version 3.1.1 and version 3.2.0:

  • 616 public example lines from public parser test suites: 185 accepted become no value,
    16 become success, 27 denied become failed, and 5 unlabelled denies become denied.
    Every line gets the value its message id requires, and no other field changes.
  • 243 more public lines (241 parser test lines and 2 lines in Cisco's documented UAUTH
    format): 94 accepted Built and teardown records become no value, one 109201 becomes
    success, and one 109203 becomes failed. Nothing else changes.
  • 91 fabricated lines in Cisco's documented formats, with RFC 5737 addresses. They cover
    every changed step and the near misses that must not change. All 91 give the expected value
    with both versions, and no other field changes.
  • No engine errors. No line in any run had an error or went missing.
  • testdata/cisco-asa/replay.py: PASS. 46 events, no parser errors, and the same two
    alerts from the botnet rule.
  • The three ASA rules, evaluated over both versions' output: the same matches.
  • contract.py: no actionResult value outside success, failed and denied (there were 51 before).
  • go test ./... in plugins/alerts: 119 tests pass and 12 skip. TestBitdefenderActionResultRaw fails on the base commit too; it is left to the Bitdefender change.

Left out, and why

  • Teardown outcomes. An answered teardown could carry success, because it has a byte
    count and a reason. But the teardown duration pattern fails on one-digit hours, so this waits
    for that parsing fix. Built records get no value, because a session start is not an outcome.
  • Address sides. Moving addresses between origin and target is a separate change, for
    example the client address of 113015, which sits in target.ip.
  • 113031/113032 keep denied. An ACL that was not applied is not really a refusal, but an
    empty value would be looked up.
  • 716003 ("access GRANTED") gets no value. It records an access decision, not a completed action.
  • Unparsed messages and parsing gaps. Unparsed messages such as 106023, 106100 and 313xxx,
    and the hexadecimal sequence numbers in 402114-402120, need parsing work, not an outcome
    change. The outcome is still written for those lines, because every step is keyed on the
    message id.

Please check against Cisco's guide

Cisco's site refused the documentation fetch during this work, so the meaning of each message id
was taken from secondary copies of the Cisco Secure Firewall ASA Series Syslog Messages guide.
Reviewers, please confirm these three calls against the official guide: 716001 (success, the
WebVPN counterpart of 113039), 719019 (kept denied: an authorization failure refused by the
access list) and 113005 (authentication "Rejected" is failed, authorization "Rejected" stays
denied).

🤖 Generated with Claude Code

@kryonsx

kryonsx commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

Per-filter details (2 of 4): firewall-cisco-firepower, firewall-fortigate-traffic, firewall-fortiweb, firewall-meraki, firewall-mikrotik, firewall-paloalto, firewall-pfsense, firewall-sonicwall, firewall-sophos-xg

firewall-cisco-firepower

fix(firewall-cisco-firepower): write only success, failed or denied in actionResult

The Firepower filter (filters/cisco/firepower.yml, now version 3.2.0) wrote accepted in 45
steps, a word the threat-intelligence gate and the rules do not recognise, and wrote no outcome
at all on the 4300xx security events, including connections the firewall blocked. The gate in
the EventProcessor skips indicator lookups only for failed, denied (and the older
blocked) and looks up every other value, including an empty one, so a listed address that the
firewall blocked could still raise "Known Malicious IP Detected".

After this change the filter writes exactly success, failed or denied, or leaves
actionResult empty when the record states no final outcome. Every accepted step was mapped
per message id; there is no blanket rename.

What changed, per class

4300xx security events (Firepower Threat Defense)

  • 430002 / 430003 with AccessControlRuleAction Block, Block with reset, Interactive Block or
    Interactive Block with reset: denied (before: empty).
  • 430001 intrusion events with InlineResult Block, Blocked or Dropped: denied (before: empty).
  • 430003 (connection end) with Allow, Trust or Fastpath: success only when the responder
    answered, that is ResponderBytes or ResponderPackets above zero. An unanswered allowed
    connection stays empty.
  • Unchanged and still empty: 430002 connection starts of allowed connections (the connection end
    carries the outcome), Monitor, 430007 elephant-flow notices, intrusion events without a block
    or drop (InlineResult absent, Pass or "Would have dropped"), and records that hold two joined
    syslog messages (not parsed).

LINA messages

  • denied (stated blocks): IPsec packet drops 402114-402120 (before: accepted), 209003 fragment
    limit drop and 716006 WebVPN session not allowed (before: accepted), and 106001 inbound TCP
    connection denied, 106017 land-attack deny, 113042 non-HTTP connection denied by the redirect
    filter and 733102 host added to the shun list (before: empty).
  • IPsec 402114, 402116, 402118, 402119 and 402120: the sequence number can be hexadecimal
    (0x...), which the grok did not accept, so these records lost their addresses. The pattern
    now reads 0x hexadecimal or decimal numbers; the records get their addresses together with
    denied.
  • failed (attempted and did not complete): CoA failures 109102 and 109103 (before: accepted);
    UAUTH 109201-109213 when the text says "Failed ..." (before: accepted); failed sign-ins that
    said denied: 113005 "authentication Rejected", 113015/113017, 113016, 605004, 716039, 719023,
    719024 and 611311.
  • success (completed sign-ins and sessions): 113004, 113008, 113012, 113039, 716001, 716038,
    719020/719022, 611310, 713253, and UAUTH 109201-109213 when the text says "Succeeded ..."
    (before: accepted).
  • No value (the record states no final outcome): Built records (302003, 302013, 302015, 302017,
    302020, 302022/24/26, 302303), H.323 pre-allocation notices (302004, 302012, 302033),
    teardowns (302014, 302016, 302018, 302021, 302023/25/27, 302304; their outcome follows once the
    teardown parsing is fixed), 106102/106103 access-list "permitted" hits (the grok writes the
    vendor verb into actionResult; a delete step now removes it), 109101, 113009, 113019, 201003,
    609002, 611307, 611309, 611314, 611315, 716002 and the 733101 threat detection.
  • Kept denied: 113005 "authorization Rejected" and 719019 "authorization failed" (a refused
    authorization is a policy denial), and the other existing denied steps.

Rules and tests

  • No rule changes: no rule that applies to firewall-cisco-firepower reads actionResult. The
    five Firepower rules were evaluated on base and candidate output of every replayed record and
    match exactly the same records.
  • plugins/alerts/cisco_firepower_filter_test.go: the 106102 "permitted" expectation is now "no
    value"; the step-order check covers the denied add and the new delete; the UAUTH check covers
    the two text-based outcome steps; the helper-clause floor follows the removed adds; new change
    cases pin the value of every class above; a new test,
    TestCiscoFirepowerActionResultWords, fails on any add or fabricated line whose
    actionResult is not success, failed or denied.
  • testdata/cisco-firepower: 21 new fabricated lines (Allow unanswered, Allow with responder
    packets only, Trust, Fastpath, Monitor, 430002 Allow start, Interactive Block, 430001 Dropped,
    "Would have dropped" and no inline result, 106001, 733102, 109201 Failed, 113004, 113005
    authentication and authorization, 402119 with a hexadecimal sequence number, 716001, 716002,
    719019, 719023). expected.json was recorded from the playground; for the 63 existing lines
    only actionResult changed.

Validation

EventProcessor playground at commit 8a3ade7 (go-sdk v1.1.36), base v11 dda9d45a against this
branch, filter-only replay of every input, then a field-by-field comparison.

Input set Records actionResult changed Other fields changed
Real FTD records (private, one device) 50 28 0
Public sample lines, header-normalized 213 72 0
Public sample lines, as published 251 2 0
Public 402119 line (hexadecimal sequence number) 1 1 addresses added (V-01 fix)
Fabricated fixtures, one or more per changed class 70 57 addresses added on 3 IPsec records (V-01 fix)
  • Every changed record changed to the value intended for its class, no other field changed
    (except the fields the sequence-number fix adds to 402114-402120), and every candidate value
    is success, failed, denied or empty. The 70 fabricated fixtures also each match the value
    written down for them before the run.
  • Today's gate, evaluated with the go-sdk CEL on both outputs: in no input set does any record
    become newly looked up; blocked or failed records with an address stop being looked up (10
    real, 4 public, 18 fabricated). Under a success-only gate, 18 real and 17 public records with
    an address would now be checked, against none before.
  • Real records: 18 answered Allow connection ends become success; 10 blocked records (430001
    Block 6, 430002 Block with reset 3, 430003 Block 1) become denied; 2 unanswered Allow
    connection ends, 4 elephant-flow notices and the joined records stay empty. Under today's gate
    the 10 blocked records with an address are no longer looked up.
  • Public header-normalized lines: 430003 Allow and Trust with an answer 14 success, Block and
    Block with reset 4 denied, 113005 authentication 9 failed, Built and teardown records 37 no
    value, UAUTH "Succeeded" 3 success.
  • go test ./... in plugins/alerts: all Firepower tests pass. The only failure is the existing
    TestBitdefenderActionResultRaw, which fails the same way on unchanged v11.
  • The repository's own testdata/cisco-firepower/replay.py passes against the playground: 84
    lines, no parser errors, 10 local alerts, each from its intended rule (unchanged from before).
  • contract.py: no actionResult value outside success, failed and denied (45 findings before).

Left out, and why

  • Side placement of outbound 302020 (FP-13) and the teardown parsing fix (FP-03): separate
    changes; teardowns stay empty until their duration parsing is fixed.
  • Routing of real FTD records, which today arrive under the cisco-switch data type (FP-01): an
    agent change, not a filter change.
  • LINA messages the filter does not parse at all (106023, 106100, 106015, 106016, 313005 and
    others, FP-04), 430004/430005 file and malware events, and joined records: parsing work.
  • 302034, 316002, 113035/113038 and 716007 keep denied; they are device errors that the Cisco
    ASA change moves to failed. Both words are skipped by the threat-intelligence gate, so there
    is no threat-intelligence effect; a follow-up can align the two filters.
  • The 402116/402118/402119/402120 grok stores the peer as user= <address> in origin.user
    (existing pattern, now reached by records with a hexadecimal sequence number).
  • The severity words high, medium and low reported by contract.py are unrelated to this change.

Cisco references: Secure Firewall Threat Defense syslog messages
(https://www.cisco.com/c/en/us/td/docs/security/firepower/Syslogs/b_fptd_syslog_guide.html) and
the Secure Firewall ASA syslog messages 715001-721019
(https://www.cisco.com/c/en/us/td/docs/security/asa/syslog/asa-syslog/syslog-messages-715001-to-721019.html).
All committed test inputs are fabricated and use documentation addresses.

firewall-fortigate-traffic

fix(fortigate): write only success, failed or denied in actionResult

The FortiGate filter (filters/fortinet/fortinet.yml, now version 3.4.0) writes only the three
outcome words the shared threat-intelligence gate recognizes: success, failed and denied. It
also gives an outcome to FortiGate records that state one but had none. The gate skips indicator
lookups for failed and denied and looks up every other value, including an empty one, so a
blocked or failed attempt from a listed address should never reach the lookup with a wrong or empty
outcome. FortiWeb (fortiweb.yml) is a separate data source and is not changed.

What changed, per record class

Record class Before After Why
Failed connection, traffic log id 0000000011 (dns, ip-conn) failure failed Only the three words are allowed; failure is not skipped by the gate.
Admin login failed (0100032002), SSL VPN login failed (0101039426, ssl-login-fail) failure failed Same.
IPS and DoS sensor reset, reset_client, reset_server, drop_session, clear_session (utm / ips, anomaly) empty denied The sensor blocked the attack or flood (IPS, anomaly log).
DNS filter redirect (utm / dns, 1501054801, 1501054803) empty denied The lookup was answered with the block portal.
Traffic client-rst, server-rst, timeout with received packets above zero empty success Every traffic action except deny means the policy allowed the session; received packets show the other side answered. Without received packets the record stays empty.
Security-profile pass, passthrough, permit (utm: application control, web filter, DNS filter, VoIP, and SSL, SSH and WAF inspection) empty success The profile let the traffic through. Detections and inspection notes (detected, monitored, analytics, info, bypass, resign-as-untrusted) stay empty.
SSL VPN tunnel-up (0101039424, 0101039947, tunnel type ssl-*) empty, no address success, client address (remip) in origin.ip A tunnel that came up is a completed SSL VPN sign-in. The address uses the same validity checks the filter already applies to SSL VPN login failures. IPsec tunnel-up is unchanged.

The outcome block now runs the success steps first, then the failed steps, and the denied step last,
so a security-profile block (utmaction=block) still wins over any earlier value.

Rules and tests

  • No FortiGate rule reads failure, so no rule file changes.
  • ips_critical_severity_events tests actionResult == "denied". Critical and high IPS records whose
    action is a reset or drop_session are now denied, so they count toward this rule like dropped
    records already do. They are blocks, which is what the rule describes.
  • plugins/alerts/testdata/fortigate_raw.json: four cases now expect failed instead of failure,
    and twelve cases are added: reset and timeout with and without a reply, a profile block inside an
    answered session, application control pass, web filter passthrough, VoIP permit, DNS filter
    redirect, IPS reset matching the IPS critical rule, DoS cleared session, SSL VPN tunnel up, and
    IPsec tunnel up (unchanged).

Validation

  • Local EventProcessor playground (engine main 8a3ade7, all v11 filters staged), base v11 and
    this branch over the same inputs: 303 real FortiGate records, 247 public example records (Elastic
    integration test logs and Fortinet documentation examples) and 20 fabricated records built from
    the FortiOS log message reference
    with documentation address ranges. Every input produced an output with no event errors.
  • Per-record comparison: 85 of the 303 real records changed (22 failure to failed, 16 reset
    or timeout sessions with a reply to success, 34 security-profile passes to success, 3 SSL VPN
    tunnel-up records to success with the client address, 2 DoS cleared sessions and 8 DNS filter
    redirects to denied). 48 of 247 public records and 15 of 20 fabricated records changed in the same
    way. No record changed in any other field, apart from the client address on SSL VPN tunnel-up
    and the IPS critical marker on critical or high IPS resets. No record carries a value other than
    success, failed, denied or empty. Reset and timeout sessions without received packets stay
    empty.
  • Fabricated records cover the classes the real samples lack: IPS reset_client,
    reset_server, drop_session, DoS drop_session, SSL VPN tunnel up (both tunnel types, and an
    unspecified client address, which gets success without origin.ip), and IPsec tunnel up. All 20
    expectations were met.
  • Rules: all seven FortiGate rule conditions evaluated with the SDK's CEL on the normalized
    output. The real and public records match the same rule set before and after. On the fabricated
    records the IPS critical rule matches the critical and high resets and not the informational reset,
    the medium drop_session or the critical detection without a block.
  • Go tests: go test ./... in plugins/alerts passes except
    TestBitdefenderActionResultRaw, which fails the same way on unchanged v11 (it expects the older
    field name for avc_status and is fixed by the Bitdefender change). All 87 FortiGate raw contract
    cases pass; the changed and new positive cases fail against the unchanged filter, as they should.
  • Filter contract check: no actionResult value outside success, failed, denied (unchanged
    v11 reports the two failure values).

Left out, and why

  • IPsec negotiation failures (negotiate with status failure or negotiate_error) could be
    failed. These records carry no address the gate can match, so this only affects rules and
    dashboards. Left for a follow-up.
  • Limiting the accept/close success mapping to traffic records has no observed effect and is left
    as is. accept with zero received packets also stays success.
  • No address moves between origin and target in this change.
firewall-fortiweb

FortiWeb: write only success, failed or denied in actionResult

Why

The threat-intelligence step in the EventProcessor (plugins/feeds/main.go) skips indicator
lookups only when actionResult is failed, denied or the older blocked. Every other value,
including no value, is looked up, and a listed address then raises "Known Malicious IP Detected".
A filter should therefore write exactly success, failed or denied, or nothing when the record
states no final outcome.

Before this change the FortiWeb filter wrote only denied, and only for attack logs with a blocking
action. A blocked CORS request (Return_403_error), every traffic log and every administrator event
log had no value, although each states its outcome.

What changed (filters/fortinet/fortiweb.yml, version 2.3.0 to 2.4.0)

Record class Before After
Attack log, action Return_403_error (FortiWeb answered 403 and did not forward the request, for example CORS Check Security 20000042) none denied
Attack log, action Alert, Erase, other non-blocking actions none none (unchanged; see "Left out")
Traffic log, HTTP return code 2xx none success
Traffic log, HTTP return code 401 or 403 none denied
Traffic log, other 4xx and 5xx none failed
Traffic log, 1xx, 3xx, no or non-numeric return code none none
Event log by an administrator, status=success none success
Event log by an administrator, status=failure (for example failed admin login 10000017) none failed
Event log by system or daemon (start-up, disk usage, signature download notices) none none
  • Traffic logs read http_retcode, and HTTP_retcode from older firmware. The vendor field
    reference describes it as "The HTTP return code" (FortiWeb Log Message Reference, Header and
    body fields). The quoted key="value" form of newer firmware is accepted. The spellings without
    the underscore are read too, for engines older than go-sdk v1.1.35.
  • Event logs use status, which the same reference describes as "The result of the action", and
    user, "The daemon or name of the administrator account that performed the action".
  • Administrator event logs carry the administrator's client address only inside msg
    (... from GUI(<ip>), ... from GUI->HTTPS(<ip>), ... from telnet(<ip>)). It now becomes
    origin.ip, and is geolocated like the other addresses, under these conditions:
    • only on records that also get an outcome;
    • only when the text is a valid address;
    • the last such block in msg is used, because a failed login's user name comes first and is
      chosen by the client.
  • The blocking-action step stays last, so a block always wins.
  • The header comment now describes the three classes.

Threat-intelligence effect:

  • A failed admin login from a listed address is now failed and is skipped.
  • A completed admin action and an answered traffic request are success and are looked up.
  • A 401, 403 or blocked CORS request is denied and is skipped.

Rules and tests

  • No FortiWeb rule reads actionResult, and all ten rules require log.type attack. Replaying
    every rule condition with the go-sdk v1.1.36 CEL evaluator over the base and candidate outputs
    gives identical match sets.
  • plugins/alerts/testdata/fortiweb_raw.json: 19 new raw cases, all with fabricated values and
    documentation addresses. They cover:
    • traffic return codes 200, 401, HTTP_retcode 403, 404, 502, 302 and quoted 200;
    • a traffic log without a return code;
    • admin login failed over ssh, in the quoted form with IPv6, with a forged address in the
      user name, and on the console without an address;
    • admin login success over telnet, and an admin change with an invalid address;
    • a daemon event, a system event and an admin event with an unknown status;
    • return_403_error;
    • an Alert attack that carries a return code and still gets no value.
  • fortiweb_contract_test.go and testdata/filter-contracts/fortiweb.json pin no changed value
    and are unchanged.
  • go test ./... in plugins/alerts: every test passes except TestBitdefenderActionResultRaw.
    That test fails identically on unchanged v11 (go-sdk v1.1.36 keeps underscores) and is fixed
    separately.

How it was validated

All runs used the local EventProcessor playground (main 8a3ade7, go-sdk v1.1.36), with the v11
filter and this filter over the same inputs, comparing every field of every record:

  • Stored server records (109): no record changed. Every one is an attack log, and blocking actions
    keep denied.
  • Public vendor and community examples (73), 25 changed, all as intended:
    • 15 admin successes: success, 14 of them with the admin address;
    • 1 admin login failure: failed with its address;
    • 7 traffic logs with 2xx: success;
    • 1 traffic log with 500: failed;
    • 1 Return_403_error: denied.
  • Fabricated fixtures (26) for classes without samples: 15 changed, and all 26 match the intended
    value and address.
  • In every run, no field other than actionResult changed, apart from origin.ip on admin event
    logs, and no value outside the three words appears.

Left out, and why

  • Attack logs with Alert or Erase (the WAF logged and passed the request) keep no value.
    Whether a passed request should be success needs a maintainer decision. FortiWeb attack logs
    carry no HTTP return code that could settle it.
  • Redirect and Send HTTP Response actions also stop a request. No example shows how they are
    spelled in logs, so they are not mapped yet.
  • Mapping the traffic return code to statusCode, the user to origin.user and the event
    priority to severity is not part of the outcome and is left for a follow-up.

References:

firewall-meraki

fix(meraki): write only success, failed or denied in actionResult

The Cisco Meraki filter now writes only success, failed or denied to actionResult, or
leaves it empty when the record states no final outcome. The shared threat-intelligence check
skips indicator lookups only for failed and denied (and the older blocked). Before this
change the filter wrote failure for AnyConnect login failures, claimed success on a cellular
uplink health notice, and left several stated blocks and failures with no value, so a listed
address that was refused or failed could still raise "Known Malicious IP Detected".

What changed, by class

Vocabulary

  • anyconnect_vpn_auth_failure now writes failed instead of failure.

Now failed (attempted and not completed)

  • Pre-MX 15.12 site-to-site negotiation failures ("phase2 negotiation failed ...",
    "failed to get sainfo", "failed to pre-process ph2 packet"), which had no value.
  • 802.1X 8021x_eap_failure ("802.1X failed authentication attempt" in Meraki's event list).
  • RADIUS-form AnyConnect failures ("RADIUS[..] Server IP=... Peer IP=... Authentication request
    rejected") now also keep the peer address. The old pattern required the message to start with
    Peer IP=, so the RADIUS form lost the address; the new pattern stops at the first Peer IP=,
    so the peer, not the RADIUS server, becomes origin.ip. The address arrives together with
    failed, so the threat-intelligence check skips it, and the VPN brute-force rule can now count
    RADIUS-backed failures.

Now denied (refused by a control)

  • Flows and firewall lines with a numeric inbound rule pattern 1 ... (for example
    pattern: 1 all). Meraki documents that for inbound rules 1 = deny and 0 = allow, and gives
    pattern: 1 all as its example of a blocked inbound flow. Applied to the native groups
    (flows, firewall, cellular_firewall, vpn_firewall, l7_firewall, and the new bridge group)
    and to the legacy flows step.
  • content_filtering_block events.
  • Switch guards that blocked a packet: "Blocked ARP Packet from ...", "Blocked RA Packet from ...",
    "Blocked DHCP Packet from ...".
  • bridge_anyconnect_client_vpn_firewall lines whose pattern starts with deny.

Now success (completed)

  • Numeric allow patterns 0 ... (same Meraki statement as above).
  • ip_flow_start (MX18.101 and newer): the appliance writes it for a flow it forwarded, and
    logs blocked traffic separately as a firewall or flows deny. The closing ip_flow_end record
    carries no byte or packet counts, so it cannot show that the other side answered; success on
    the start record is therefore the fallback, and a comment in the filter says so. ip_flow_end
    stays without an outcome.
  • bridge_anyconnect_client_vpn_firewall lines whose pattern starts with allow.
  • client_vpn_connect and anyconnect_vpn_connect ("user id '...' local ip ... connected from
    ..."): the VPN session came up. The same step maps the user to origin.user, the remote address
    to origin.ip and the assigned tunnel address to log.vpnLocalIp.
  • 802.1X 8021x_eap_success.

No longer success

  • "Cellular connection up" is an uplink health notice, not a completed action (legacy and native
    steps). The state stays in log.connectionState.

Parsing added with the outcome

  • The three header patterns now accept the MX18.101+ groups ip_flow_start, ip_flow_end and
    bridge_anyconnect_client_vpn_firewall. Before, these lines produced no field at all. They now
    get source and destination address and port, protocol, log.eventType and the outcome above.

Kept as they were: site-to-site tunnels that came up (vpn_connectivity_change with
connectivity='true', "ISAKMP-SA established", "IPsec-SA established") stay success; a tunnel that
went down stays empty. The vendor blocked step stays the last actionResult step, so a block
always wins.

Event meanings follow Cisco Meraki documentation:

Filter version 3.2.0 to 3.3.0.

Rules and tests

  • No rule for firewall-meraki reads actionResult, so no rule changed. The rule conditions were
    still evaluated on the old and new output: every rule matches the same records, except that
    meraki_vpn_brute_force now also matches RADIUS-form authentication failures (2 more records),
    which is the class it is written for. RADIUS-form successes do not match.
  • plugins/alerts/testdata/meraki_raw.json: the AnyConnect failure case expects failed; the
    "Cellular up" case expects no value; 20 new cases cover the RADIUS forms, VPN connects, a
    disconnect and an anyconnect_vpn_connection_success record (both stay empty), numeric patterns
    (native and legacy header), the bridge group, ip_flow_start and ip_flow_end, site-to-site
    failure and success, 802.1X EAP, content filtering and switch guards.
  • plugins/alerts/testdata/filter-contracts/cisco-meraki.json: seven isolated outcome controls
    (AnyConnect failure failed, EAP failure failed, numeric deny, numeric allow, ip_flow_start
    success, ip_flow_end and the cellular notice without a value).
  • plugins/alerts/meraki_contract_test.go: new TestMerakiActionResultVocabulary checks that every
    actionResult step writes one of the three words and that the blocked step is the last one.
  • Against the old filter, 26 of the 30 new or changed cases fail; all 30 pass with the new one. The
    4 that already pass are cases whose value does not change (a disconnect, the connection record, a
    tunnel that came up, and the isolated ip_flow_end control).

Validation

All replays used the EventProcessor playground (main 8a3ade7, go-sdk v1.1.36), old filter versus
new filter over the same inputs:

  • 200 public example logs (Meraki documentation examples and the Elastic and Sekoia Meraki
    integration test data): 40 records changed, all as intended; 160 unchanged; no other field
    changed on any record; 0 event errors. Changes: 2 failure to failed; 3 site-to-site failures,
    2 EAP failures to failed; 3 numeric-deny flows, 5 switch guards, 1 content-filtering block to
    denied; 1 numeric-allow flow, 6 ip_flow_start, 1 bridge allow, 5 VPN connects, 3 EAP
    successes to success; 1 cellular notice from success to no value; 5 ip_flow_end and 2
    RADIUS-form successes gained addresses with their outcome unchanged. Values after the change:
    none 132, success 43, denied 18, failed 7 (before: none 161, success 28, denied 9, failure 2).
  • 25 fabricated fixtures (documentation address ranges, Meraki native, RFC 3164-wrapped and
    legacy header formats) for classes the public examples do not show: numeric patterns on the
    firewall, vpn_firewall and cellular_firewall groups and on the legacy header, bridge deny,
    IPv6 and relayed ip_flow_start, relayed ip_flow_end, the documented content-filtering and
    802.1X forms, a RADIUS failure with an IPv6 peer, VPN connects, a phase 1 failure, and negative
    controls (802.1X auth notice, "Blocked DHCP server response", session-manager notice,
    "ISAKMP-SA deleted", tunnel up). 25 of 25 as expected; 18 changed, 7 unchanged.
  • Go tests in plugins/alerts (go test ./...): all pass except TestBitdefenderActionResultRaw,
    which fails the same way on the unchanged base (unrelated to Meraki).
  • The contract check reports no actionResult value outside success, failed, denied (the base
    reported failure).

Threat-intelligence effect to note

  • ip_flow_start, ip_flow_end, bridge-group lines and VPN connects now carry addresses. The
    starts, bridge allows and connects arrive with success; bridge denies with denied.
    ip_flow_end arrives with no value, so the current check looks up its addresses. These records
    describe traffic the appliance forwarded, so a listed address there is real contact.

Left out, and why

  • success on urls records: Meraki says every HTTP GET request produces a urls line and that
    content-filtering events travel in the same role, but not whether a blocked request also produces
    a plain urls line. That needs checking on a device first; a wrong success on a blocked request
    is worse than no value.
  • pattern: Group Policy Allow: the meaning is not documented; left empty.
  • anyconnect_vpn_connection_success: not documented, and the only public example ends
    "Connection closed", so it is left empty and unparsed.
  • Identity parsing that changes no outcome is a follow-up: user and remote address on
    anyconnect_vpn_disconnect and anyconnect_vpn_session_manager, and User= on RADIUS lines.
  • Also follow-ups, not part of this outcome change: the post-MX 15.12 strongSwan "Site-to-Site VPN:" /
    "VPN:" tunnel lines are not parsed; the NAT fields on ip_flow_* (translated_src_ip,
    translated_dst_ip, translated_port) are not mapped; "Blocked DHCP server response" and the
    WPA failed-authentication form of disassociation (auth_neg_failed='1') still have no outcome.
  • Which side an address belongs on is a separate change.
firewall-mikrotik

fix(mikrotik): write only success, failed or denied in actionResult

The threat-intelligence check skips an indicator lookup only when actionResult is failed,
denied or blocked, and looks up every other value, including an empty one. The MikroTik filter
wrote failure for login failures, which the check does not skip, and gave no outcome at all to
firewall lines from drop rules or to successful logins. This change makes the filter write only
success, failed or denied, or nothing when the line states no outcome.

What changed, per class

  • Login failures (system,error,critical "login failure for user U from A via S"): failed
    instead of failure.
  • Firewall lines (firewall,info): RouterOS does not log the rule action, so the rule's log
    prefix is the only place it appears
    (Firewall filter, log-prefix).
    A prefix action:drop, action:reject or action:tarpit, or a block word as a whole word
    (drop, deny, block, reject, tarpit, with their past forms), gives denied. A prefix
    action:accept or an allow word (accept, allow, permit) gives success: RouterOS logs nothing
    after an allowed packet, so the allow decision is the only outcome it reports. A block word wins
    when both appear. A line without such a prefix keeps no value.
  • Successful logins ("user U logged in from A via S",
    Log): success, with
    origin.ip, origin.user and log.service. Logouts are unchanged.
  • Login failures with an empty user name or a name with spaces: the address and service are
    now read after the last " from ", so these lines keep origin.ip (they already had the outcome).
  • Lines with a /system logging prefix ("mk_R1: login failure for user ..."): the prefix is
    split into log.loggingPrefix, so the login steps read the message and these lines get their
    address and outcome together.

An address is never added without the outcome the same line states.

Rules and tests

  • rules/mikrotik/mikrotik_fw/routeros_brute_force_attempts.yml (v1.1.1): the follow-up search
    counts actionResult failed instead of failure.
  • No test in plugins/alerts pins a MikroTik value.

Validation

Local EventProcessor playground (main 8a3ade7, go-sdk v1.1.36), base v11 against this branch,
same inputs:

  • 166 public example lines (85 in the RouterOS default remote format, 81 relayed through syslog),
    33 public and synthetic verification lines, and 27 fabricated fixtures in the documented format
    with documentation addresses (231 in total).
  • Every changed record changed as intended and no other field changed. 48 records changed:
    21 login failures from failure to failed (5 of them also gain their address), 2 login
    failures behind a logging prefix from nothing to failed with their address, 8 successful
    logins to success, 10 firewall lines to success from an allow prefix and 7 to denied from
    a block prefix. No value outside success, failed, denied.
  • All 27 fixtures give the expected value, including the negatives: no prefix, a prefix word that
    only contains "block" inside another word, logouts, a console login and a MAC-address login
    source (success without an address).
  • Rule condition replayed with the engine's CEL: the brute-force rule matches every candidate
    login failure with an address, and each of them carries failed, so its follow-up search counts
    them.
  • go test ./... in plugins/alerts: everything passes except TestBitdefenderActionResultRaw,
    which fails the same way on unchanged v11.
  • Contract check: no actionResult value outside the three words (the base reported failure).

Left out

  • Default remote format (lines that arrive as "topics message" with no syslog header) is still
    not parsed. Parsing it would give an address to every firewall line in that format, and lines
    without an action prefix would then be looked up with no outcome. This needs its own change.
  • Logouts keep no address (they state no outcome).
  • ssh_brute_force_attempts.yml compares protocol with "tcp" while the filter writes "TCP", so
    it does not match; unrelated to this change.
  • Documentation for users: drop rules should log with prefix action:drop and accept rules with
    action:accept, which is the most reliable way to state the outcome.
firewall-paloalto

fix(paloalto): write only success, failed or denied in actionResult

The threat-intelligence step in the EventProcessor decides whether to look up an address by the
actionResult word: it skips failed, denied (and the older blocked) and looks up every other
value, including an empty one. The Palo Alto PAN-OS filter wrote failure, which the step does
not recognise, so failed sign-ins were looked up as if nothing had been refused. It also wrote
success on GlobalProtect records that do not report a finished sign-in. After this change the
filter writes only success, failed or denied, or leaves the field empty when the record states
no final outcome.

What changed, per class

Class Before After Why
CONFIG, result Failed failure failed Standard word.
SYSTEM auth-fail failure failed Standard word. The brute-force history marker log.correlationCandidate.paloalto.authFailure now checks failed.
GLOBALPROTECT, status failure (any event id) failure failed Standard word.
TRAFFIC ended with resources-unavailable failure failed Standard word.
GLOBALPROTECT portal-prelogin, gateway-prelogin with status success success empty The pre-login exchange happens before authentication; any client, including an internet scanner, gets an answer. The following portal-auth / gateway-auth record carries the sign-in result.
GLOBALPROTECT gateway-tunnel-latency, gateway-agent-msg, gateway-tunnel-notify with status success success empty Latency, agent-message and inactivity notices are status reports, not a completed action.
TRAFFIC / THREAT action drop-packet or drop-icmp empty denied Both are blocking actions, like drop and reset-*, which already give denied.
SYSTEM subtype vpn, IKE negotiation failures (ike-*-fail*, ikev2-*-fail*) empty, no address failed; when the object column holds the peer as address[port], the peer becomes origin.ip and the port log.paPeerPort A failed negotiation is an attempt that did not complete. With failed the threat-intelligence step skips the peer, and rules and dashboards can now see who tried.

Unchanged on purpose: TRAFFIC allow still gives success; SYSTEM auth-success and GlobalProtect
portal-auth, gateway-auth, gateway-connected success still give success; CONFIG Succeeded
and Unauthorized still give success and denied; all existing denied mappings stay.

Filter version: 3.1.2 to 3.1.3.

References:

  • PAN-OS syslog field descriptions:
    System,
    GlobalProtect,
    Threat,
    Traffic.
  • Public PAN-OS example logs from the Elastic panw integration test data
    (packages/panw/data_stream/panos/_dev/test/pipeline/, revision 354ff4c9), which include
    THREAT drop-packet, GlobalProtect portal-prelogin and gateway-tunnel-latency records.
  • go-sdk Standard Event Schema (actionResult), EventProcessor plugins/feeds/main.go at 8a3ade7
    (the words the threat-intelligence step skips).

Rules and tests

  • rules/paloalto/pa_firewall/panos_admin_brute_force.yml (v2.0.1): tests actionResult failed
    instead of failure. Without this change the rule would stop matching once the filter ships.
  • ioc_threat_intel_match.yml and ioc_threat_intel_match_server_to_client.yml test denied and
    need no text change; drop-packet threat records with a listed category now reach them.
  • plugins/alerts/testdata/paloalto_raw.json: 14 fixtures pin failed instead of failure; 14 new
    fabricated fixtures cover pre-login and notice records (empty), portal-auth success, portal-prelogin
    failure, DNS security and command-and-control drop-packet (denied, the latter a positive for
    ioc_threat_intel_match), TRAFFIC drop-icmp (denied), IKE failures with an IPv4 peer, an IPv6
    peer and a gateway name (failed), and an IKEv2 success (empty).

Validation

  • EventProcessor playground (main 8a3ade7 build, go-sdk v1.1.36), base v11 filter and this filter
    over the same 357 inputs: 150 real server records, 175 public example logs and 32 fabricated
    fixtures. Every field of every record was compared. 50 records changed, all as listed above:
    25 failure to failed (18 real, 1 public, 6 fixtures); 11 GlobalProtect pre-login and notice
    records lost success (3 real, 3 public, 5 fixtures); 6 drop-packet/drop-icmp records became
    denied (3 public, 3 fixtures); 8 IKE failures became failed (5 real, 3 fixtures), 7 of them
    with the peer address. The other 307 records are identical, no record changed outside these
    classes, and no record carries a value other than success, failed or denied.
  • Rule conditions evaluated with the go-sdk CEL engine on base and candidate output: the brute-force
    rule matches the same 12 auth-fail records before and after (the old rule text would match 0
    after the filter change); ioc_threat_intel_match gains only the fabricated drop-packet
    command-and-control fixture; the server-to-client variant is unchanged.
  • go test ./... in plugins/alerts: all Palo Alto tests pass (139 raw fixtures). One test in the
    package fails, TestBitdefenderActionResultRaw, and it fails the same way on unchanged v11; it is
    unrelated to this change.
  • Contract check: no actionResult value outside success, failed, denied (unchanged v11
    reports four failure writes).

Left out, and why

  • TRAFFIC allow gives success whether or not the other side answered. Requiring received
    packets is a separate decision about allowed sessions and is left for a later change.
  • Short CSV lines that fail before any field is read, and the HIPMATCH and AUTH type spellings, are
    parsing work unrelated to the outcome word; follow-up.
  • GlobalProtect gateway-hip-report, gateway-hip-check, gateway-logout and
    gateway-config-release keep success; they were not part of this review's findings.
  • panos_dns_security_alerts and its history marker do not list the drop-packet action, so DNS
    security drop-packet records do not reach that rule. That is rule coverage, not the outcome
    word; follow-up.
firewall-pfsense

fix(pfsense): write only success, failed or denied in actionResult

The threat-intelligence check skips an indicator lookup only when actionResult is failed,
denied or blocked, and looks up every other value. The pfSense filter already writes the right
words for firewall lines (filterlog pass is success, block is denied); those are unchanged.
Sign-in lines outside filterlog had neither an outcome nor an address, so neither threat
intelligence nor the rules could use them. This change gives each of these classes its outcome and,
where the line names one, its address, always together.

What changed, per class

  • SSH (sshd, OpenSSH format "Accepted|Failed METHOD for [invalid user ]USER from ADDRESS
    port N"): success for Accepted, failed for Failed, with origin.ip, origin.port,
    origin.user, and the vendor words in log.authEvent and log.authMethod.
  • webConfigurator (php-fpm, "Successful login for user 'X' from: ADDRESS" and
    "webConfigurator authentication error for user 'X' from: ADDRESS"):
    success or failed, with origin.ip and origin.user. The general header step does not read
    a program name with a hyphen, so these two messages get their own header step.
  • sshguard blocks ('Blocking "ADDRESS/32" for N secs'): denied, with the blocked address in
    origin.ip. sshguard "Attack from" lines are detections without an outcome, so they get no
    address.
  • OpenVPN user authentication ("user 'X' authenticated", "user 'X' could not
    authenticate."): success or failed, with origin.user. The line names no address.

The filter version is now 3.2.0.

Rules and tests

No rule reads actionResult for pfSense and no test pins a pfSense value, so none changed. The
rule "pfSense admin brute force" requires origin.ip; with this change it can now match
webConfigurator authentication errors and sshguard blocks, which it never could before.

Validation

Local EventProcessor playground (main 8a3ade7, go-sdk v1.1.36), base v11 against this branch,
same inputs:

  • 167 public example lines, 6 synthetic lines, and 17 fabricated fixtures in the documented formats
    with documentation addresses (190 in total).
  • 21 records changed, all as intended, and no other field changed: filterlog lines are identical
    (36 denied, 29 success). Public lines: 2 webConfigurator logins success, 1 webConfigurator
    error failed, 1 SSH failure failed, 1 sshguard block denied, 1 OpenVPN success, 1 OpenVPN
    failed. No value outside success, failed, denied.
  • All 17 fixtures give the expected value, including the negatives: sshd "Invalid user", an
    empty invalid user, sshguard "Attack from" and "unblocking", and a webConfigurator
    configuration-change line, which all stay without outcome or address.
  • Rule conditions replayed with the engine's CEL: no errors; the admin brute-force rule matches the
    6 failure and block records with an address and none of the successful sign-ins.
  • go test ./... in plugins/alerts: everything passes except TestBitdefenderActionResultRaw,
    which fails the same way on unchanged v11.
  • Contract check: no actionResult finding.

Left out

  • sshguard "Attack from" and Snort lines: no address added, because they state no outcome and an
    empty outcome is looked up.
  • The message text of non-filterlog lines is still deleted at the end of the filter.
  • Lines whose syslog host name contains an underscore are not parsed at all today (header step);
    separate change.
  • OPNsense RFC 5424 lines with structured data (PF-04) and ICMP detail (PF-08): parsing work with
    no outcome change.
firewall-sonicwall

fix(sonicwall): write only success, failed or denied in actionResult

The SonicWall filter now writes only success, failed or denied to actionResult, or
leaves it empty when the record states no final outcome. The threat-intelligence check skips
indicator lookups only for failed and denied (and the older blocked). Before this change the
filter wrote failure for login and IKE failures, and left many stated blocks and failures with
no value, so a listed address that was refused or failed could still raise "Known Malicious IP
Detected".

What changed, by class

Vocabulary

  • Login, authentication and IKE failures now write failed instead of failure.

Order of the steps

  • The steps now run success, then failed, then denied. The "first value wins" guards are gone
    from the code-based steps, so a stated failure or block always overrides a broad success from
    fw_action="forward" or fw_action="mgmt". The fw_action drop/deny step moved into the final
    denied step. The 537 "Connection Closed" success step keeps its guard.

Now failed (attempted and not completed)

  • IKE negotiation failures that had no value: 401 NO_PROPOSAL_CHOSEN, 403 negotiation aborted
    (timeout), 405 payload validation failed, 414 INVALID_COOKIES, 545 tunnel end point mismatch,
    616 payload processing failed, 914, 916 and 919 Phase 1 algorithm or DH group mismatch,
    971 IKEv2 peer not responding, 981 IKEv2 proposal mismatch, 983 IKEv2 notify error.
  • 1222 "Invalid SNMPv3 User" and 1226 "HTTPS Handshake" alert.
  • IKE retransmit notices (931, 972) keep no value: they are not a final outcome.

Now denied (refused by a control)

  • 27 "Land attack dropped", 267 "TCP Xmas Tree dropped", 1475 "Responder from country blocked".
  • 809 Gateway Anti-Virus alerts whose message ends "blocked.".
  • Port and FIN scan detections (82, 83, 177) whose note starts "Pkt is dropped".
  • Older SonicOS lines that have no fw_action key: 14 "Web site access denied" and
    38 "ICMP packet dropped".

Now success (completed)

  • fw_action="mgmt" (526 "Web management request allowed").
  • 89 "IKE negotiation complete (Phase 2)", 357 "Main Mode complete (Phase 1)",
    139 "XAUTH Succeeded with VPN client", 1794 allowed login by a user whose password may be compromised.

Classes left empty on purpose: 98 "Connection Opened" (the closing 537 record carries the
outcome), 537 without received bytes, detections without an in-line verdict (608, 1154, floods),
and health or notice messages.

Event meanings follow the SonicOS log event reference guides:

Filter version 4.1.0 to 4.2.0.

Rules and tests

  • rules/sonicwall/sonicwall_firewall/sonicwall_admin_auth_failures.yml (v2.2.0 to v2.2.1) tests
    actionResult == "failed" in its condition and filters on failed in its follow-up search.
  • The other SonicWall rules read denied, which did not change spelling. One effect to note:
    gateway_antivirus_detection.yml now also matches 809 anti-virus alerts that say "blocked.",
    because those records now carry denied. That is the class the rule is written for.
  • plugins/alerts/testdata/sonicwall_admin_auth_raw.json: existing cases expect failed; five new
    cases cover a bad-credential login logged with fw_action="forward" (must be failed, and the rule
    must match), a management request (success), a dropped scan (denied), an IKE failure
    (failed) and a retransmit notice (no value). The five new cases fail against the old filter.

Validation

All replays used the EventProcessor playground (main 8a3ade7, go-sdk v1.1.36), old filter versus
new filter over the same inputs:

  • 199 sampled production records (four installations): 48 records changed, all as intended;
    151 unchanged; no other field changed on any record; 0 event errors. 21 failure became
    failed; 14 records with no value became failed; 7 became denied; 6 became success.
    Values after the change: none 89, denied 53, failed 35, success 22.
  • 116 public example logs (Elastic and Sekoia SonicWall test data): 11 changed as intended
    (3 failure to failed, 5 management requests to success, 3 older-format drops to denied);
    no other field changed.
  • 16 fabricated fixtures (documentation address ranges, SonicOS key=value format) for classes
    the samples do not show, mainly the step order: a failure or policy denial logged with
    fw_action="forward" or "mgmt" now ends failed or denied instead of success, and a success
    code logged with fw_action="drop" stays denied. Negative cases (809 without "blocked.",
    a scan note without "Pkt is dropped", a retransmit notice, 38 with fw_action="NA") stay empty.
    16 of 16 as expected.
  • Rule condition replay (go-sdk CEL): the updated admin authentication rule matches exactly the
    same 9 events on the new output that the old rule matched on the old output; the old rule
    matches 0 on the new output.
  • go test ./... in plugins/alerts: all SonicWall subtests pass (11 of 11). The package has one
    failing test, TestBitdefenderActionResultRaw, which fails the same way on unchanged v11 and is
    unrelated to this change.
  • Contract check: no actionResult value outside success, failed, denied (before the change it
    reported failure).

Left out

  • Other drop events in the older format without fw_action (for example 36, 37, 41, 173 to 175):
    no sample to test against; current SonicOS sends them with fw_action="drop", which is already denied.
  • 1232 "NTP Request sent" with fw_action="forward" keeps success; it is a health message and
    can be reviewed separately.
  • Address side placement (origin versus target) is not changed here.
firewall-sophos-xg

Sophos XG: write only success, failed or denied in actionResult

The Sophos XG filter (filters/sophos/sophos_xg_firewall.yml) now writes exactly success,
failed or denied to actionResult, or leaves it empty when the record states no final
outcome. These are the words the EventProcessor threat-intelligence step understands: it skips
indicator lookups for failed and denied and looks up every other value, including an empty
one. Before this change the filter wrote failure, so failed sign-ins from a listed address raised
"Known Malicious IP Detected" alerts, and several blocked or failed web records carried no value
or the wrong one.

What changed, per class

Class Before After Why
Records with status Failed/Failure/Error: GUI, CLI and API admin sign-in, Firewall, SSL VPN, VPN and VPN Portal authentication, IPSec failure failed Engine vocabulary.
Web answers 400-599 other than 401/403 (WAF requests passed to the server) failure failed Engine vocabulary. 401/403 stay denied.
Content Filtering, subtype Allowed, site answered 4xx or 5xx denied (401/403) or failure success The firewall let the user's request through and the site answered; the site's HTTP status is not the firewall's decision. Subtype Denied stays denied.
Content Filtering, component SSL, subtype Error (the server did not complete the TLS handshake) empty failed The connection was attempted and did not complete.
Content Filtering / Web Content Policy with action Deny or Block empty denied The policy refused the request.
WAF records that name a block reason (for example "WAF Anomaly", "Static URL Hardening", "Antivirus") denied only through a 401/403 status; the reason list in the filter never matched real values denied whenever reason is present and not - The web firewall blocked the request, whatever page it served.
WAF requests passed to the server (reason -) 401/403 denied, other 4xx/5xx failure, 2xx/3xx success same, with failed for 4xx/5xx For requests from outside clients to your own services the server's answer decides.

Hardening (same filter): the KV step also splits key=value text inside quoted values such as a
user agent or a mail subject. A client could send a user agent containing statusCode=200 and turn a
blocked WAF request into success, or set origin.host, target.domain, log.subType or
deviceTime. The filter now deletes the camelCase names it builds itself (log.statusCode,
log.subType, log.component, log.clientHostName, log.domainName, log.timesTamp and others)
right after the KV step, so only the vendor keys can set them.

Rules and tests

  • rules/sophos/sophos_xg_firewall/sophos_password_guessing_on_administrator_account.yml and
    rules/sophos/sophos_xg_firewall/sophos_xg_vpn_auth_failures.yml test failed instead of
    failure. The history markers the filter writes for these rules test failed too. Their
    history queries use the markers, not actionResult, so stored events keep counting.
  • plugins/alerts/testdata/sophos_xg_raw.json: six pinned values updated (four Content Filtering
    Allowed 401/403/404/500 cases now success, two Failed records now failed) and ten fabricated
    fixtures added: Web Content Policy Deny and Allow, SSL Error and Do not decrypt, WAF block reason
    with status 500, WAF passed requests with 401 and 502, and three quoted-text injection cases.

Validation

  • EventProcessor playground (main 8a3ade7, go-sdk v1.1.36), base v11 filter against this filter on
    the same inputs: 59 captured production records, 276 public vendor example records, 9 injection
    probe records and 22 fabricated fixtures. On the 335 real records, 17 changed and every change is
    one of the classes above: 11 failure to failed, 3 Content Filtering Allowed to success,
    2 Web Content Policy Deny to denied, 1 SSL Error to failed. No other field changed on any real
    record, and no record carries a value other than success, failed, denied or empty. All 22
    fixtures and all injection probes produced the intended result.
  • Rule conditions evaluated with the engine's CEL on the normalized output: the updated
    administrator rule matches 3 records and the VPN rule 5, the same records the old rules matched
    on the old output; successful sign-ins, IPSec failures and Firewall authentication failures do
    not match. The old rules match nothing on the new output, which is why they change here.
  • go test ./... in plugins/alerts: everything passes except TestBitdefenderActionResultRaw,
    which already fails on unchanged v11 (go-sdk v1.1.36 keeps underscores in field names) and is not
    touched here. The Sophos XG contract test runs all 89 fixtures; the 13 changed or new outcome
    fixtures fail against the old filter and pass against this one.
  • The repository contract check no longer reports an actionResult value outside success,
    failed, denied.

Fixtures follow the Sophos Firewall syslog formats
(https://docs.sophos.com/nsg/sophos-firewall/20.0/syslog/index.html) and the public Elastic Sophos
XG test logs, with documentation address ranges and example.test names.

Left out

  • WAF requests passed to the server keep the server's answer (401/403 denied, other 4xx/5xx
    failed); 3xx stays success as before.
  • Records with no final outcome stay empty: session notes (IPSec and SSL VPN Terminated, SSL Do not
    decrypt), heartbeat and system health records, WAF records without a status code.
  • Follow-up, parsing rather than outcome: quoted text containing sophosBody= can also overwrite
    the field the later parsing steps read, which leaves that record without parsed fields. Fixing it
    needs a separate change to where the filter keeps the message body.

🤖 Generated with Claude Code

@kryonsx

kryonsx commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

Per-filter details (3 of 4): github, google, ibm-aix, ibm-as400, linux, macos, netflow, o365, sophos-central, suricata, syslog

github

fix(github): write only success, failed or denied in actionResult

Why

actionResult takes exactly success, failed or denied, or stays empty when a record states
no final outcome. The shared threat-intelligence gate recognizes those words; failure is not one
of them. The GitHub filter wrote failure for failed workflow runs, and it set no outcome at all
for other finished CI records (workflow jobs, check runs, check suites, deployment statuses and
commit statuses), so searches and dashboards could not count them.

GitHub webhook payloads carry no client address, so this change does not alter what threat
intelligence can match for this source; it fixes the vocabulary and the completeness of the field.

What changed, per class (filters/github/github.yml)

Webhook delivery Before After
workflow_run completed, conclusion failure, timed_out, startup_failure failure failed
workflow_run completed, conclusion success success success
workflow_job, check_run, check_suite completed, conclusion success none success
same, conclusion failure, timed_out, startup_failure none failed
same, conclusion action_required, cancelled, neutral, skipped, stale none none
any of the four with an action other than completed (requested, rerequested, in_progress), even when the payload repeats an earlier conclusion none (workflow_run: could be success) none
deployment_status state success none success
deployment_status state failure, error none failed
deployment_status state pending, queued, in_progress, inactive none none
status (commit status: top-level state, context, sha) state success none success
status state failure, error none failed
status state pending none none

Only the completed delivery states a run's, job's, check's or suite's final result; a
rerequested check delivery repeats the earlier conclusion, which is why the outcome now requires
action = completed. Conclusions are read where GitHub puts them (log.workflow_job.conclusion
and so on); no field is renamed or removed.

References: GitHub webhook events and payloads documentation
(https://docs.github.com/en/webhooks/webhook-events-and-payloads) and the octokit/webhooks payload
examples.

Rules and tests

  • No GitHub rule reads actionResult; no rule changed. All 13 GitHub rules match the same
    records before and after in the replay below.
  • plugins/alerts/github_action_result_test.go: describes the new payload shapes and rejects an
    expected value outside empty, success, failed, denied.
  • plugins/alerts/testdata/github_action_result.json: failure -> failed (3 cases), the
    workflow-job case now expects failed, and 22 new fabricated cases cover every class in the
    table (including rerequested checks and a non-completed workflow run that carries a conclusion).

Validation

  • EventProcessor playground (main 8a3ade7, go-sdk v1.1.36), base v11 and this branch over the same
    inputs: 147 public GitHub webhook examples, 22 more public examples and 39 fabricated payloads.
    Every input produced an event with no errors. 12 of 147 and 17 of 39 records changed, each as in
    the table; 0 of 22 changed; no field other than actionResult changed on any record; the branch
    writes only success and failed.
  • Rule conditions replayed with the SDK's CEL on all 208 normalized records: identical matches.
  • go test ./... in plugins/alerts: passes except TestBitdefenderActionResultRaw, which
    already fails on unchanged v11 (go-sdk v1.1.36 keeps underscores) and is fixed by the Bitdefender
    change; TestGitHubActionResultRaw 39 of 39 pass.
  • Contract check: no actionResult value outside success, failed, denied (base had one
    failure).

Left out

  • GitHub audit-log streaming (which carries the actor address) is not supported by the
    integration; completed actions there would need success if it is added.
google

fix(google): write only success, failed or denied in actionResult

Why

The EventProcessor threat-intelligence step skips indicator lookups only when actionResult is
failed, denied or blocked (plugins/feeds/main.go). Every other value, including an empty
one, is looked up, and a listed address raises "Known Malicious IP Detected". The GCP filter wrote
failure, which that step does not recognize, so failed sign-ins, failed API calls, DNS errors and
HTTP errors from outside addresses were looked up as if they had worked. Filters now write exactly
success, failed or denied, or leave the field empty when the record states no final outcome.

What changed, per record class (filters/google/gcp.yml, version 2.3.1)

Class Before After
Cloud Audit Logs, final record with a status code other than 0, 7 (PERMISSION_DENIED) and 16 (UNAUTHENTICATED) failure failed
Google Workspace loginFailure and SamlLoginFailed failure failed
Cloud DNS query logs with an error response code other than REFUSED (for example SERVFAIL, NXDOMAIN) failure failed
HTTP request logs (load balancer, Cloud Run, Firebase Hosting) with 4xx or 5xx other than 401 and 403 failure failed
Firewall Rules Logging, disposition DENIED empty, no addresses denied, with addresses
Firewall Rules Logging, disposition ALLOWED empty, no addresses success, with addresses

Firewall Rules Logging: before this change the final delete of log.jsonPayload removed the
whole connection object and the disposition, so these records had neither an outcome nor
addresses. The filter now keeps connection.src_ip/dest_ip as origin.ip/target.ip, the ports
as origin.port/target.port, the protocol number as tcp/udp/icmp/icmpv6, and the rule
reference, direction and disposition under log.jsonPayloadRuleReference,
log.jsonPayloadRuleDirection and log.jsonPayloadDisposition. Every one of these steps is limited
to records whose disposition is ALLOWED or DENIED, so a record gets addresses only together with
its outcome. A denied connection is denied (a firewall rule refused it). An allowed connection is
success as a fallback: Google writes one record per connection and nothing after the allow
decision (no closing record, no reply count), and the filter comment says so. Reference:
https://cloud.google.com/firewall/docs/firewall-rules-logging

Unchanged on purpose: Cloud Audit status 7 and 16 stay denied; first records of long-running
operations stay empty; HTTP 3xx, 202 and 207 stay empty; Cloud Armor outcomes are unchanged; VPC
Flow Logs keep no addresses and no outcome.

Rules and tests

  • No rule under rules/ reads actionResult for the google data type (checked with
    catalog.py and grep), so no rule changed. All 43 Google rules were evaluated with the engine's
    CEL (go-sdk v1.1.36) on the base and candidate output of every replayed record: the match sets
    are identical and there are no errors.
  • plugins/alerts/google_action_result_test.go: the outcome predicate list tests failed instead
    of failure.
  • plugins/alerts/testdata/google_action_result.json: 11 cases now expect failed; 4 cases added
    (firewall DENIED inbound, ALLOWED inbound, DENIED outbound UDP with addresses, ports, protocol and
    rule fields; a VPC flow record that keeps no outcome and no address). The updated test fails on
    the old filter (14 cases) and passes on the new one (82 cases).

How it was validated

Local EventProcessor playground (main 8a3ade7, go-sdk v1.1.36), base v11 dda9d45a and this
change over the same inputs, every field compared record by record:

Input set Records Changed Result
Public example logs (Elastic GCP integration test logs, Google documentation examples, Panther Cloud Armor tests) 100 26 4 failure to failed (1 audit, 3 DNS NXDOMAIN); 16 firewall DENIED to denied and 6 ALLOWED to success, each with addresses; 74 identical, including all 12 VPC flow records
Further public examples (Google identity federation examples, a load-balancer example) 5 0 identical
Fabricated fixtures in Google's documented formats (documentation addresses) 24 15 11 failure to failed (audit codes 5 and 9, Workspace loginFailure and SamlLoginFailed, DNS SERVFAIL and NXDOMAIN, HTTP 404, 429, 500, 502 on load balancer, Cloud Run and Firebase Hosting); 4 firewall records (inbound and outbound, ALLOWED and DENIED); 9 control records identical (audit OK and status 7, suspicious login, DNS NOERROR, HTTP 200, 301, 401, VPC flow SRC and DEST)
Stored server records (Cloud Audit) 5 0 identical, all success

Across all 134 replayed records: no value outside success, failed, denied or empty, no field
changed outside the listed classes, no processing errors. The repository contract check reports no
actionResult value outside the three words (the base reported four failure steps).
go test ./... in plugins/alerts passes except TestBitdefenderActionResultRaw, which already
fails on unchanged v11 (go-sdk v1.1.36 keeps underscores in field names) and is fixed by the
Bitdefender change, not this one.

Left out, and why

  • VPC Flow Logs addresses (the other half of the connection mapping): adding addresses without an
    outcome would create new lookups. The outcome for flows reported by the destination VM needs a
    maintainer decision, so both wait for it.
  • HTTP 3xx responses stay empty by design; the remaining fix for them is the planned gate change in
    the EventProcessor, not the filter.
  • Field gaps unrelated to the outcome (DNS query name to target.domain, Cloud Armor policy
    details, Security Command Center actor, federated principals, backend serverIp, redacted
    callerIp values) are separate follow-ups.
ibm-aix

fix(ibm-aix): write only success, failed or denied in actionResult

The IBM AIX filter (filters/ibm/ibm_aix.yml) now writes only success, failed or denied to actionResult, or leaves it empty when a record states no final outcome. These are the three words the EventProcessor threat-intelligence step reads: it skips the address lookup for failed and denied and looks up every other value, including an empty one.

Before this change, only Oracle audit records got a value, and a non-zero return code was written as failure, a word the engine does not read. SSH, su and sudo records stated a result but got none, even though failed SSH sign-ins carry the client address.

What changed, by record class

Class Before After
Oracle audit, RETURNCODE 0 success success (unchanged)
Oracle audit, other codes (for example 1017 wrong password, 28000 account locked) failure failed
Oracle audit, privilege refusals 1031, 1035, 1045 failure denied
Oracle audit records with TERMINAL or COMMENT$TEXT before RETURNCODE nothing (the return code was never read) from the return code, as above
sshd Accepted <method> for <user> from <address> port <n> (also from sshd-session) nothing success, plus origin.user, log.authMethod and log.authResult
sshd Failed <method> for [invalid user ]<user> from ... nothing failed, plus the same three fields
AIX syslog: ssh: failed login attempt for <user> nothing failed
AIX Login restricted for <user>: 3004-303 (account locked after too many failed logins) nothing failed
su: from <user> to <user> / su: BAD SU from ... nothing success / failed
sudo line that goes straight to TTY= or PWD= (the command was allowed and ran) nothing success
sudo <n> incorrect password attempt(s) nothing failed
sudo user NOT in sudoers, user NOT authorized on host, command not allowed nothing denied
sshd Accepted key ... found at ... (an intermediate step), Invalid user notices, session and disconnect lines, other Login restricted codes, sudo a password is required nothing nothing (unchanged)

Notes:

  • The value steps run success, then failed, then denied, because add overwrites.
  • The conditions match exact message shapes. For example, a sudo command line whose command text contains "incorrect password attempt" is still success.
  • The Oracle return-code step reads the key wherever it sits. It does not consume log.msg, which the filter deletes and rebuilds afterwards anyway. The other Oracle keys are left as they are (see "Left out").
  • The filter version is now 3.2.0.

Rules and tests

  • No rule under rules/ reads actionResult for this data type, so no rule changes.
  • The 10 AIX rules were evaluated with the engine's CEL library over the old and new filter output for every input below. No rule matched before or after, so the new fields start no alert.
  • No test in plugins/alerts covers this filter. go test ./... in plugins/alerts gives 118 passed, 12 skipped and 1 failed. The failure is TestBitdefenderActionResultRaw. It already fails on unchanged v11, because go-sdk v1.1.36 keeps underscores in field names, and it is fixed in the Bitdefender change.

How it was validated

The old and new filters were run in the local EventProcessor playground (engine main 8a3ade7, go-sdk v1.1.36) with all v11 filters loaded. Every input was compared field by field.

  • 50 real AIX records (SSH, su, sudo, lockout and session lines). 28 now get a value: 17 success and 11 failed. The other 22 are identical in every field.
  • 27 public example records: OpenSSH and sudo examples, Oracle standard audit records published in Oracle, NXLog and community documentation, and an AIX audit example. 15 now get a value (12 success, 4 failed, 1 denied), including the 1017 failed Oracle logon and 3 Oracle records whose return code was hidden. The other 12 are unchanged.
  • 22 fabricated fixtures using documented formats and documentation addresses only. They cover Oracle 1031, 1035, 1045, 28000, 942 and 1017, a return code as the last key, every sudo refusal wording, sshd-session from OpenSSH 9.8 and later, Failed keyboard-interactive/pam for invalid user, and negative cases (other Login restricted codes, a password is required, the intermediate Accepted key line). All 22 match their expected value.
  • In every run, no record carries a value outside the three words, and no record outside the changed classes changed any field.
  • The review contract check reports no actionResult value outside success, failed and denied. It still reports the from.host field, which is a separate issue.

Left out, and why

  • Other Oracle audit keys read wherever they sit (part of finding AIX-06). This PR changes only RETURNCODE. Fixing the other keys also fixes the unescaped $ in OBJ$CREATOR, OBJ$NAME and OS$USERID, which today never match. That would start filling log.osUserID, which the rule "System integrity violations" reads (log.osUserID == root with a different origin.user), and so could start new alerts on Oracle records. That needs its own review.
  • AIX audit (auditpr) status and the Oracle client address (finding AIX-12). This is new parsing of a record layout that depends on how each site forwards auditpr output. It would also add an address to Oracle records, and it needs a decision on whether FAIL_AUTH means failed or denied. It is follow-up work.
  • The address of ssh: failed login attempt ... from <address> and the accounts in su and session lines: this is parsing, not outcome, so it was left for a separate change.
  • AIX records stored as generic syslog in production: this is a routing problem (AIX-01), separate from this change.

References: OpenSSH auth_log messages; sudo log format (sudoers(5), "Log format"); IBM AIX message 3004-303; Oracle Database error messages ORA-01017, ORA-01031, ORA-01035, ORA-01045, ORA-28000; Oracle standard audit to syslog (AUDIT_SYSLOG_LEVEL).

ibm-as400

fix(ibm-as400): write only success, failed or denied in actionResult

Why

actionResult takes exactly success, failed or denied, or stays empty when a record states
no final outcome. The shared threat-intelligence gate recognizes those words; failure is not one
of them. The IBM i (AS/400) filter wrote failure for wrong passwords. Two gaps also hid real
outcomes: records the collector sends with a few digits in front of the JSON object (for example
48{...}) were not parsed at all, so successful host-server connections in them lost both
success and the client address, and the CPIAD02 connection message got no outcome.

What changed, per class (filters/ibm/ibm_as_400.yml, version 4.1.0 -> 4.1.1)

Record Before After
CPF2234 (password not correct) failure failed
CPIAD09 (user from client connected to a host server job) success + client address unchanged
CPIAD02 (user from client connected to server) none, no address success + client address, same parsing as CPIAD09
Collector JSON record with a digit prefix (48{...}) not parsed: only log.message parsed exactly like the same record without the prefix
Prefixed text that is not a whole JSON object log.message = raw unchanged
CPIAD0B, CPIAD12, CPF1124, CPF1164, CPF1393 and other notices none none

The prefix handling is three small steps: a grok that splits the digits from the JSON text, a
json step on that text, and removal of the helper field. The raw-text fallback now runs only when
no JSON text was found, so anything that cannot be parsed keeps its text as before.

References: IBM support, "Data Queue Host Server Job Log Messages" (CPIAD02 is logged when a user
connects to a host server job, with the client address or host name); TechChannel, "Prestart Job
Messages"; IBM i message descriptions CPIAD09 and CPF2234.

Rules and tests

  • No IBM AS/400 rule reads actionResult (all 8 read log.message); no rule changed. All 8
    match the same records before and after in the replay below.
  • No test in plugins/alerts covers this filter, so none pinned a changed value.

Validation

  • EventProcessor playground (main 8a3ade7, go-sdk v1.1.36), base v11 and this branch over the same
    inputs: 50 real collector records, 15 public examples built from IBM documentation texts, and 19
    fabricated fixtures (documentation addresses, placeholder profiles). Every input produced an
    event with no errors.
    • Real records: 6 of 6 wrong-password records failure -> failed; 4 of 4 prefixed records now
      parsed, each identical to the same record without the prefix (the one CPIAD09 among them gains
      success and its client address); every other record unchanged in every field.
    • Public examples: 2 wrong-password records -> failed; 1 CPIAD02 -> success with its client
      address; nothing else changed.
    • Fixtures: all 19 matched their expected value and address, including English and Spanish
      texts, a cut-off prefixed record (kept as text) and collector status lines (unchanged).
    • The branch writes only success and failed.
  • Rule conditions replayed with the SDK's CEL on all normalized records: identical results.
  • go test ./... in plugins/alerts: passes except TestBitdefenderActionResultRaw, which already
    fails on unchanged v11 (go-sdk v1.1.36 keeps underscores) and is fixed by the Bitdefender change.
  • Contract check: no actionResult value outside success, failed, denied (base had one
    failure).

Left out

  • Setting action to user_login for CPF2234: it changes the action field, not the outcome, and
    the action vocabulary is not settled yet.
  • Placing the monitored system and the client on separate sides, and keeping a host-name client:
    address placement is a separate change.
  • Dropping the collector's own status lines that arrive with the same digit prefix: noise
    handling, not the outcome.
linux

fix(linux): write only success, failed or denied in actionResult

Why

The shared threat-intelligence gate skips indicator lookups only when actionResult is exactly
failed, denied (or the older blocked). Every other value, including an empty one, is looked
up, and a listed address raises "Known Malicious IP Detected".

The v11 Linux filter wrote failure for failed systemd jobs, failed audit syscalls and failed
USER_LOGIN records. USER_LOGIN is the one record that carries the peer address, so on v11 every
failed SSH login from a listed internet address would be looked up and alert. Other classes
(PAM checks, account changes, AVC denials, journald copies of audit records, kernel firewall
lines, SSH lines in journald) stated an outcome that the filter did not write.

The filter now writes exactly success, failed or denied, or nothing when the record states
no final outcome.

What changed, per class

Class Before After
systemd job JOB_RESULT failed, timeout, dependency failure failed (done stays success)
native audit SYSCALL result fail failure failed (EACCES/EPERM stay denied, connect in progress stays empty)
native audit USER_LOGIN res=failed failure failed
native audit USER_AUTH empty, no address success / failed from res, valid peer in origin.ip
native audit USER_ACCT empty, no address success, or denied when the account check refused, valid peer in origin.ip
ADD_USER, DEL_USER, ADD_GROUP, DEL_GROUP, USER_MGMT, GRP_MGMT, USER_CHAUTHTOK, ACCT_LOCK, ACCT_UNLOCK empty success / failed from res
standalone AVC empty denied for AppArmor DENIED or SELinux denied with permissive=0; AppArmor STATUS/AUDIT/ALLOWED, SELinux permissive or without the flag stay empty
journald copies of audit records (_TRANSPORT=audit) empty same outcomes as the native records for USER_LOGIN, USER_AUTH, USER_ACCT, account management, SYSCALL (success=yes/no, exit -13/-1 is denied, -115/-114/-11 stay empty) and AppArmor AVC (journald keeps the quotes: "DENIED"). No address is promoted from these copies. Both key spellings are read (_AUDIT_TYPE_NAME/AUDITTYPENAME, AUDIT_FIELD_RES/AUDITFIELDRES).
kernel netfilter LOG lines empty, addresses only in the text denied when the prefix names a refusal ([UFW BLOCK], [UFW LIMIT BLOCK], CSF *TCP_IN Blocked*, firewalld REJECT, DROP: and similar), with origin.ip/target.ip, ports and tcp/udp. [UFW ALLOW], [UFW AUDIT] and prefixes with an unknown verdict stay empty and keep their addresses in the message.
sshd, PAM, unix_chkpwd and sudo lines in journald empty, no address success for Accepted <method> for; failed for Failed <method> for, pam_unix(<svc>:auth): authentication failure, unix_chkpwd password failure, and sshd lines that ended before sign-in ([preauth], Invalid user, Did not receive identification string, Unable to negotiate, Timeout before authentication, banner exchange); sudo success / failed (incorrect password, password required) / denied (NOT in sudoers, command not allowed). The peer becomes origin.ip (and origin.port) only on lines that state an outcome; session ends after a sign-in and Postponed lines stay empty. log.message is kept.

Filter version 5.1.0 becomes 5.2.0. Nothing writes the older fail or done: the block starts by
deleting any incoming actionResult, and every writer is an add of one of the three words.

Rules and tests

  • No rule under rules/ reads actionResult for the linux data type, so no rule changes.
    All 49 Linux rule conditions were evaluated on the old and new output of every replayed record
    with the go-sdk v1.1.36 CEL evaluator: the match sets are identical.
  • plugins/alerts/linux_action_result_test.go: the predicate loop tests failed instead of
    failure.
  • plugins/alerts/testdata/linux_action_result.json: failure becomes failed; the USER_AUTH case
    now expects success; the "audit does not inherit a journal job result" case uses USER_CMD so it
    keeps testing that; 68 new cases for USER_AUTH/USER_ACCT with peers, account management, AVC,
    journald audit copies (both key spellings), unix_chkpwd and sudo.
  • plugins/alerts/testdata/linux_action_result_rules.json: failure becomes failed; the USER_AUTH
    cases now expect failed/success and the peer in origin.ip. The brute-force rule predicate
    results are unchanged.
  • Kernel firewall and sshd lines use kv and multi-pattern grok, which the Go raw model does not
    execute; they are covered by the playground replay below.

Validation

  • EventProcessor playground (main 8a3ade7, go-sdk v1.1.36), old and new filter over the same inputs:
    402 real Linux records sampled from customer servers, 282 public example records (Elastic
    integrations and Beats test logs, go-libaudit test data, journald examples, SELinux notebook) and
    101 fabricated fixtures built from the documented formats (OpenSSH, sudoers, pam_unix, netfilter
    LOG, UFW, systemd journal fields, Linux audit records) using documentation address ranges only.
    Every input produced one output in both runs, with no event errors.
  • Per record, every field was compared. Assertions, all passing:
    every new actionResult equals an independent re-derivation of the rules above; only the fields
    of the changed classes changed; no value outside success, failed, denied; every newly
    promoted address sits on a record with an outcome; every fixture's stated expectation holds.
  • Real server samples: 20 failure became failed; 10 USER_AUTH/USER_ACCT, 9 journald audit copies,
    13 firewall blocks and 53 SSH/PAM/sudo lines gained an outcome; 161 records outside these classes
    are unchanged. Public samples: 26 account-management/AVC and 10 USER_AUTH/USER_ACCT records gained
    an outcome.
  • go test ./... in plugins/alerts: all Linux tests pass (232 subtests). The only failure is
    TestBitdefenderActionResultRaw, which already fails on unchanged v11 (go-sdk v1.1.36 keeps
    underscores) and is fixed separately.
  • The review contract check reports no actionResult value outside success, failed, denied
    (it reported three failure writers before).

Left out, with the reason

  • Filebeat system module auth lines sent as linux: they need the syslog header split first;
    follow-up.
  • [UFW ALLOW] lines: no real sample, and an allow record without a reply is not a completed
    connection; left empty with no address.
  • Custom netfilter prefixes with an unknown verdict: left empty, addresses stay in the message.
  • Addresses of journald audit copies: not promoted, because the native collector already reports the
    same records; follow-up.
  • SELinux AVC lines in journald and AppArmor lines in the kernel transport: text without a parsed
    verdict field; follow-up.
  • User and command names from sshd and sudo lines (target.user, origin.user, origin.command):
    many Linux rules read origin.command, so this is a separate change.
  • action for systemd job records, USER_CMD, USER_ERR and CRED_* records: not an outcome
    change.
macos

macOS: write success, failed or denied where the message states an outcome

Why

A filter should write exactly success, failed or denied in actionResult, or nothing when the
record states no final outcome. These are the only words the EventProcessor threat-intelligence step
recognizes. The macOS filters wrote no outcome at all, although several unified-log message classes
state one: sandbox denials, failed directory sign-ins, granted and refused authorization rights,
privacy (TCC) decisions and rejected screen-lock passwords.

macOS records carry no remote address, so threat intelligence is not affected. The value is for
rules, dashboards and searches.

What changed (filters/macos/macos.yml, version 3.1.0 to 3.2.0; macos-syslog.yml unchanged)

The steps key on the subsystem or process and the start of the message text, never on the log
level.

Class Condition Value
Authorization right granted subsystem com.apple.Authorization, message starts Succeeded authorizing right success
Privacy access allowed subsystem com.apple.TCC, AUTHREQ_RESULT: with authValue=2 success
Directory sign-in failed subsystem com.apple.opendirectoryd, Authentication failed for or Failed SecureToken authentication for failed
Authorization right refused or cancelled subsystem com.apple.Authorization, Failed to authorize right failed
Screen-lock password rejected process loginwindow, -[LWDefaultScreenLockUI authFailWithMessage:] with INCORRECT password (only the line that states the result, not the follow-up lines of the same method) failed
Privacy access denied subsystem com.apple.TCC, AUTHREQ_RESULT: with authValue=0, or ...; returning denied. denied
Sandbox and System Policy denials process kernel (or image /kernel in the legacy log stream form), or subsystem com.apple.sandbox.reporting; message [N duplicate report(s) for ]Sandbox: or System Policy: followed by ... deny(N) denied
Sandbox approval request rejected process kernel, sandboxd rejected approval request from denied
Service lookup denied by the sandbox process launchd, denied lookup: denied

Everything else stays empty. That includes:

  • TCC authValue=3 (limited);
  • sandbox allow(...) reports and fragments of multi-line sandbox reports;
  • XProtect "Forwarding detection succeeded!" (a detection, not a completed action);
  • the same message texts written by other processes.

Rules and tests

  • No macOS rule reads actionResult. Replaying all 36 macOS rule conditions with the go-sdk v1.1.36
    CEL evaluator over the base and candidate outputs gives identical match sets.
  • plugins/alerts/testdata/macos_raw.json: 24 new raw cases, all with fabricated values. Each runs
    in the three filter orders the test uses, so 72 subtests. They cover every class above and the
    negative controls:
    • forms: native JSON, forwarding wrapper and legacy ndjson;
    • TCC authValue=3, sandbox allow, the sandbox report fragment;
    • denied lookup from another process, sandbox text from a user process;
    • getpwnam failed, the screen-lock follow-up line and XProtect.
  • go test ./... in plugins/alerts: every test passes except TestBitdefenderActionResultRaw.
    That test fails identically on unchanged v11 (go-sdk v1.1.36 keeps underscores) and is fixed
    separately.

How it was validated

All runs used the local EventProcessor playground (main 8a3ade7, go-sdk v1.1.36), with the v11
filter and this filter over the same inputs, comparing every field of every record:

  • Stored records from real macOS endpoints (250): 30 changed as intended and nothing else changed.
    • denied 19: 6 sandbox denials, 11 rejected approval requests, 2 TCC denials.
    • success 6: 4 granted rights, 2 TCC allows.
    • failed 5: directory sign-in failures.
  • A second bounded set of stored records for the classes the first set lacked (32): 29 changed as
    intended.
    • denied 27: 20 kernel System Policy or sandbox reports, 4 sandboxd reports, 3 launchd denied
      lookups.
    • failed 2: a cancelled authorization and a rejected screen-lock password.
    • The two screen-lock follow-up lines and a report fragment kept no value.
  • Public examples (53): the 9 legacy log stream sandbox denials became denied; nothing else
    changed.
  • Fabricated fixtures (27): every one has the intended value.
  • In every run only actionResult changed, and no value outside the three words appears.

Left out, and why

  • Legacy plain-syslog macOS lines (for example kernel[0]: Sandbox: ... deny(1) inside a text line)
    are not parsed into process and message today. That is a parsing change, not an outcome change.
  • No sample exists for remote sign-ins that carry an address (sshd, Screen Sharing), for
    application-firewall blocks or for VPN logs, so none are mapped.
netflow

fix(netflow): write only success, failed or denied in actionResult

actionResult is the final outcome of the action an event describes. The shared
threat-intelligence gate skips indicator lookups for failed and denied and looks up every
other value, including an empty one. The NetFlow filter wrote no outcome at all, so every flow,
including unanswered and refused connection attempts, was looked up, and a gate that checks only
completed actions would never check a flow. This change derives the outcome from the TCP flags
the exporter reports. The filter version goes from 3.1.1 to 3.1.2.

What changed

The flags field is cumulative: a bit is set when any packet of the flow had that TCP flag
(IANA IPFIX registry, element 6 tcpControlBits). Decimal values: FIN 1, SYN 2, RST 4, PSH 8,
ACK 16, URG 32, ECE 64, CWR 128.

TCP flow flags Before After Why
Include ACK (for example 16, 24, 26, 27, 31) empty success The handshake completed and the connection carried traffic.
Only SYN and ACK, with or without URG, ECE or CWR (18, 50, 82, 114, 146, 178, 210, 242) empty empty This is a server's answer to a SYN; a one-way record cannot show the client's ACK, so the handshake is not proven.
A reset without SYN, FIN or PSH (4, 20, 36, 52, 68, 84, 100, 116, 132, 148, 164, 180, 196, 212, 228, 244) empty failed The other side refused the connection.
SYN only, no flags, or not TCP (UDP, ICMP) empty empty No outcome can be read.

The two new steps run just before the final clean-up step; they write only actionResult.

Rules and tests

  • No rule under rules/ reads actionResult for this data type (12 rules checked), and no test
    in plugins/alerts covers NetFlow, so no rule or test changes.

Validation

  • Local EventProcessor playground (latest engine build, go-sdk v1.1.36), previous filter against
    this one, record by record:
    • 297 public NetFlow v5, v9 and IPFIX flows (device captures from the logstash NetFlow codec
      test suite, decoded and rendered with the agent's own code): 54 TCP flows with ACK became
      success; 8 SYN+ACK-only flows, 7 SYN-only flows and all UDP, ICMP and flag-less flows stay
      empty; no other field changed.
    • 20 fabricated flows (documentation addresses only), one flag class each, including the
      reset-only branch that no public capture has (flags 4, 20 and 148, IPFIX and v9): every flow
      got its expected value.
  • Every record carries success, failed or no value.
  • go test ./... in plugins/alerts: the only failure, TestBitdefenderActionResultRaw, fails
    the same way on the unchanged branch and belongs to the Bitdefender change.
  • The filter contract check reports no actionResult value outside the three words.

Left out, and why

  • NetFlow v5 cannot get an outcome yet. For v5 flows the filter parses neither the protocol
    nor the flags (its patterns expect the bracketed v9 and IPFIX form), so these steps cannot
    reach them. Fixing that is parsing work for a separate change.
  • A flags field exported as two bytes (for example 0 16) is not read as a number and stays
    empty; same parsing follow-up.
  • UDP and ICMP flows stay empty. Whether a two-way UDP flow counts as completed needs a
    maintainer decision.
  • The IPFIX firewallEvent element (a denied flow) is not parsed and no sample has it.
o365

fix(o365): write only success, failed or denied in actionResult

Why

The threat-intelligence step in EventProcessor skips indicator lookups only when actionResult
is failed, denied or blocked. Every other value, including failure and an empty value, is
looked up, and a listed address raises "Known Malicious IP Detected".

The v11 Office 365 filter writes failure for failed operations. Failed sign-ins
(UserLoginFailed) were therefore looked up, so an attacker's address on a feed raised an alert
for a sign-in that did not work. Across the servers we reviewed, roughly 143,600 failed sign-ins
a month are stored today as failed by the older filter and skipped. The v11 filter would change
them to failure, and they would all be looked up. This change makes the filter write only
success, failed or denied, or no value when the record states no final outcome.

What changed in the filter (filters/office365/o365.yml, version 1.4.2)

Record class Before After Reason
Every step that wrote failure (ResultStatus Failure/Failed, Exchange admin False, sign-in error codes, Planner failures, every UserLoginFailed) failure failed One word per outcome, the word the engine reads
Sign-ins refused by conditional access or security defaults (Entra 53000, 53001, 53002, 53003, 530032, 530034, 530035 in ErrorNumber or ErrorCode) failure denied A policy refused the sign-in; it is not a credential failure. The step runs after the UserLoginFailed step so it wins
Safe Links (RecordType 41) with UrlClickAction (the spelling seen in real records) no value 2 denied, 4/5 success The filter read only the documented URLClickAction spelling; both are read now
Mail threat records (RecordType 28) with DeliveryAction Blocked no value denied The service stopped the message. Delivered and DeliveredAsSpam keep no value
Power BI and Fabric (RecordType 20) no value IsSuccess true success, false failed These records carry the outcome in IsSuccess, not ResultStatus
Teams TeamsSessionStarted, and SharePoint/OneDrive operations without ResultStatus no value success These records are written only after the operation happened
SharePoint/OneDrive detections, DLP rule matches, blocks, denials and partial operations (FileMalwareDetected, DLPRuleMatch, BaselineSecurityModeThirdPartyAppHPA, ContentSecurityPolicyViolated, names ending in Blocked, Denied, Failed, Violated, Detected or Partial) no value no value (unchanged) Not a completed action; blocks still become denied in the later step
Yammer (RecordType 22) with ResultStatus TRUE no value success Yammer reports a completed operation as TRUE
Compliance cmdlets (RecordType 18) with ResultStatus Error no value failed The cmdlet did not complete
Compliance alert detections (RecordType 40, AlertTriggered, AlertEntityGenerated) success no value A detection is not a completed action. AlertUpdated keeps success

Step order is unchanged in principle: success steps first, then failed, then denied, then the
forced UserLoginFailed step, then the new conditional-access step.

Rules changed (rules/office365/)

Six rules accepted both the new and the legacy word. Each now reads one word per outcome:

  • credential_access_microsoft_365_potential_password_spraying_attack.yml and
    possible_succesfull_password_guessing_o365.yml: ["failure", "failed"] became
    ["failed", "denied"], so sign-ins refused by conditional access still count toward these
    sign-in rules.
  • dlp_policy_violations.yml: !oneOf(..., ["failure", "failed"]) became !equals(..., "failed").
  • insider_risk_indicators.yml, information_barriers_violations.yml,
    safe_links_click_patterns.yml: ["denied", "blocked"] became equals(..., "denied").

Rule history queries (afterEvents) do not filter on actionResult, so stored events keep counting.
The other 48 rules that read actionResult read success only and need no change.

Tests changed (plugins/alerts)

  • o365_action_result_test.go and testdata/o365_action_result.json: the predicate loop and 10
    cases now pin failed. The SharePoint "no ResultStatus" case now expects success. 39 new
    fabricated cases cover every changed class and its negatives, for 105 cases in total.
  • o365_action_result_rules_test.go: legacy failure and blocked stay in the outcome list only
    as negative cases. A raw conditional-access sign-in (53003) must normalize to denied and
    still match both sign-in rules.
  • testdata/o365_awareness.json: 8 failed-operation cases pin failed.

How it was validated

  • EventProcessor playground (main 8a3ade7, go-sdk v1.1.36, the same staged plugins for both
    runs). Base v11 dda9d45a and this change were run over the same inputs.
    • 369 sampled production records. 108 changed actionResult, and every change was the
      intended one:
      • 48 failure became failed. This includes 35 failed sign-ins, 32 of them from public
        addresses, which now leave the threat-intelligence lookup.
      • 34 Teams/SharePoint/OneDrive records became success.
      • 6 Power BI records became success.
      • 5 Yammer records became success and 1 compliance-cmdlet record became failed.
      • 8 blocked mail records became denied.
      • 6 compliance alert detections lost success.
    • No other field changed on any record. No value outside success, failed and denied was
      written. There were no processing errors.
    • 124 public vendor sample records (11 more are dropped by the filter's existing drop list, the
      same way in both runs), plus 13 earlier synthetic probes. They gave the same result. DLP rule
      matches and malware detections in SharePoint/OneDrive kept no value.
    • 29 fabricated fixtures (documentation address ranges) cover classes with no production
      sample: the seven conditional-access codes (denied), wrong password, locked account, a
      multi-factor interrupt and a conditional-access session expiry (all stay failed), Power BI
      IsSuccess false (failed), and Safe Links with both key spellings.
  • Rule conditions were evaluated with the go-sdk v1.1.36 CEL on the normalized playground
    output. Each of the six changed rules has at least one matching and one non-matching record.
    Across all 72 Office 365 rules, the only rule whose matches changed on production samples is
    power_bi_data_export.yml, which now also matches completed Power BI exports (2 more). It still
    needs 5 exports in 30 minutes to alert.
  • Go tests: go test ./... in plugins/alerts passes except
    TestBitdefenderActionResultRaw, which fails the same way on unchanged v11 and is fixed by the
    Bitdefender change.
  • Contract check: no actionResult value outside success, failed and denied. Unchanged
    v11 reported four failure steps.

Dashboards

The Office 365 "Result Status" panels (Liquibase changelogs 20260331005, 20260331014,
20260331015, 20260331020) aggregate actionResult.keyword. They need no change, but their
labels change: failure becomes failed, new success and denied buckets appear for the
classes above, and compliance alert detections move out of success.

Left out, and why

  • Sign-in interrupts (multi-factor prompt, registration, device check such as 50074, 50076)
    stay failed. Emptying them would make threat intelligence look them up again.
  • The clicked Safe Links host on target.domain is left out. It would add a new value that
    threat intelligence matches, so it needs its own review.
  • Mapping the mail sender address (SenderIp) to origin.ip is left out. That is a change to
    which side holds the address; this change only gives blocked mail its outcome.
  • Older sign-in records without ErrorNumber or ErrorCode get no success. This is optional,
    and only historical public samples have that shape.
  • Other parsing fixes found in the same review are follow-ups: stripping the port from Exchange
    admin client addresses, reformatting deviceTime, and ignoring placeholder user IDs.
  • Endpoint DLP enforcement modes and other Teams actions without ResultStatus need
    Microsoft's value list confirmed first.
  • safe_links_click_patterns.yml matches action ClickedSafeLink, but the Management Activity
    API writes TIUrlClickData for these records. The rule's action name is a separate fix.
  • Ship this filter together with the updated rules. Rules deployed on servers from older
    releases still compare legacy words.

References

sophos-central

fix(sophos-central): write only success, failed or denied in actionResult

actionResult must hold exactly success, failed or denied, or stay empty when the record states
no final outcome. These are the words the shared threat-intelligence gate recognizes: it skips indicator
lookups for failed and denied and looks up everything else, including failure and an empty value.

The Sophos Central filter wrote failure for authentication failures, left several stated blocks and
failures empty, and kept the remote sender of an IPS inbound block only under log.

What changed, per event type

Event type Before After
Event::ZTNA::ZTNAAuthenticationFailure, Event::Endpoint::AuthenticationFailure failure failed
Event::Endpoint::CoreAmsiBlocked ("AMSI Protection blocked a threat") empty denied
Event::Endpoint::Threat::IpsInboundDetection whose name says "traffic blocked" empty, sender only under log.ips_threat_data denied, and the sender (ips_threat_data.remoteIp, when it is a usable address) in origin.ip with geolocation
Event::Endpoint::UpdateFailure, CoreCleanFailed ("Manual malware cleanup required"), CoreRestoreFailed, NotProtected named "Failed to protect ..." empty failed

The IPS sender is mapped only on records that state the traffic was blocked, so it never reaches
threat intelligence without its outcome. The failed update, cleanup, restore and protection records
carry only the managed endpoint's own name and address; with failed the gate stops looking them up.

Rules and tests

  • No rule reads actionResult for this data type, so no rule changed. sophos_central_unknown_threat_detected
    (adversary: origin) now shows the IPS sender as the adversary.
  • plugins/alerts/testdata/sophos_central_raw.json: ZTNA cases failure -> failed; the CoreCleanFailed
    case now expects failed; eight new cases (AMSI block, IPS block with sender, IPS record without block
    wording, IPS block with an unusable sender, update failure, restore failure, "Failed to protect", and a
    web-control block that stays empty).

How it was validated

  • Local EventProcessor playground (engine main 8a3ade7, go-sdk v1.1.36), base and candidate over the
    same inputs, every field compared per record:
    • 191 real server records: 11 empty -> failed (update failure 6, cleanup failed 2, restore failed 1,
      "Failed to protect" 2); the 26 denied records and everything else unchanged.
    • 31 public example records (Elastic, SEKOIA, Microsoft Sentinel, Rapid7, Sophos and Sumo Logic
      samples): IPS inbound block -> denied plus the sender in origin.ip; AMSI block -> denied; update
      failure -> failed; nothing else changed.
    • 16 fabricated fixtures (documentation addresses): all matched their expected value, including IPv6 and
      unusable senders and classes that must stay empty.
    • No other field changed; no output value outside the three words.
  • All 20 Sophos Central rules replayed with CEL on base and candidate output: identical matches, no errors.
  • go test ./... in plugins/alerts: everything passes except TestBitdefenderActionResultRaw, which
    already fails on unchanged v11 and is fixed in the Bitdefender change.
  • Contract check: no actionResult value outside success, failed, denied (base had failure).

Left out, and why

  • Blocked web-control requests ("'' blocked due to category ...") still get no outcome. The review
    pairs this with extracting the site from the event name, which is larger parsing work and depends on an
    open decision about which side the requesting endpoint belongs on. Both should land together.
  • "Allowed" outcomes (data-loss-prevention "allow file transfer", "Peripheral allowed") need the same side
    decision before they get success.
  • Malware and PUA clean-ups stay empty, matching how removed or disinfected malware is treated in the other
    antivirus filters; one decision should cover all of them.
  • The action key check in the denied step is kept: removing it changes no real record.
  • Other "...CleanFailed" types (PUA, HitmanPro.Alert, AMSI) are follow-up: no real or public record was available.
suricata

fix(suricata): write only success, failed or denied in actionResult

actionResult is the final outcome of the action an event describes. The shared
threat-intelligence gate skips indicator lookups for failed and denied and looks up every
other value, including an empty one. The Suricata filter already wrote only success and
denied, but it left stated blocks empty, labelled a refused TCP connection success, and gave
no outcome to application records that prove the server answered. This change fixes those
classes. The filter version goes from 1.1.0 to 1.1.1.

What changed, per record class

Class (EVE event_type) Before After Why
drop (IPS drop record) empty denied Suricata writes a drop record only for a packet it dropped.
alert of a reject rule (IDS mode, verdict logging) empty denied The verdict lists the reject and Suricata sent a reset or ICMP unreachable. The key is reject-target up to Suricata 8 and reject_target from 9 (upgrade guide); the reject array exists in both, and all three are tested.
flow, TCP, SYN answered by a reset success failed Bytes flowed both ways, but only a SYN and a RST. A refused connection is a failed attempt.
flow, TCP, no completed handshake (half-open scan, or no tcp flags) success empty A TCP flow is success only when the server sent SYN-ACK (SYN in tcp_flags_tc) and the client sent ACK (0x10 in tcp_flags_ts).
flow with flow.action: accept, alert with verdict.action: accept (Suricata 8 firewall mode) empty success (same tests as pass) accept is the firewall-mode allow verdict.
http, and fileinfo carried by HTTP empty 2xx success; 401 and 403 denied; other 4xx and 5xx failed; 3xx and no response stay empty The server's answer decides, as for other web sources.
fileinfo without HTTP, file seen whole (state: CLOSED) empty success The transfer completed. OPEN and TRUNCATED stay empty.
tls with a server-side value empty success A server certificate (subject, fingerprint, serial), a JA3S hash, a resumed session, or a negotiated version other than UNDETERMINED shows the server answered. A client hello alone stays empty.

Unchanged: two-way UDP and completed TCP flows stay success; IDS alerts, flow new, dns and
anomaly records stay empty; blocked alerts and drop verdicts stay denied. The steps now run
success, then failed, then denied, so a drop always ends as denied. The outcome steps
moved above the fileinfo rename so they read the fileinfo object; no other field changes.

Rules and tests

  • No rule under rules/ reads actionResult for this data type (35 rules checked).
  • plugins/alerts/suricata_action_result_test.go now checks the predicate failed instead of
    failure, and requires the new classes.
  • plugins/alerts/testdata/suricata_action_result.json grows from 18 to 43 cases. Four existing
    TCP flow cases gain the handshake flags that real EVE flow records carry; the HTTP 200 case
    now expects success. Twenty of the 43 cases fail on the previous filter and pass on this one.

Validation

  • Local EventProcessor playground (latest engine build, go-sdk v1.1.36), previous filter against
    this one, record by record:
    • 207 public example records (EVE documentation, public integration test data and forum
      posts; 205 events, 2 stats records are dropped by design): 26 records changed, all as
      intended (5 drop to denied, 15 tls, 3 http and 3 fileinfo to success); no other
      field changed.
    • 78 fabricated fixtures (the 43 test cases plus 35 records shaped on the EVE documentation
      examples, documentation addresses only): every record got its expected value.
    • 5 earlier synthetic probes (refused TCP connection, IDS reject, firewall-mode accept,
      priority-4 alert, UDP control).
  • Every record carries success, failed, denied or no value.
  • go test ./... in plugins/alerts: all Suricata tests pass. The only failure,
    TestBitdefenderActionResultRaw, fails the same way on the unchanged branch and belongs to the
    Bitdefender change.
  • The filter contract check reports no actionResult value outside the three words.

Left out, and why

  • The no-op delete of actionResult stays: it guards the steps that follow and changes nothing.
  • pgsql and ssh records get no outcome: they need a per-protocol reading of the server's
    answer.
  • An alert with a pass or accept verdict keeps the two-way byte test. Alert records carry
    no TCP flags, so the handshake test cannot apply there.
  • TCP flows picked up mid-stream (no SYN-ACK seen) now stay empty; the handshake cannot be shown.
  • The fileinfo and ftp_data renames that turn objects into text, and the EVE host field
    mapped to target.host, are parsing issues unrelated to the outcome and belong to a separate
    change.
syslog

fix(syslog): write only success, failed or denied in actionResult

The generic CEF / LEEF filter (filters/syslog/syslog-cef-leef.yml, version 1.0.0 to 1.1.0) now
writes only the three outcome words the threat-intelligence step recognizes: success, failed
and denied. When a record states no final outcome, it writes nothing. Threat intelligence skips
indicator lookups for failed and denied and looks up every other value, so a wrong word either
raises a false "Known Malicious IP Detected" alert or hides a real one.

filters/syslog/syslog-generic.yml is unchanged: it writes no outcome and no address.

What changed, per class

Both the CEF stage and the LEEF stage read the vendor act key. The same lists now apply to both:

Class (act value) Before After
allow, accept, permit, permitted, pass, success, ok (and their forms) success (permitted: nothing) success
error, fail, timeout failure (not a recognized word) failed
failed, failure, failed auth, authentication failed denied (failure: nothing) failed
block, deny, drop, reject, quarantine (and forms) denied denied
refuse, reset, reset-both/-client/-server, IDS:Reset, clear_session, prevent nothing denied
invalid (a dropped packet in an invalid connection state) denied denied (kept)
login, logon, connect, start, open, session start, signed in success nothing (an attempt, not an outcome)
Alerted, Detected success nothing (a detection, not a completed action)
closed, teardown, terminated denied nothing (the end of a completed connection is not a refusal)
  • The CEF outcome key (success, failure, /Success, /Failure, and close variants) is now
    read in both stages, after act, so it wins. Example: a FortiGate administrator login with
    act=login outcome=failed was success and is now failed.
  • Trend Vision One: act arrives as list text such as ['Quarantine']; a list containing
    Quarantine or Block now gives denied. Account Audit Log records (event 900003) carry the
    result in cn1 labelled Result; per Trend's CEF account audit log mapping (1: Success,
    0: Fail) they now give success or failed.

Rules and tests

No rule reads actionResult for this data type (the three rules under rules/syslog/cef/ read
CEF header and extension fields only), and no test in plugins/alerts loads these filters. No
rule or test changed.

Validation

EventProcessor playground (main 8a3ade7, go-sdk v1.1.36), base v11 dda9d45a against this
change, same inputs:

Inputs Records Changed Result
Server samples 149 7 6 Trend Account Audit records now success, 1 Trend quarantine record now denied
Public vendor examples (19 sources) 125 7 IPS resets, a refused connection and a cleared session now denied; a permitted web request and an authentication success now success; a failed admin login now failed
Synthetic probes 10 4 Detected and Alerted now empty; closed now empty; failed now failed
New fabricated fixtures 100 71 every act word in both stages, outcome-key cases, Trend list and audit cases

For every record: the new value matches an independent model of the new steps, no other field
changed, and no value falls outside the three words. The 100 fixtures also match hand-written
expectations for both base and candidate. No LEEF example with act exists publicly, so the LEEF
half is proven by 37 fabricated LEEF 2.0 fixtures. Fixtures use documentation address ranges
(RFC 5737) and follow the ArcSight CEF standard, IBM's LEEF 2.0 layout and Trend's CEF pages for
Observed Attack Techniques and Account Audit logs.

The contract check reports no actionResult value outside success, failed and denied (before:
two errors for failure). go test ./... in plugins/alerts passes except
TestBitdefenderActionResultRaw, which already fails on unchanged v11 and is fixed separately.
The three syslog rules match the same records before and after.

Left out

  • Record classes that only a parsing change would reach: LEEF 1.0 with tab separators and LEEF
    2.0 with a hex delimiter do not parse in this filter at all, and CEF values with spaces are cut
    at the first space. These are follow-up parsing work, not outcome fixes.
  • Products that reach this data type only because of routing (for example PAN-OS GlobalProtect
    or AIX lines arriving as plain syslog) need their own filters; the plain stage stays without an
    outcome.

Why invalid and timeout are mapped this way

  • invalid stays denied. Several firewalls use it for a packet dropped because its connection
    state was invalid. Threat intelligence looks up an empty value, so emptying it would create new
    false alerts on traffic the firewall already dropped.
  • timeout becomes failed (it was failure, a word the gate does not recognize). A timed-out
    attempt did not complete, which is what failed means. Note for review: FortiGate also uses
    timeout for an allowed session that later timed out; no sample here shows that case.

🤖 Generated with Claude Code

@kryonsx

kryonsx commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

Per-filter details (4 of 4): utmstack, vmware-esxi, wineventlog

utmstack

UTMStack platform logs: write only success, failed or denied in actionResult

Why

The threat-intelligence step skips indicator lookups only when actionResult is failed,
denied or the older blocked. A filter should write exactly success, failed or denied, or
nothing when the record states no final outcome. The utmstack filter wrote no outcome at all. The
backend's panel sign-in lines state one, so they should carry it.

What changed (filters/utmstack/utmstack.yml, version 1.0.1 to 1.0.2)

The line format comes from the v11 backend source:

  • logback-spring.xml writes timestamp, severity, msg and the structured args.
  • AuditAspect and ApplicationEventService write messages as <context>: <text>.
  • LogContextBuilder fills args.
Backend line (msg) Before After
UserJWTController.authorize: Login successfully completed for user '<login>' none success; args.username becomes origin.user (not when empty or anonymous)
UserJWTController.authorize: Bad credentials none failed; no user, because args.username is anonymous before sign-in
Every other line (sign-in attempt, two-factor challenge, other controllers, service logs) none none

args.remoteAddr is not promoted to origin.ip. The backend reads it from
request.getRemoteAddr(), and the panel runs behind its own nginx. The value is therefore the
proxy's address, not the client's, as LoginAttemptService.getClientIP explains. Promoting it would
make every sign-in appear to come from the proxy. The records carry no address, so threat
intelligence is not affected either way.

Rules and tests

  • No rule reads the utmstack data type.

  • plugins/alerts/testdata/filter-contracts/core.json: seven new raw cases with fabricated values:

    • a successful sign-in, with its user;
    • a successful sign-in whose args.username is anonymous, with no user;
    • Bad credentials;
    • the sign-in attempt line;
    • the two-factor challenge line;
    • Bad credentials from another controller;
    • the success text inside another message.

    Every case also asserts that no origin.ip appears.

  • go test ./... in plugins/alerts: every test passes except TestBitdefenderActionResultRaw.
    That test fails identically on unchanged v11 (go-sdk v1.1.36 keeps underscores) and is fixed
    separately.

How it was validated

All runs used the local EventProcessor playground (main 8a3ade7, go-sdk v1.1.36), with the v11
filter and this filter over the same inputs, comparing every field of every record:

  • Public platform log examples (17) and EventProcessor output (111): no record changed.
  • Fabricated backend lines (10): three changed as intended (two success, one of them with
    origin.user; one failed). The other seven, which include a line without args, kept no
    value.
  • No other field changed, no address was added, and no value outside the three words appears.

Left out, and why

  • The client address: the backend logs the proxy's address (see above). It can be mapped once the
    backend logs the client address it already computes for lockouts.
  • Other sign-in outcomes the backend writes are follow-ups outside this finding:
    • Authentication blocked: IP ... exceeded login attempt threshold (lockout, would be denied);
    • Authentication failed: user '...' not found;
    • the two-factor verification result (TfaResource.verifyCode: Login successfully completed,
      TFA invalid for user ...).
  • Service log lines that start with an icon before their JSON do not parse today. That is a parsing
    fix, separate from the outcome.
vmware-esxi

fix(vmware-esxi): write only success, failed or denied in actionResult

The VMware ESXi filter (filters/vmware/vmware-esxi.yml) now writes only success, failed or denied to actionResult, or leaves it empty when a record states no final outcome. These are the three words the EventProcessor threat-intelligence step reads: it skips the address lookup for failed and denied and looks up every other value, including an empty one.

Before this change, two steps wrote failure, a word the engine does not read. The documented ESXi failed sign-in (Cannot login) and the SSH sign-ins got no value at all.

What changed, by record class

Class Before After
Any message with authentication failed, or authentication of user ... failed failure failed
hostd Event <n> : Cannot login [user ]<user>@<address> and bare Event <n> : Cannot login nothing failed, plus origin.user and origin.ip (only when the address is a valid IP)
hostd Event <n> : Cannot login user <user>@<address>: no permission (the account has no permission on the host) nothing denied, plus origin.user and origin.ip
sshd Accepted <method> for <user> from <address> port <n> nothing success, plus origin.user, origin.ip, log.authMethod and log.authResult
sshd error: PAM: Authentication failure for [illegal user ]<user> from <address> nothing failed, plus origin.user and origin.ip
hostd Event <n> : User <user>@<address> logged in as <client> success success (unchanged)
permission denied text denied denied (unchanged)
hostd Accepted password / Rejected password for user ..., pam_unix authentication failure; ... rhost=, sshd Failed ... and Invalid user, vobd or hostd SSH session was opened / SSH login has failed, account locked notices, logouts nothing nothing (unchanged)

Notes:

  • One record per attempt. Each sign-in attempt gets its outcome on exactly one line, so a sign-in is counted once and threat intelligence checks it once:
    • Completed vSphere sessions already use the logged in as event. Refused vSphere sign-ins now use the matching Cannot login event.
    • The paired password-check lines (Accepted password / Rejected password) keep no result. This follows the filter's existing design and the existing password-check-not-final test.
    • SSH uses sshd's own lines. ESXi signs SSH users in through keyboard-interactive/PAM, and sshd writes the PAM failure line for every refused password. The other lines of the same attempt stay empty and get no address.
  • Addresses are added only together with an outcome. Refused sign-ins are now failed or denied, which threat intelligence skips. Completed SSH sign-ins are success, so they are checked, the same way completed vSphere sessions already are.
  • Order of steps. The value steps keep the filter's pattern where the first value wins (a !exists("actionResult") guard). Failed and denied run before the success steps.
  • SSH steps. They require log.process to be sshd, so the hostd Accepted password for user line is not matched.
  • The filter version is now 3.2.0.

Rules and tests

  • No rule under rules/ reads actionResult for this data type, so no rule changes.
  • The 14 ESXi rules were evaluated with the engine's CEL library over the old and new filter output for every input below. Every rule matched exactly the same records before and after.
  • plugins/alerts/vmware_esxi_action_result_test.go and testdata/vmware_esxi_action_result.json:
    • The test now checks the word failed, and the two cases that expected failure now expect failed.
    • The model can set log.process, and it checks that the new scratch fields are removed.
    • 11 fabricated cases were added: Cannot login with an address, bare, and with no permission; Rejected password; sshd Accepted over IPv4 and IPv6; the PAM failure for a valid user and for an illegal user; and the pam_unix, SSH session was opened and SSH login has failed lines, which must stay empty.
    • The existing password-check-not-final case now runs with log.process set to hostd.
  • go test ./... in plugins/alerts gives 118 passed, 12 skipped and 1 failed. The failure is TestBitdefenderActionResultRaw. It already fails on unchanged v11, because go-sdk v1.1.36 keeps underscores in field names, and it is fixed in the Bitdefender change.

How it was validated

The old and new filters were run in the local EventProcessor playground (engine main 8a3ade7, go-sdk v1.1.36) with all v11 filters loaded. Every input was compared field by field.

  • 100 real records from two servers. 8 cluster-agent authentication failed records change from failure to failed. The other 92 are identical in every field, including the 3 logged in as sessions that stay success.

  • 172 public example records from Broadcom knowledge-base failure examples, ESXi 8 forensic write-ups, Sekoia ESXi parser tests and Elastic vSphere test logs. 7 change:

    • 3 Cannot login become failed.
    • 1 Cannot login ... no permission becomes denied.
    • 2 sshd Accepted become success.
    • 1 PAM authentication failure becomes failed.

    The other 165 are unchanged in every field.

  • 16 fabricated fixtures using documented formats and documentation addresses only. They cover IPv6, a vSphere SSO user name that contains @, an address given as a host name, the illegal-user PAM form, and negatives (sshd Failed ..., Invalid user, the hostd password check, the lock notice). All 16 match their expected value.

  • In every run, no record carries a value outside the three words, and no record outside the changed classes changed any field.

  • The review contract check no longer reports failure. The remaining findings are about origin.hostname and adversary.hostname; they exist on unchanged v11 and are a separate issue.

Left out, and why

  • vCenter RFC 5424 records (finding F2). vCenter sign-ins, including the SSO Failed login ... from <address> record, cannot get an outcome until the RFC 5424 header is parsed. That is large parsing work, and it would start several log.message rules on vCenter records. It is follow-up work.
  • Narrowing the permission denied step to access decisions (finding F13). The narrowing would turn records that are denied today into empty ones. The risk it guards against only appears once vCenter records are parsed (F2), so it belongs with that change.
  • success on hostd Accepted password for user <user> from <address> (finding AR-ESXI-01). This password check belongs to the same session as the Event <n> : User ... logged in as record, which already carries success and the address; the real and public examples share the session id. Marking both would count each sign-in twice, and it would contradict the filter's documented design and its password-check-not-final test. If the maintainers want it, they need to decide which line carries the session.
  • failed on the vobd account locked notice. It is raised once, when the lock starts, after the failed attempts have already been recorded. Attempts against a locked account are reported by their own sign-in records.

References: VMware/Broadcom "ESXi log message formats" and the knowledge-base articles on Cannot login and Rejected password for user in hostd.log; OpenSSH auth_log and sshpam messages; ESXi auth.log and vobd.log SSH events.

wineventlog

fix(windows): write only success, failed or denied in actionResult

Why

actionResult holds the final outcome of the action a Windows event describes. The shared
threat-intelligence gate skips indicator lookups only when the value is failed, denied (or the
older blocked) and looks up everything else, including an empty value. The Windows filter wrote
failure for failed sign-ins, so every failed Windows sign-in from a listed address would raise
"Known Malicious IP Detected" even though the sign-in failed. Older deployed filters wrote failed
for these records, so this is a regression once the v11 filters ship. Several other classes that
state a clear outcome (Kerberos ticket requests, Audit Failure records, SQL Server logins, Defender
blocks) carried no value at all.

The rule used here: a filter writes exactly success, failed or denied, or leaves the field
empty when the record states no final outcome.

What changed (filters/windows/windows-events.yml, version 3.2.0 -> 3.2.1)

Class Before After
4625 failed logon, 4771 Kerberos pre-authentication failed, 4776 NTLM validation with non-zero Status failure failed
4768 TGT request, 4769 service ticket request, Status 0 empty success
4768 / 4769 with non-zero Status empty failed
4770 service ticket renewed (written only when granted) empty success
SQL Server 18453 / 18454 login succeeded (provider MSSQLSERVER or MSSQL$<instance>) empty success
SQL Server 18456 login failed empty failed
Microsoft Defender 1121 (attack surface reduction blocked an operation) empty denied
Security log Audit Failure 4656, 4661, 4663, 4673, 4674, 4675, 4888 (access or privilege refused) and 5031 (Windows Firewall blocked an application) empty denied
Any other Security log Audit Failure record with no value yet (for example failed 4723/4724 password change or reset, 6273 NPS refusal) empty failed
4729, 4731, 4733, 4735, 4737, 4738, 4741, 4742, 4743, 4781 logged as Audit Success empty success

Unchanged on purpose: 4648 (explicit credentials; only an attempt, the target logs the result) and
4740 (lockout) stay empty; Defender detections 1116/1117, WFP listen/bind 5154/5158 stay empty; all
existing success and denied classes keep their value. The two Audit Failure steps are guarded by
!exists("actionResult"), so they never override a value set by an earlier step.

References: Microsoft Learn security auditing pages for events 4625, 4656, 4661, 4673, 4674, 4675,
4723, 4724, 4768, 4769, 4770, 4771, 4776, 4888, 5031 and the account and group management events;
Microsoft Learn MSSQLSERVER_18456; Microsoft Defender attack surface reduction event 1121; Elastic
Winlogbeat exported fields.

Rules and tests

  • Rules: no rule reads actionResult for wineventlog, so no rule changes. All 48 Windows rules were
    evaluated over the base and candidate output: identical matches.
  • plugins/alerts/windows_action_result_test.go: outcome predicates now test success, failed,
    denied.
  • plugins/alerts/testdata/windows_action_result.json: two cases pinned failure, now failed;
    24 new fabricated cases cover every added class plus negatives (other SQL provider, Defender
    detection, account change without the Audit Success keyword, 4648).
  • plugins/alerts/testdata/filter-contracts/windows.json and testdata/windows_raw.json: the failed
    NTLM cases now pin failed.

Validation

  • Local EventProcessor playground (EventProcessor main 8a3ade7, go-sdk v1.1.36), base and candidate
    over the same inputs: 459 real Windows records, 91 public example records and 10 fabricated
    fixtures (documentation address ranges). Every input produced one output in both runs.
  • Per-record comparison: 141 records changed, each exactly as intended for its class (42
    failure->failed, 59 empty->success, 26 empty->failed, 14 empty->denied, fixtures
    included); no other field changed on any record; no record lost a value; no value outside the
    three words.
  • Failed Kerberos requests, failed Kerberos pre-authentication and failed logons that carry a public
    address now get failed and are skipped by the current gate.
  • go test ./... in plugins/alerts: everything passes except TestBitdefenderActionResultRaw,
    which also fails on unchanged v11 (go-sdk v1.1.36 keeps underscores in field names) and is fixed by
    the Bitdefender change.
  • Contract check: no actionResult value outside success, failed, denied (the base reports the
    two failure steps).

Left out

  • Moving the 4648 address to the target side: side placement is a separate change.
  • Winlogbeat-shaped records keep Status and keywords under winlog.*, which these steps do not read,
    so Winlogbeat 4768/4769 and Audit Failure records stay empty (unchanged); 4625/4771 in that shape
    do get failed. Reading winlog.* is parsing work for a follow-up.
  • Defender 1126 (network protection block): no example seen yet.
  • 5145 (share access check): the filter drops it, so it cannot carry a value.
  • Promoting the SQL Server client address: collector change, separate.
  • Other group-management codes (4727, 4730, 4734, 4754, 4755, 4757, 4758) and successful 4723/4724:
    not observed in the data reviewed; not added.

🤖 Generated with Claude Code

@kryonsx
kryonsx marked this pull request as ready for review September 29, 2026 22:31
@kryonsx
kryonsx requested a review from a team September 29, 2026 22:31
@kryonsx
kryonsx merged commit ee52c91 into utmstack:v11 Sep 29, 2026
3 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant