Repository navigation
Action result: align every filter with the values the engine expects (success, failed, denied) - #2769
Conversation
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)
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)
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-asaantivirus-bitdefender-gzSummaryThe Bitdefender GravityZone filter ( Changes per class
Filter version 3.3.0 becomes 3.3.1. Rules
Tests (
|
| 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.ymlandeset_quarantine_failures.ymltest
failedinstead offailure(v1.0.1). The console rule can now also count refused logins
reported as "Access denied".exploit_detection_events.ymlandsuspicious_powershell_activity_blocked.ymlare unchanged but
readdenied, 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 pinnedfailurevalues arefailed; the documented
deletion case and the heuristic remediation case expectdenied; 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 inactionResult(plus the console marker on
3 refused logins) and each as intended: 36 remediations and 5 terminated downloads todenied,
5 refused logins tofailed, 2 block phrases todenied, 2 failures tofailed. 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 ./...inplugins/alerts: everything passes exceptTestBitdefenderActionResultRaw,
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
actionResultvalue outsidesuccess,failed,denied
(base v11 reported twofailurevalues). - 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
actionkey (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 fromdetail, 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): readsfailedinstead offailure.
Without this the rule would stop matching failed LOLBin operations. The other four Kaspersky
rules that readactionResulttestdeniedorsuccessand are unchanged.plugins/alerts/kaspersky_action_result_test.go: the explicit failed operation expects
failed; the outcome predicates also check thatfailureandblockednever 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 withfailed,
lolbins negative without an outcome, native and CEF block types withdenied).
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
actionResultchanged; 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 ./...inplugins/alerts: passes exceptTestBitdefenderActionResultRaw, which already
fails on unchanged v11 (go-sdk v1.1.36 keeps underscores) and is fixed by the Bitdefender change.- Contract check: no
actionResultvalue outsidesuccess,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 withdenied
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
actionResultfor this data type, so no rule changed. plugins/alerts/sentinel_one_action_result_test.go: the predicate check now testssuccess,failed,
denied.plugins/alerts/testdata/sentinel_one_action_result.json: four casesfailure->failed; the
role-assigned case now expectssuccess; 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.
- 29 real server records: 14 console changes empty ->
- All 19 SentinelOne rules replayed with CEL on base and candidate output: identical matches, no errors.
go test ./...inplugins/alerts: everything passes exceptTestBitdefenderActionResultRaw, which
already fails on unchanged v11 (a Bitdefender field-name expectation) and is fixed in the Bitdefender
change.- Contract check: no
actionResultvalue outsidesuccess,failed,denied(base hadfailure).
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.ymland
rules/cloud/aws/credential_access_aws_iam_assume_role_brute_force.ymlnow testfailed
instead offailure; the two matching history markers in the filter changed the same way.
The other 73 AWS rules testsuccessand need no change.plugins/alerts/aws_action_result_test.go: the twofailureexpectations are nowfailed;
nine new cases cover SwitchRole, the Identity Center steps and Grafana 200/412/login events.plugins/alerts/testdata/aws_raw.json: eightfailureexpectations are nowfailed.plugins/alerts/testdata/filter-contracts/aws.json: two new fixtures pinfailedfor a failed
console sign-in andsuccessfor 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
outsidesuccess,failed,deniedor empty. Only the recognised Grafana events gained
fields other thanactionResult(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 ./...inplugins/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
actionResultvalue outside success, failed, denied
(it reportedfailureon v11).
Left out, and why
- Allowed VPC endpoint calls (
AwsVpceEventwithout an error): left empty. AWS documents
VpceAccessDeniedas 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 getssuccess, and no
AWS rule filters on event type, sosuccesshere 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 asUpdateDistribution2020_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 arefailedfor now; a follow-up could map
them todenied. - 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.ymlnow testsfailed. The filter's own
password-spray correlation marker, which the rule's history query reads, testsfailedtoo.
No other azure rule reads a changed value (31 readsuccess, 1 readsdenied).plugins/alerts/testdata/azure_action_result.json: 10 pinsfailuretofailed, WAF
Detectedpinned 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 pinsfailuretofailed, WAFDetectedpinned
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.jsonpins 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:
failure13 tofailed; WAFDetected1 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.
- Real records:
- 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 ./...inplugins/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
actionResultvalue outside the three words.
Left out, and why
- Key Vault 401 authentication challenges stay
denied, and Entra sign-in interrupts (50074,
50076 and similar) stayfailed: making them empty would send them to threat intelligence,
which looks up empty values. - Application Gateway WAF
Allowedkeepssuccess, and App Service access-restrictionAllowed
and Front Door WAFAllowget nothing: an allow decision states no answer, and whether it
counts as success is a maintainer decision. - Application Gateway WAF
Matchedrows keep no value (a rule hit, not a final result). In
detection mode the request passed, so they are notdenied; the lookup on them is a gate
question, not a filter one. - Defender
ConnectionRequestand 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
failurewithout a refusal code staysfailed. - 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/*) readsactionResult, 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 twoSW_DAIexpectations move fromblocked
todenied; 40 new per-class cases (positive and near-miss); a new test,
TestCiscoSwitchActionResultWords, checks that everyactionResultstep and every fabricated
line uses onlysuccess,failedordenied.plugins/alerts/testdata/cisco-switch/raw.jsonandexpected.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.jsonre-recorded with EventProcessor 8a3ade7 (go-sdk v1.1.36). On the 44 earlier
lines the only differences are the intendedactionResultvalues.filters/audits/cisco-switch.md: deferred item D-10 (failedandblocked) 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.pyagainst the 8a3ade7 binaries: 79 events, zero
parser errors, every stored field as inexpected.json, 6 alerts each from its intended rule.go test ./...inplugins/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
actionResultvalue outsidesuccess,failedand
denied(the base reportedblocked). Its remaining findings, the severity words
high/medium/lowand a rule readinglog.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_ACLor
SW1), which still leaves such lines with a wronglog.severityandlog.facilityMnemonicand
noseverity; 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/readsactionResultfor thecrowdstrikedata type (checked with the
review catalog andgrep), so no rule changes. The brute-force rule readslog.eventSuccess,
which is unchanged. plugins/alerts/crowdstrike_action_result_test.go: expectsfailedinstead offailure,
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 thansuccess,failedordenied.plugins/alerts/testdata/crowdstrike_action_result.json: 6 cases changed tofailed; 10 new
fabricated cases: API 401, API 403, API 403 withSuccessfalse (alldenied); 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.goneeds no change (it never assertsactionResult).
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
failuretofailed; 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 todenied; 1 API 404 changed tofailed; 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 becomedenied; 404, 429, 500 and a
failed sign-in becomefailed; six prevention variants becomedenied; 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,deniedor no value; no field other
thanactionResultchanged; 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 ./...inplugins/alerts: every CrowdStrike test passes (25 outcome cases). The only
failure isTestBitdefenderActionResultRaw, 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
actionResultvalue outsidesuccess,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 ofRuleAction; the Elastic and SEKOIA-IO
parsers both read2as blocked and Elastic reads1as 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.
Writingdeniedthere 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;
successwould
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 inorigin. Moving them totargetis a separate side-placement change that
the CrowdStrike rules usingoriginmust follow.
References:
- Elastic CrowdStrike integration,
packages/crowdstrike/data_stream/falcon(ingest pipelines and
test logs): https://github.com/elastic/integrations/tree/main/packages/crowdstrike - SEKOIA-IO intake formats, CrowdStrike Falcon: https://docs.sekoia.com/integration/categories/endpoint/crowdstrike_falcon/
- CrowdStrike, "How to Manage a Host Firewall with CrowdStrike" (monitor mode and watch mode):
https://www.crowdstrike.com/blog/tech-center/manage-host-firewall/
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
actionResultfor this data type, so no rule changed. plugins/alerts/deceptive_bytes_filter_test.go: newTestDeceptiveBytesOutcomeAndSourceAddress
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 withdeniedor a readable non-block action. - 5 fabricated CEF shapes from the earlier review: 1 changes to
deniedwith 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.
- 16 fabricated CEF and key=value fixtures (documentation addresses): all matched their expected value;
- All 16 Deceptive Bytes rules replayed with CEL on base and candidate output: identical matches, no errors.
go test ./...inplugins/alerts: everything passes exceptTestBitdefenderActionResultRaw, which
already fails on unchanged v11 and is fixed in the Bitdefender change.- Contract check: no
actionResultfinding; 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
actionResultforfirewall-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
acceptedsteps were removed and eight steps were added or split. - New
TestCiscoASAActionResultMapping. It runs everyactionResultstep 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: 22acceptedvalues are removed. The two 113015 lines are nowfailed. The 113042 line is nowdenied.filters/audits/cisco-asa.mdrecords 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
acceptedbecome no value,
16 becomesuccess, 27deniedbecomefailed, and 5 unlabelled denies becomedenied.
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): 94acceptedBuilt and teardown records become no value, one 109201 becomes
success, and one 109203 becomesfailed. 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: noactionResultvalue outside success, failed and denied (there were 51 before).go test ./...inplugins/alerts: 119 tests pass and 12 skip.TestBitdefenderActionResultRawfails 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
originandtargetis a separate change, for
example the client address of 113015, which sits intarget.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
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-xgfirewall-cisco-firepowerfix(firewall-cisco-firepower): write only success, failed or denied in actionResultThe Firepower filter ( After this change the filter writes exactly What changed, per class4300xx security events (Firepower Threat Defense)
LINA messages
Rules and tests
ValidationEventProcessor playground at commit 8a3ade7 (go-sdk v1.1.36), base v11
Left out, and why
Cisco references: Secure Firewall Threat Defense syslog messages firewall-fortigate-trafficfix(fortigate): write only success, failed or denied in actionResultThe FortiGate filter ( What changed, per record class
The outcome block now runs the success steps first, then the failed steps, and the denied step last, Rules and tests
Validation
Left out, and why
firewall-fortiwebFortiWeb: write only success, failed or denied in actionResultWhyThe threat-intelligence step in the EventProcessor ( Before this change the FortiWeb filter wrote only What changed (
|
| 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, andHTTP_retcodefrom older firmware. The vendor field
reference describes it as "The HTTP return code" (FortiWeb Log Message Reference, Header and
body fields). The quotedkey="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
msgis 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
failedand is skipped. - A completed admin action and an answered traffic request are
successand are looked up. - A 401, 403 or blocked CORS request is
deniedand is skipped.
Rules and tests
- No FortiWeb rule reads
actionResult, and all ten rules requirelog.typeattack. 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_retcode403, 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
Alertattack that carries a return code and still gets no value.
- traffic return codes 200, 401,
fortiweb_contract_test.goandtestdata/filter-contracts/fortiweb.jsonpin no changed value
and are unchanged.go test ./...inplugins/alerts: every test passes exceptTestBitdefenderActionResultRaw.
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
keepdenied. - 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:
failedwith its address; - 7 traffic logs with 2xx:
success; - 1 traffic log with 500:
failed; - 1
Return_403_error:denied.
- 15 admin successes:
- 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
actionResultchanged, apart fromorigin.ipon admin event
logs, and no value outside the three words appears.
Left out, and why
- Attack logs with
AlertorErase(the WAF logged and passed the request) keep no value.
Whether a passed request should besuccessneeds a maintainer decision. FortiWeb attack logs
carry no HTTP return code that could settle it. RedirectandSend HTTP Responseactions 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 toorigin.userand the event
priority toseverityis not part of the outcome and is left for a follow-up.
References:
- FortiWeb Log Message Reference, Header and body fields:
https://docs.fortinet.com/document/fortiweb/7.2.2/log-message-reference/578387/header-body-fields - FortiWeb Log Message Reference, event 10000017:
https://docs.fortinet.com/document/fortiweb/7.6.0/log-message-reference/434863/10000017 - FortiWeb Log Message Reference, traffic:
https://docs.fortinet.com/document/fortiweb/7.6.0/log-message-reference/848761/traffic - FortiWeb Log Message Reference, attack 20000042:
https://docs.fortinet.com/document/fortiweb/7.6.0/log-message-reference/412795/20000042
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_failurenow writesfailedinstead offailure.
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 firstPeer IP=,
so the peer, not the RADIUS server, becomesorigin.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 allas 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_blockevents.- Switch guards that blocked a packet: "Blocked ARP Packet from ...", "Blocked RA Packet from ...",
"Blocked DHCP Packet from ...". bridge_anyconnect_client_vpn_firewalllines whose pattern starts withdeny.
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 closingip_flow_endrecord
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_firewalllines whose pattern starts withallow.client_vpn_connectandanyconnect_vpn_connect("user id '...' local ip ... connected from
..."): the VPN session came up. The same step maps the user toorigin.user, the remote address
toorigin.ipand the assigned tunnel address tolog.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 inlog.connectionState.
Parsing added with the outcome
- The three header patterns now accept the MX18.101+ groups
ip_flow_start,ip_flow_endand
bridge_anyconnect_client_vpn_firewall. Before, these lines produced no field at all. They now
get source and destination address and port, protocol,log.eventTypeand 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:
- https://documentation.meraki.com/General_Administration/Monitoring_and_Reporting/Syslog_Event_Types_and_Log_Samples
- https://documentation.meraki.com/General_Administration/Monitoring_and_Reporting/Syslog_Server_Overview_and_Configuration
Filter version 3.2.0 to 3.3.0.
Rules and tests
- No rule for
firewall-merakireadsactionResult, 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_forcenow 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 expectsfailed; the
"Cellular up" case expects no value; 20 new cases cover the RADIUS forms, VPN connects, a
disconnect and ananyconnect_vpn_connection_successrecord (both stay empty), numeric patterns
(native and legacy header), the bridge group,ip_flow_startandip_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 failurefailed, EAP failurefailed, numeric deny, numeric allow,ip_flow_start
success,ip_flow_endand the cellular notice without a value).plugins/alerts/meraki_contract_test.go: newTestMerakiActionResultVocabularychecks that every
actionResultstep writes one of the three words and that theblockedstep 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 isolatedip_flow_endcontrol).
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: 2failuretofailed; 3 site-to-site failures,
2 EAP failures tofailed; 3 numeric-deny flows, 5 switch guards, 1 content-filtering block to
denied; 1 numeric-allow flow, 6ip_flow_start, 1 bridge allow, 5 VPN connects, 3 EAP
successes tosuccess; 1 cellular notice fromsuccessto no value; 5ip_flow_endand 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_firewallandcellular_firewallgroups and on the legacy header, bridge deny,
IPv6 and relayedip_flow_start, relayedip_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 exceptTestBitdefenderActionResultRaw,
which fails the same way on the unchanged base (unrelated to Meraki). - The contract check reports no
actionResultvalue outsidesuccess,failed,denied(the base
reportedfailure).
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 withsuccess; bridge denies withdenied.
ip_flow_endarrives 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
successonurlsrecords: Meraki says every HTTP GET request produces aurlsline and that
content-filtering events travel in the same role, but not whether a blocked request also produces
a plainurlsline. That needs checking on a device first; a wrongsuccesson 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_disconnectandanyconnect_vpn_session_manager, andUser=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 onip_flow_*(translated_src_ip,
translated_dst_ip,translated_port) are not mapped; "Blocked DHCP server response" and the
WPA failed-authentication form ofdisassociation(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 offailure. - 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 prefixaction:drop,action:rejectoraction:tarpit, or a block word as a whole word
(drop, deny, block, reject, tarpit, with their past forms), givesdenied. A prefix
action:acceptor an allow word (accept, allow, permit) givessuccess: 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.userandlog.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 keeporigin.ip(they already had the outcome). - Lines with a
/system loggingprefix ("mk_R1: login failure for user ..."): the prefix is
split intolog.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
countsactionResultfailedinstead offailure.- No test in
plugins/alertspins 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 fromfailuretofailed(5 of them also gain their address), 2 login
failures behind a logging prefix from nothing tofailedwith their address, 8 successful
logins tosuccess, 10 firewall lines tosuccessfrom an allow prefix and 7 todeniedfrom
a block prefix. No value outsidesuccess,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 carriesfailed, so its follow-up search counts
them. go test ./...inplugins/alerts: everything passes exceptTestBitdefenderActionResultRaw,
which fails the same way on unchanged v11.- Contract check: no
actionResultvalue outside the three words (the base reportedfailure).
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.ymlcomparesprotocolwith "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:dropand 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
panwintegration test data
(packages/panw/data_stream/panos/_dev/test/pipeline/, revision354ff4c9), which include
THREATdrop-packet, GlobalProtectportal-preloginandgateway-tunnel-latencyrecords. - go-sdk Standard Event Schema (
actionResult), EventProcessorplugins/feeds/main.goat 8a3ade7
(the words the threat-intelligence step skips).
Rules and tests
rules/paloalto/pa_firewall/panos_admin_brute_force.yml(v2.0.1): testsactionResultfailed
instead offailure. Without this change the rule would stop matching once the filter ships.ioc_threat_intel_match.ymlandioc_threat_intel_match_server_to_client.ymltestdeniedand
need no text change;drop-packetthreat records with a listed category now reach them.plugins/alerts/testdata/paloalto_raw.json: 14 fixtures pinfailedinstead offailure; 14 new
fabricated fixtures cover pre-login and notice records (empty), portal-auth success, portal-prelogin
failure, DNS security and command-and-controldrop-packet(denied, the latter a positive for
ioc_threat_intel_match), TRAFFICdrop-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:
25failuretofailed(18 real, 1 public, 6 fixtures); 11 GlobalProtect pre-login and notice
records lostsuccess(3 real, 3 public, 5 fixtures); 6drop-packet/drop-icmprecords became
denied(3 public, 3 fixtures); 8 IKE failures becamefailed(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 thansuccess,failedordenied. - Rule conditions evaluated with the go-sdk CEL engine on base and candidate output: the brute-force
rule matches the same 12auth-failrecords before and after (the old rule text would match 0
after the filter change);ioc_threat_intel_matchgains only the fabricateddrop-packet
command-and-control fixture; the server-to-client variant is unchanged. go test ./...inplugins/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
actionResultvalue outsidesuccess,failed,denied(unchanged v11
reports fourfailurewrites).
Left out, and why
- TRAFFIC
allowgivessuccesswhether 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-logoutand
gateway-config-releasekeepsuccess; they were not part of this review's findings. panos_dns_security_alertsand its history marker do not list thedrop-packetaction, so DNS
securitydrop-packetrecords 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"):successfor Accepted,failedfor Failed, withorigin.ip,origin.port,
origin.user, and the vendor words inlog.authEventandlog.authMethod. - webConfigurator (
php-fpm, "Successful login for user 'X' from: ADDRESS" and
"webConfigurator authentication error for user 'X' from: ADDRESS"):
successorfailed, withorigin.ipandorigin.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."):successorfailed, withorigin.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
(36denied, 29success). Public lines: 2 webConfigurator loginssuccess, 1 webConfigurator
errorfailed, 1 SSH failurefailed, 1 sshguard blockdenied, 1 OpenVPNsuccess, 1 OpenVPN
failed. No value outsidesuccess,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 ./...inplugins/alerts: everything passes exceptTestBitdefenderActionResultRaw,
which fails the same way on unchanged v11.- Contract check: no
actionResultfinding.
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
failedinstead offailure.
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"orfw_action="mgmt". Thefw_actiondrop/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_actionkey: 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:
- https://www.sonicwall.com/techdocs/pdf/SonicOS-X_7.0.1_LogEvents_ReferenceGuide.pdf
- https://www.sonicwall.com/techdocs/pdf/sonicos-6-5-4-log-events-reference-guide.pdf
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 onfailedin its follow-up search.- The other SonicWall rules read
denied, which did not change spelling. One effect to note:
gateway_antivirus_detection.ymlnow also matches 809 anti-virus alerts that say "blocked.",
because those records now carrydenied. That is the class the rule is written for. plugins/alerts/testdata/sonicwall_admin_auth_raw.json: existing cases expectfailed; five new
cases cover a bad-credential login logged withfw_action="forward"(must befailed, 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. 21failurebecame
failed; 14 records with no value becamefailed; 7 becamedenied; 6 becamesuccess.
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
(3failuretofailed, 5 management requests tosuccess, 3 older-format drops todenied);
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 endsfailedordeniedinstead ofsuccess, and a success
code logged withfw_action="drop"staysdenied. Negative cases (809 without "blocked.",
a scan note without "Pkt is dropped", a retransmit notice, 38 withfw_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 ./...inplugins/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
actionResultvalue outside success, failed, denied (before the change it
reportedfailure).
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 withfw_action="drop", which is alreadydenied. - 1232 "NTP Request sent" with
fw_action="forward"keepssuccess; it is a health message and
can be reviewed separately. - Address side placement (
originversustarget) 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.ymland
rules/sophos/sophos_xg_firewall/sophos_xg_vpn_auth_failures.ymltestfailedinstead of
failure. The history markers the filter writes for these rules testfailedtoo. Their
history queries use the markers, notactionResult, 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 nowsuccess, two Failed records nowfailed) 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: 11failuretofailed, 3 Content Filtering Allowed tosuccess,
2 Web Content Policy Deny todenied, 1 SSL Error tofailed. No other field changed on any real
record, and no record carries a value other thansuccess,failed,deniedor 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 ./...inplugins/alerts: everything passes exceptTestBitdefenderActionResultRaw,
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
actionResultvalue outsidesuccess,
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 stayssuccessas 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
Per-filter details (3 of 4): github, google, ibm-aix, ibm-as400, linux, macos, netflow, o365, sophos-central, suricata, sysloggithubfix(github): write only success, failed or denied in actionResultWhy
GitHub webhook payloads carry no client address, so this change does not alter what threat What changed, per class (
|
| 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 expectsfailed, 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 thanactionResultchanged on any record; the branch
writes onlysuccessandfailed. - Rule conditions replayed with the SDK's CEL on all 208 normalized records: identical matches.
go test ./...inplugins/alerts: passes exceptTestBitdefenderActionResultRaw, which
already fails on unchanged v11 (go-sdk v1.1.36 keeps underscores) and is fixed by the Bitdefender
change;TestGitHubActionResultRaw39 of 39 pass.- Contract check: no
actionResultvalue outsidesuccess,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 needsuccessif it is added.
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/readsactionResultfor thegoogledata type (checked with
catalog.pyandgrep), 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 testsfailedinstead
offailure.plugins/alerts/testdata/google_action_result.json: 11 cases now expectfailed; 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, backendserverIp, redacted
callerIpvalues) 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
addoverwrites. - The conditions match exact message shapes. For example, a
sudocommand line whose command text contains "incorrect password attempt" is stillsuccess. - 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/readsactionResultfor 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/alertscovers this filter.go test ./...inplugins/alertsgives 118 passed, 12 skipped and 1 failed. The failure isTestBitdefenderActionResultRaw. 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: 17successand 11failed. The other 22 are identical in every field. - 27 public example records: OpenSSH and
sudoexamples, Oracle standard audit records published in Oracle, NXLog and community documentation, and an AIX audit example. 15 now get a value (12success, 4failed, 1denied), 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
sudorefusal wording,sshd-sessionfrom OpenSSH 9.8 and later,Failed keyboard-interactive/pam for invalid user, and negative cases (otherLogin restrictedcodes,a password is required, the intermediateAccepted keyline). 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
actionResultvalue outsidesuccess,failedanddenied. It still reports thefrom.hostfield, 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$inOBJ$CREATOR,OBJ$NAMEandOS$USERID, which today never match. That would start fillinglog.osUserID, which the rule "System integrity violations" reads (log.osUserID == rootwith a differentorigin.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 forwardsauditproutput. It would also add an address to Oracle records, and it needs a decision on whetherFAIL_AUTHmeansfailedordenied. It is follow-up work. - The address of
ssh: failed login attempt ... from <address>and the accounts insuand 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 readlog.message); no rule changed. All 8
match the same records before and after in the replay below. - No test in
plugins/alertscovers 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
successand its client address); every other record unchanged in every field. - Public examples: 2 wrong-password records ->
failed; 1 CPIAD02 ->successwith 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
successandfailed.
- Real records: 6 of 6 wrong-password records
- Rule conditions replayed with the SDK's CEL on all normalized records: identical results.
go test ./...inplugins/alerts: passes exceptTestBitdefenderActionResultRaw, which already
fails on unchanged v11 (go-sdk v1.1.36 keeps underscores) and is fixed by the Bitdefender change.- Contract check: no
actionResultvalue outsidesuccess,failed,denied(base had one
failure).
Left out
- Setting
actiontouser_loginfor 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/readsactionResultfor thelinuxdata 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 testsfailedinstead of
failure.plugins/alerts/testdata/linux_action_result.json:failurebecomesfailed; the USER_AUTH case
now expectssuccess; 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:failurebecomesfailed; the USER_AUTH
cases now expectfailed/successand the peer inorigin.ip. The brute-force rule predicate
results are unchanged.- Kernel firewall and sshd lines use
kvand multi-patterngrok, 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 newactionResultequals an independent re-derivation of the rules above; only the fields
of the changed classes changed; no value outsidesuccess,failed,denied; every newly
promoted address sits on a record with an outcome; every fixture's stated expectation holds. - Real server samples: 20
failurebecamefailed; 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 ./...inplugins/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
actionResultvalue outsidesuccess,failed,denied
(it reported threefailurewriters before).
Left out, with the reason
- Filebeat
systemmodule auth lines sent aslinux: 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 readorigin.command, so this is a separate change. actionfor systemd job records,USER_CMD,USER_ERRandCRED_*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, sandboxallow, the sandbox report fragment; denied lookupfrom another process, sandbox text from a user process;getpwnam failed, the screen-lock follow-up line and XProtect.
go test ./...inplugins/alerts: every test passes exceptTestBitdefenderActionResultRaw.
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.
denied19: 6 sandbox denials, 11 rejected approval requests, 2 TCC denials.success6: 4 granted rights, 2 TCC allows.failed5: directory sign-in failures.
- A second bounded set of stored records for the classes the first set lacked (32): 29 changed as
intended.denied27: 20 kernel System Policy or sandbox reports, 4 sandboxd reports, 3 launchd denied
lookups.failed2: 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 streamsandbox denials becamedenied; nothing else
changed. - Fabricated fixtures (27): every one has the intended value.
- In every run only
actionResultchanged, 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/readsactionResultfor this data type (12 rules checked), and no test
inplugins/alertscovers 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.
- 297 public NetFlow v5, v9 and IPFIX flows (device captures from the logstash NetFlow codec
- Every record carries
success,failedor no value. go test ./...inplugins/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
actionResultvalue 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
firewallEventelement (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.ymland
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"]becameequals(..., "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.goandtestdata/o365_action_result.json: the predicate loop and 10
cases now pinfailed. The SharePoint "no ResultStatus" case now expectssuccess. 39 new
fabricated cases cover every changed class and its negatives, for 105 cases in total.o365_action_result_rules_test.go: legacyfailureandblockedstay in the outcome list only
as negative cases. A raw conditional-access sign-in (53003) must normalize todeniedand
still match both sign-in rules.testdata/o365_awareness.json: 8 failed-operation cases pinfailed.
How it was validated
- EventProcessor playground (main
8a3ade7, go-sdk v1.1.36, the same staged plugins for both
runs). Base v11dda9d45aand this change were run over the same inputs.- 369 sampled production records. 108 changed
actionResult, and every change was the
intended one:- 48
failurebecamefailed. 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
successand 1 compliance-cmdlet record becamefailed. - 8 blocked mail records became
denied. - 6 compliance alert detections lost
success.
- 48
- No other field changed on any record. No value outside
success,failedanddeniedwas
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 stayfailed), Power BI
IsSuccessfalse (failed), and Safe Links with both key spellings.
- 369 sampled production records. 108 changed
- 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 ./...inplugins/alertspasses except
TestBitdefenderActionResultRaw, which fails the same way on unchanged v11 and is fixed by the
Bitdefender change. - Contract check: no
actionResultvalue outsidesuccess,failedanddenied. Unchanged
v11 reported fourfailuresteps.
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)
stayfailed. Emptying them would make threat intelligence look them up again. - The clicked Safe Links host on
target.domainis left out. It would add a new value that
threat intelligence matches, so it needs its own review. - Mapping the mail sender address (
SenderIp) toorigin.ipis 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
ErrorNumberorErrorCodeget 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, reformattingdeviceTime, and ignoring placeholder user IDs. - Endpoint DLP enforcement modes and other Teams actions without
ResultStatusneed
Microsoft's value list confirmed first. safe_links_click_patterns.ymlmatches actionClickedSafeLink, but the Management Activity
API writesTIUrlClickDatafor 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
- Microsoft Entra authentication and authorization error codes:
https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes - Office 365 Management Activity API schema (record types, Safe Links and mail threat records):
https://learn.microsoft.com/en-us/office/office-365-management-api/office-365-management-activity-api-schema - Power BI and Fabric user activity auditing:
https://learn.microsoft.com/en-us/fabric/admin/track-user-activities
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
actionResultfor 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 casesfailure->failed; the CoreCleanFailed
case now expectsfailed; 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 26deniedrecords and everything else unchanged. - 31 public example records (Elastic, SEKOIA, Microsoft Sentinel, Rapid7, Sophos and Sumo Logic
samples): IPS inbound block ->deniedplus the sender inorigin.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.
- 191 real server records: 11 empty ->
- All 20 Sophos Central rules replayed with CEL on base and candidate output: identical matches, no errors.
go test ./...inplugins/alerts: everything passes exceptTestBitdefenderActionResultRaw, which
already fails on unchanged v11 and is fixed in the Bitdefender change.- Contract check: no
actionResultvalue outsidesuccess,failed,denied(base hadfailure).
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 getsuccess. - 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
actionkey 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/readsactionResultfor this data type (35 rules checked). plugins/alerts/suricata_action_result_test.gonow checks the predicatefailedinstead of
failure, and requires the new classes.plugins/alerts/testdata/suricata_action_result.jsongrows 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 expectssuccess. 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, 2statsrecords are dropped by design): 26 records changed, all as
intended (5droptodenied, 15tls, 3httpand 3fileinfotosuccess); 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).
- 207 public example records (EVE documentation, public integration test data and forum
- Every record carries
success,failed,deniedor no value. go test ./...inplugins/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
actionResultvalue outside the three words.
Left out, and why
- The no-op
deleteofactionResultstays: it guards the steps that follow and changes nothing. pgsqlandsshrecords get no outcome: they need a per-protocol reading of the server's
answer.- An
alertwith apassoracceptverdict 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
fileinfoandftp_datarenames that turn objects into text, and the EVEhostfield
mapped totarget.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
outcomekey (success,failure,/Success,/Failure, and close variants) is now
read in both stages, afteract, so it wins. Example: a FortiGate administrator login with
act=login outcome=failedwassuccessand is nowfailed. - Trend Vision One:
actarrives as list text such as['Quarantine']; a list containing
Quarantine or Block now givesdenied. Account Audit Log records (event 900003) carry the
result incn1labelledResult; per Trend's CEF account audit log mapping (1: Success,
0: Fail) they now givesuccessorfailed.
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
invalidstaysdenied. 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.timeoutbecomesfailed(it wasfailure, a word the gate does not recognize). A timed-out
attempt did not complete, which is whatfailedmeans. Note for review: FortiGate also uses
timeoutfor an allowed session that later timed out; no sample here shows that case.
🤖 Generated with Claude Code
Per-filter details (4 of 4): utmstack, vmware-esxi, wineventlogutmstackUTMStack platform logs: write only success, failed or denied in actionResultWhyThe threat-intelligence step skips indicator lookups only when What changed (
|
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
utmstackdata 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.usernameisanonymous, 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.ipappears. -
go test ./...inplugins/alerts: every test passes exceptTestBitdefenderActionResultRaw.
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; onefailed). The other seven, which include a line withoutargs, 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 bedenied);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 asevent. Refused vSphere sign-ins now use the matchingCannot loginevent. - The paired password-check lines (
Accepted password/Rejected password) keep no result. This follows the filter's existing design and the existingpassword-check-not-finaltest. - 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.
- Completed vSphere sessions already use the
- Addresses are added only together with an outcome. Refused sign-ins are now
failedordenied, which threat intelligence skips. Completed SSH sign-ins aresuccess, 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.processto besshd, so the hostdAccepted password for userline is not matched. - The filter version is now 3.2.0.
Rules and tests
- No rule under
rules/readsactionResultfor 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.goandtestdata/vmware_esxi_action_result.json:- The test now checks the word
failed, and the two cases that expectedfailurenow expectfailed. - The model can set
log.process, and it checks that the new scratch fields are removed. - 11 fabricated cases were added:
Cannot loginwith an address, bare, and withno permission;Rejected password; sshdAcceptedover IPv4 and IPv6; the PAM failure for a valid user and for an illegal user; and the pam_unix,SSH session was openedandSSH login has failedlines, which must stay empty. - The existing
password-check-not-finalcase now runs withlog.processset to hostd.
- The test now checks the word
go test ./...inplugins/alertsgives 118 passed, 12 skipped and 1 failed. The failure isTestBitdefenderActionResultRaw. 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 failedrecords change fromfailuretofailed. The other 92 are identical in every field, including the 3logged in assessions that staysuccess. -
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 loginbecomefailed. - 1
Cannot login ... no permissionbecomesdenied. - 2 sshd
Acceptedbecomesuccess. - 1 PAM authentication failure becomes
failed.
The other 165 are unchanged in every field.
- 3
-
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 (sshdFailed ...,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 aboutorigin.hostnameandadversary.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 severallog.messagerules on vCenter records. It is follow-up work. - Narrowing the
permission deniedstep to access decisions (finding F13). The narrowing would turn records that aredeniedtoday into empty ones. The risk it guards against only appears once vCenter records are parsed (F2), so it belongs with that change. successon hostdAccepted password for user <user> from <address>(finding AR-ESXI-01). This password check belongs to the same session as theEvent <n> : User ... logged in asrecord, which already carriessuccessand 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 itspassword-check-not-finaltest. If the maintainers want it, they need to decide which line carries the session.failedon the vobdaccount lockednotice. 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
actionResultforwineventlog, 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 testsuccess,failed,
denied.plugins/alerts/testdata/windows_action_result.json: two cases pinnedfailure, nowfailed;
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.jsonandtestdata/windows_raw.json: the failed
NTLM cases now pinfailed.
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 getfailedand are skipped by the current gate. go test ./...inplugins/alerts: everything passes exceptTestBitdefenderActionResultRaw,
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
actionResultvalue outsidesuccess,failed,denied(the base reports the
twofailuresteps).
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 getfailed. Readingwinlog.*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
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 thewords the EventProcessor recognizes:
success,failedordenied, or no value when the recordstates no final outcome.
Why
The EventProcessor's threat-intelligence analysis reads
actionResultbefore it looks up anevent's addresses, domains and hosts in the threat-intelligence lists. It skips that lookup only
when the value is exactly
failed,deniedorblocked. Every other value, including an emptyone, is looked up, and a listed address raises "Known Malicious IP Detected".
The v11 filters did not match that:
failure(the word in the go-sdk wiki at the time). The engine does not knowfailure, so failed sign-ins and failed attempts from listed addresses would raisethreat-intelligence alerts. Today's deployed filters write
failed, which is skipped.acceptedon drops, failedlogins, detections and completed sign-ins alike.
blocked.The rule every filter now follows
successfaileddeniedFilters write
denied, neverblocked(the engine reads both the same way), so rules test oneword. 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
failedfor "still present" and failed tasks; deleted or disinfected malware and exploit-mitigation kills aredenied; Exchange quarantine isdenied; fixesTestBitdefenderActionResultRawfailedwording; refused console loginsfailed; blocked URLs, blocked files, cut downloads and cleaned or quarantined detectionsdeniedfailedwording; Security Center events that state a block aredeniedfailedwording; failed mitigationsfailed, completed kills and quarantinesdenied; console user and role changessuccessfailedwording; IAM Identity Center sign-in results; console SwitchRole; Grafana calls use their own status instead of the HTTP codefailedwording; conditional-access refusals 53000 to 53003, 530032, 530034 and 530035denied; web-firewall "Detected" no longersuccess; service-principal sign-ins; Defender logon and connection, Front Door and App Service access-restriction records get outcomes (address only with an outcome)blockedbecomesdenied; SSH, login, 802.1X, MAB, privilege and access-list outcomesfailedwording; API calls answered 401 or 403 and detections whose prevention ran aredeniedactkey (any letter case); the attacker address is mapped only when the record carries its actionacceptedmapped per message id: dropsdenied, failed loginsfailed, sign-ins and tunnelssuccess, Built/teardown records and notices no value; firewall denies and shunsdenieddenied, allowed connectionssuccessonly when the responder answered; IPsec sequence numbers parsefailedwording; IPS and DoS resets and DNS-filter redirectsdenied; answered resets and timeoutssuccess; security-profile passsuccess; SSL VPN tunnel-upsuccesswith the client addressdenied; traffic logs take the outcome from the HTTP return code; admin loginssuccessorfailedwith the administrator's addressfailedwording; 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 longersuccessfailedwording; firewall lines from drop or allow rules; successful logins; login failures keep their addressfailedwording; GlobalProtect pre-login and notice records no longersuccess; drop-packet and drop-icmpdenied; IKE failuresfailedwith the peer addressfailedwording; 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 requestssuccessfailedwording; allowed web requestssuccesswhatever the site answered; web-content and web-firewall blocksdenied; quoted text can no longer overwrite parsed fieldsfailedwording; jobs, check runs, check suites, deployment and commit statuses get their outcome on completed deliveriesfailedwording; firewall rule logsdeniedorsuccesswith their addressesfailedwording; sshd, su and sudo outcomes; Oracle account lockoutfailed, privilege refusalsdenied; the Oracle return code is read wherever it sitsfailedwording; records with a numeric prefix now parse; CPIAD02 connectionssuccesswith the client addressfailedwording; 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)success, reset-only flowsfailed; SYN-only and SYN+ACK-only flows stay emptyfailedwording (including the final UserLoginFailed step); conditional-access refusalsdenied; Safe Links reads both key spellings; Teams, SharePoint and OneDrive operations without a statussuccess; Power BI results; blocked mail threatsdenied; compliance alert detections no longersuccessfailedwording; AMSI and IPS inbound blocksdenied(the IPS sender becomesorigin.ip); update, cleanup and restore failuresfaileddenied; a TCP flow issuccessonly after a completed handshake and a refused one isfailed; Suricata 8accept; HTTP by answer code, completed TLS and file transferssuccessfailedwording; attempted actions and detections no longersuccess; session-end words no longerdenied; the CEFoutcomekey wins; Trend Vision One resultsfailedwording; "Cannot login" and SSH failuresfailed, SSH sign-inssuccessfailedwording for 4625, 4771 and failed 4776; Kerberos 4768/4769 by status, 4770success; Security Audit Failure records; SQL Server logins; Defender attack-surface blocksdenied; completed account and group changesjson-input and generic write no outcome and are unchanged.
Rules. The 15 rules that read
failurenow readfailed; the Office 365 rules that readblockednow readdenied, and the password-spraying and guessing rules countfailedordeniedso conditional-access refusals still count. Every rule of each data type matches thesame records before and after except where noted in the per-filter details.
Tests. Every
plugins/alertstest that pinned a changed value is updated, with new cases foreach 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
main8a3ade7, go-sdk v1.1.36):documented formats (documentation address ranges only) were replayed through the old and the
new filter;
intended fields, and that no value outside
success,failedanddeniedremains;On this branch as a whole:
go test ./...inplugins/alertspasses (on unchanged v11TestBitdefenderActionResultRawfails; the Bitdefender change fixes it);
actionResultvalue outside the three words;For reviewers to decide
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
succeededandallowed. If the team prefers those words, that is a follow-up that also changesthe rules. See also feat(plugins): define the action result values the engine recognizes threatwinds/go-sdk#29.
success), 719019 (keptdenied) and113005 ("authentication Rejected"
failed, "authorization Rejected"denied) against Cisco'ssyslog message guide; the official page could not be fetched during this work.
failedin ASA andstay
deniedin Firepower; both are skipped by threat intelligence.VPC endpoint events, Azure web-firewall and access-restriction "Allowed" rows, CrowdStrike
firewall match events (the action codes are undocumented), and the ESXi
vpxuserpassword line.failedanddenied; making them empty now would make the engine look them up.ip_flow_endrecords now carry addresses without a value (their start recordcarries
success); Deceptive Bytes detections without a block carry the attacker address withouta value; syslog
timeoutisfailed.Found along the way, not changed here: the MikroTik
ssh_brute_force_attemptsrule comparesprotocolwithtcpwhile the filter writesTCP; the Office 365safe_links_click_patternsrule looks for
ClickedSafeLinkwhile real records useTIUrlClickData; the Palo Alto DNSsecurity 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