Anti-Virus False Positives
Warning: You will get false positives from certain anti-virus programs like Windows Defender and Web Browsers saying they have detected malicious files. They are detecting on Sigma .yml files that are not executable and just contain certain signatures from malware. They are not malicious. If you are running Hayabusa for live analysis and do not want to cause any anti-virus alerts, be sure to use the live response packages that use encoded Sigma rules.
4.0.0 [2026/07/29] - Black Hat Arsenal USA Release
Enhancements:
- Combined the
csv-timelineandjson-timelinecommands into a singledfir-timelinecommand whose output format is chosen with-t, --output-type: unspecified or-t csvoutputs CSV (default),-t jsonoutputs JSON, and-t jsonloutputs JSONL. The value is case-insensitive (e.g.-t JSONL). The-tshorthand, previously--threads, now belongs to--output-type(--threadskeeps its long form). The CSV-only options-M, --multiline,-S, --tab-separator, and-R, --remove-duplicate-dataare grouped under a new "CSV Output" help category and now error if used with a non-CSV-t. The oldjson-timeline-L, --JSONL-outputflag is removed (use-t jsonl). (#1906) (@YamatoSecurity) - Standardized all command-line option long names to lowercase for consistency:
--GeoIP→--geo-ip,--HTML-report→--html-report,--UTC→--utc,--ISO-8601→--iso-8601,--RFC-2822/--RFC-3339→--rfc-2822/--rfc-3339,--US-time/--US-military-time/--European-time→lowercase,--JSON-input/--JSON-output→--json-input/--json-output, and--EID-filter→--eid-filter(--GeoIPwas the only long name that joined words by capitalization, so it also gains a hyphen). Short flags are unchanged. The help for-T, --visualize-timeline,-X, --remove-duplicate-detections, and-R, --remove-duplicate-datanow notes that they require event sorting (-s, --sort). (#1909) (@YamatoSecurity) - Added a Linux aarch64 (ARM64) musl release binary (
hayabusa-<ver>-lin-aarch64-musl), built with a proper aarch64 musl cross toolchain so it runs correctly. (The previously reported failure was from linking a musl target with the glibcaarch64-linux-gnu-gcc, which produced a binary killed at startup.) (#1332) (@YamatoSecurity) - The
logon-summarycommand now also counts RDP session events: Security4778/4779(session reconnect/disconnect, which carry the RDP client's workstation name and IP) and the Terminal Services operational events RemoteConnectionManager/Operational1149(network-level authentication) and LocalSessionManager/Operational25(session reconnect) — on top of the existing LocalSessionManager21and Gateway302. This keeps RDP logons summarized even when the matching Security4624has been flooded out of the log, and surfaces the source client hostname for reconnect/disconnect. (#1893) (@YamatoSecurity) - Added PowerShell event IDs
4100/4102(Microsoft-Windows-PowerShell/Operational and PowerShellCore/Operational) and classic403/600(Windows PowerShell) to theextract-base64command.4100/4102scan theContextInfo(Host Application = powershell -encodedcommand ...) andPayloadfields;403/600scan theEventData.Datadetail blob like the existing400. (#1889) (@YamatoSecurity) - Added
First Logon/Last Logoncolumns to thelogon-summarysuccessful-logons table andFirst Attempt/Last Attemptcolumns to the failed-logons table, showing the time range over which each account/source combination logged on (or attempted to). (#1883) (@YamatoSecurity)
Bug Fixes:
- Fixed
eid-metrics,logon-summaryandpivot-keywords-listproducing differently ordered output on every run, so two scans of the same logs could not be diffed.eid-metricsandlogon-summarysorted only by count, leaving equal-count rows inHashMapiteration order, which is reseeded per process; ties are now broken by channel and event ID, and by the logon grouping key.pivot-keywords-listwrote each category's keywords inIndexSetinsertion order, which varies because the values are inserted from the per-record parallel tasks; they are now sorted. Only the ordering changes -- the rows and keywords themselves are the same. (#1912) (@YamatoSecurity) - Fixed a panic in the results-summary tables when a top-5 rule title or author name contained multi-byte UTF-8 (e.g. a Japanese rule title). The titles/authors were truncated with a raw byte slice (
&title[..32],&author[0..24]), which panics when the byte index falls inside a multi-byte character — so with piped/redirected output (where the width defaults such that titles over 32 bytes are truncated) the whole run crashed after the scan finished, losing the summary. Truncation is now done at character boundaries, and the width limits use saturating subtraction so very narrow terminals no longer underflow. (#1904) (@YamatoSecurity) - Fixed
ComplexDataevent fields (e.g. theIdleState/PerfStatevalues in Kernel-Processor-Power EID26) not being extracted correctly: the twoNameattributes collapsed into aNamearray and the field values were dropped entirely. They are now keyed by theirNameattribute like normal<Data>fields (fixed in the bundledhayabusa-evtx, bumped to0.9.10). (#1520) (@YamatoSecurity) - Fixed
-c(custom rules config directory) being ignored bypivot-keywords-list, which always loadedpivot_keywords.txtfrom the bundled config next to the executable. It now resolvespivot_keywords.txtthrough the-cdirectory (falling back to the bundled copy), the same way every other config file is loaded. (#1902) (@YamatoSecurity) - Fixed the
read_jsonl_to_value/read_json_to_valuefile-open error printing the literal placeholder{path}instead of the file path (the error string was a plain string literal rather than aformat!). (#1897) (@YamatoSecurity) - Fixed a
u16underflow in theeid-metricstable's "Event" column width on terminals narrower than 55 columns:terminal_width - 55underflowed before the 45-character floor could apply, panicking in overflow-checked builds and wrapping to a huge value in release builds (leaving the column effectively uncapped). It now uses saturating subtraction. (#1897) (@YamatoSecurity) - Fixed Splunk-JSON timestamps carrying an explicit UTC offset (e.g.
+09:00) being parsed as aNaiveDateTime, which discards the offset and stored the local wall-clock time as if it were UTC — skewing thelog-metricsFirst/Last Timestamp columns as well as theeid-metrics/logon-summarytime ranges (EventMetrics::stats_time_cntandparse_evtx_datetime). The timeline aggregators now share one offset-aware parser (utils::parse_evtx_timestamp) that applies the offset. (#1897) (@YamatoSecurity) - Fixed
-G(GeoIP enrichment) with a custom rules config directory (-c) ignoring that directory'sgeoip_field_mapping.yaml: the lookup used the extensionless namegeoip_field_mapping, which never matched the real file, so it always fell back to the default bundled mapping. (#1897) (@YamatoSecurity) - Fixed
count(field)correlation rules producing a false-positive alert spanning longer than the rule'stimeframe. Injudge_timeframe's sliding window, the branch handling a window that did not satisfy the count condition did an uncheckedadd_data(right)before dropping the left edge, pullingrecords[right]— already known to be outside the timeframe from the window's left edge — into the next window; when that record's field value was new it could push the distinct-value count over the threshold across a span longer than the timeframe. The slide now only drops the left edge and lets the inner loop re-extendrightwith the timeframe check, so no record beyond the timeframe is counted. Barecount()was unaffected. (#1896) (@YamatoSecurity) - Fixed narrow-terminal issues in the results summary:
_print_timeline_histdid uncheckedusizesubtraction that could underflow on a very narrow terminal (huge allocation / hang) — it now skips the histogram when there is no room and usessaturating_sub; the rule-authors table computed its row count with a hard-codedis_multiple_of(4)instead of the actual column count (uneven tables at most widths) — nowauthors_num.div_ceil(table_column_num); and the rule-authors table now falls back to a single column on very narrow terminals (≤72 chars) instead of overflowing/wrapping. Timeline output is unaffected. (#1858) (@YamatoSecurity) - Fixed the ungrammatical "Successed submodule update" message printed after
update-rules(now "Submodule update succeeded"). (#1840) (@YamatoSecurity) - Fixed
temporal/temporal_orderedcorrelations not enforcing the rule'sgroup-byvalue when combining referenced-rule matches. Each referenced rule was aggregated per group-by value, but matches from different groups (e.g. different Computers) could still be correlated together as long as their timestamps fit the timeframe, producing false-positive alerts. Referenced-rule matches are now required to share the base match's group-by value. (#1841) (@YamatoSecurity) - Fixed
temporal_orderedcorrelations not enforcing event order beyond the first referenced rule: a no-op assignment (last_base = base) left every subsequent rule checked only against the base event's window, so out-of-order attack-chain events (e.g. A→C→B for a rule requiring A→B→C) were still matched. Each referenced rule is now required to match at or after the previous rule's matched event while staying within the timeframe window anchored at the base event. (#1841) (@YamatoSecurity)
Refactoring:
- Updated all Rust crate dependencies to their latest versions, including the bundled
hayabusa-evtxcrate to0.9.11(hayabusa-evtx#93). That release is a dependency refresh with no.rschanges, so evtx parsing behavior is unchanged:dfir-timelineCSV/JSON/JSONL output (including with-x, --recover-records) andlog-metricsoutput are byte-identical over a 60-file evtx corpus. (#1912) (@YamatoSecurity) - The version number in every command's CLI help banner (
Hayabusa vX.Y.Z - <Release Name>) is now filled in automatically fromCargo.tomlat compile time viaenv!("CARGO_PKG_VERSION"), instead of being hard-coded in eachhelp_template. The release name lives in a singleRELEASE_NAMEconstant insrc/detections/configs.rs. Cutting a release now means bumping the version inCargo.tomland editing that one constant, rather than hand-updating the banner in every subcommand. Help output is byte-identical. (#1909) (@YamatoSecurity) - Bumped the
compact_strdependency from 0.9 to 0.10. (#1909) (@YamatoSecurity) - Renamed 292 one-character local variables —
let/for/if letbindings, closure parameters, and a few function parameters — to descriptive names based on what each value holds (e.g. a value read from["System"]["Computer"]is nowcomputer, aFile::openresult isfile, a record row in a.mapisrow, a Sigma tag closure|x|is|tag|). Idiomatic names were kept (Err(e)error bindings, loop indices, trivial throwaway closures), and no struct fields, serde identifiers, function/type/macro names, or public API were touched. Pure rename with no functional change: the release binary's.text(machine code) section is byte-identical to before, and thecsv-timeline/json-timelineoutput over the 599-file sample-evtx corpus is byte-for-byte unchanged. (#1905) (@YamatoSecurity) - Reorganized
DefaultMatcher's pipe-modifier handling into per-category modules undersrc/detections/rule/matchers/modifiers/(string,regex,numeric,cidr,fieldref,encoding). Each modifier's logic (pattern wrapping/wildcard conversion, regex flags, numeric comparison, cidr, field references, base64/utf16 encoding) previously lived in hand-enumeratedmatcharms spread acrosspipe_element.rsanddefault_matcher.rs; it now lives in one category module per group, with the value-matching modifiers (cidr/numeric/fieldref) behind a smallValueMatchertrait soDefaultMatcher::is_match_inneris a singlevalue_matchcall. ThePipeElementenum and theMatchPlannormalization from #1862 are unchanged, so adding a Sigma modifier now means editing one category module instead of several dispatch sites. Pure reorganization — 157 matcher tests pass and detection output is byte-identical (the sample-evtx CSV/JSON/search outputs and a 2,838-evtxcsv-timelinecorpus). (#1895) (@YamatoSecurity) - Split
src/detections/rule/mod.rs(~1,200 lines that mixed the module declarations with the core rule types and a large test module) into a newrulenode.rsholdingRuleNode,DetectionNode,CorrelationType,create_rule/get_detection_keys, and the tests, and movedAggResulttocount.rs(next to the aggregation logic and theAggRecordTimeInfoit depends on);mod.rsis now just module wiring plus re-exports, so every existing import path is unchanged. Pure code movement with byte-identical detection output. (#1890) (@YamatoSecurity) - Refactored the ~860-line
App::analysis_filesfunction insrc/main.rs, which inlined the entire interactive rule-set wizard, an unreadable five-positional-arg count closure, ad-hoc channel filters, and progress-bar setup. Extracted four named units:run_scan_wizard()(the whole interactive wizard, returning the selected rule-set label),calculate_wizard_rule_count()(the closure promoted to a free function taking aWizardCountFilterstruct instead of a leadingboolplus threeVec<&str>, now unit-tested),apply_channel_filters()(thelogon-summary/config-critical-systems/log-metricsad-hoc channel filters), andbuild_progress_bar().analysis_filesnow reads as: print totals, wizard, load rules, filter, scan loop, emit. No behavior change — output is byte-identical across every affected subcommand, and the interactive wizard path (not covered by--no-wizardscans) was confirmed a faithful line-by-line move by an adversarial review. (#1885) (@YamatoSecurity) - Refactored the ~700-line
StoredStatic::create_static_datafunction insrc/detections/configs.rs. It took anOption<Config>that was neverNoneand.unwrap()ed it 33 times; the signature now takesConfigand binds the action once (let action = config.action.as_ref()), removing the repeated unwraps and the panic-on-Nonefootgun (all call sites updated). Also extracted three helpers to collapse the copy-paste:resolve_config_file()for the 12-line config-file fallback chain that was duplicated for 9 setting files,is_valid_level()for the repeatedLEVEL::iter().any(...)level validation, andcompact_string_set()for the ~120 lines of include/exclude computer and EID.iter().map(CompactString::from).collect()copy-paste. Net −250 lines inconfigs.rswith no behavior change — output is byte-identical across every affected subcommand (csv-timeline,json-timeline,logon-summary,computer-metrics,log-metrics,search;eid-metricsand thelogon-summarysuccessful table match too, modulo an intra-group tie-order nondeterminism present in the unmodified binary). (#1884) (@YamatoSecurity) - De-duplicated the search command's hit-processing logic.
search_keywordandsearch_regexcarried a verbatim-identical ~65-line tail (extract the event info, look up theProvider_EventIDfield-abbreviation table, normalize the AllFieldInfo whitespace, and either collect the hit for sorting or write it out on the fly), so any fix to the hit path had to be applied twice and the two copies could drift. It is now a singleemit_hitmethod that both loops call after their match predicate. Output is byte-identical for both search modes, sorted and on-the-fly. (#1881) (@YamatoSecurity) - Retired the
PIVOT_KEYWORDglobal mutable (alazy_static RwLock<IndexMap<String, PivotKeyword>>, thepivot-keywords-listkeyword accumulator): it became apivot_keyword: Arc<RwLock<PivotKeywordMap>>field onStoredStatic. Because the keyword values are filled in from the per-record parallel tasks (insert_pivot_keywordinexecute_rule, which readsstored_staticfrom theSTORED_STATICglobal clone) and read back inApp::exec(a separate instance), theArcletsStoredStatic::clone()share one map across both (mirroring the existinggeo_ip_searchfield).load_pivot_keywords/insert_pivot_keywordnow take the map explicitly, and themain.rsread sites usestored_static.pivot_keyword. ThePIVOT_KEYWORD_TEST_LOCK(only needed because the tests shared the global) is gone — each pivot test builds its own local map and they pass under full parallelism. Behavior-preserving —pivot-keywords-listproduces the same category files with byte-identical sorted content on the sample-evtx corpus (the intra-category line order is inherently non-deterministic across runs, onmaintoo, because the keywords are inserted from the parallel tasks). (#1873) (@YamatoSecurity) - Retired the
ERROR_LOG_STACKglobal mutable (alazy_static Mutex<Nested<String>>, the run-wide error-log accumulator flushed to./logs/errorlog-<timestamp>.log): it became anerror_log_stack: Arc<Mutex<Nested<String>>>field onStoredStatic. TheArcletsStoredStatic::clone()share one stack with theSTORED_STATICglobal clone, because errors are pushed from the per-record parallel tasks too (acount(field)miss runs insideRuleNode::select, called per rule fromexecute_rule). The ~28 push sites that already hadstored_staticin scope now usestored_static.error_log_stack; the four functions that only took a barequiet_errors_flag(create_error_log,get_file_size,peek_channel_from_evtx_first_record/create_channel_filter, and thecount→create_count_key→get_alias_value_in_recordchain) take an explicit&Mutex<Nested<String>>threaded from their callers — including throughfilter_evtx_files→apply_channel_filtersolog-metricschannel filtering keeps reporting evtx-open errors. Behavior-preserving — same collect-and-flush semantics, verified end to end (a corrupt evtx still writes the errorlog with itsFailed to open evtx fileentry). (#1875) (@YamatoSecurity) - Retired the
STORED_STATICglobal mutable (alazy_static RwLock<Option<StoredStatic>>, the god-struct itself and the last of the process globals):Detection::execute_ruleruns each rule in its own spawned task and read the sharedStoredStaticback out of this global, whichanalysis_filespublished once before the scan. It had exactly one production reader (execute_rule) and one production writer, so it was purely the fallback that handedstored_staticto the'staticspawned tasks. Nowanalysis_filessnapshotsstored_staticonce into anArc<StoredStatic>— the same single clone the global held, and itsArc-wrapped inner fields (error_log_stack,pivot_keyword) stay shared with the livestored_static, so parallel-path accumulation is still visible — and threads it throughanalysis_file/analysis_json_file/process_detection_batchintoDetection::start/execute_rules, which hands each spawned task its own cheapArc::clone(mirroring the existingrecords_arc);execute_rulereads itsstored_staticargument. The one production write, the global definition, and the redundant test-only primings are gone. Behavior-preserving — detection output is byte-identical on the sample-evtx corpus (CSV, super-verbose profile). (#1876) (@YamatoSecurity) - Retired the last shared-mutable process globals —
COMPUTER_MITRE_ATTCK_MAP(aDashMap) andCOMPUTER_MITRE_ATTCK_UNIQUE_KEYS(aDashSet), the per-computer MITRE ATT&CK tactic accumulators for the HTML report. They becamecomputer_mitre_attck_map: Arc<DashMap<..>>/computer_mitre_attck_unique_keys: Arc<DashSet<..>>fields onStoredStatic(mirroringpivot_keyword/error_log_stack): written from the per-record parallel tasks (create_log_recordinexecute_rule, via the sharedArc) and read/cleared when the report is rendered (_output_html_computer_by_mitre_attcknow takesstored_static). WithSTORED_STATICalready gone, no shared-mutable state remains to force serialized tests, soRUST_TEST_THREADS: 1is dropped from therust.ymlandcoverage.ymlworkflows — the lib + bin suites pass under full parallelism. Behavior-preserving —csv-timelineoutput and the HTML report's MITRE ATT&CK Tactics table (per-computer unique|total counts) are byte-identical on the sample-evtx corpus. (#1878) (@YamatoSecurity) - Scoped the MITRE ATT&CK tactics HTML-report accumulators (
COMPUTER_MITRE_ATTCK_MAPandCOMPUTER_MITRE_ATTCK_UNIQUE_KEYS) to a single report by clearing them after the table is emitted, so a report generated later in the same process no longer leaks keys or undercounts the per-tactic unique count; also removed an intermediateVecwhen joining the tactic cells. No behavior change for normal single-report runs. (#1840) (@YamatoSecurity) - Collapsed the three near-identical detection-selection node types (
AndSelectionNode,AllSelectionNode,OrSelectionNode) into a singleNarySelectionNodeparameterised by a logicalAll/Anyoperator, removing ~105 lines of duplicated code. Pure refactor; output verified byte-identical. (#1843) (@YamatoSecurity) - Replaced the hand-rolled JSON string escaping in
afterfact.rs(_convert_valid_json_str's.replace('🛂', "\\").replace('\\', "\\\\").replace('"', "\\\"")chain) with aserde_json-backed helper, so backslash/quote/control-character escaping is handled by the serializer instead of by inspection. Byte-identicalcsv-timelineandjson-timelineoutput (verified on the sample-evtx corpus; locked by a new regression test and the existing exact-string emit tests); first step of #1845. (#1850) (@YamatoSecurity) - Second step of the JSON-serialization refactor tracked in #1845: rebuilt the
json-timelinerecord assembly onserde_json— each record is now an orderedserde_jsonvalue serialized by the library instead of hand-rolled string concatenation, deleting_create_json_output_format,_convert_valid_json_str,json_escape_body, andprocess_target_stock.csv-timelineoutput is byte-identical;json-timelinedata is identical (verified with an order-preserving canonical compare on the sample-evtx corpus) except that repeatedDetailsarrays now print multi-line, JSONL is standard-compact ("k":v), and a data-corruption bug is fixed — the old JSONL path stripped double-spaces inside values (mangling e.g. author lists andEvtxFilepaths), now preserved verbatim. (#1851) (@YamatoSecurity) - Third step of the JSON-serialization refactor (#1845): the
json-timelineoutput no longer goes through acsv::Writerabused as a plain-text sink — JSON/JSONL records are now written directly to the target (via aResultWriterenum), whilecsv-timelinekeeps usingcsv::Writer. Byte-identical output forcsv-timeline,json-timeline(-o) andjson-timeline(-L), verified on the sample-evtx corpus. (#1852) (@YamatoSecurity) - Renamed the obscure
afterfactmodule and its identifiers to self-describing result-output names:src/afterfact.rs→src/results.rs,AfterfactInfo→ResultOutputState,AfterfactWriter→OutputWriter,output_afterfact→output_results,output_additional_afterfact→output_result_summary, and the localafterfact_info/afterfact_writer→result_state/output_writer. Pure rename; no behavior change (full test suite passes). (#1853) (@YamatoSecurity) - Removed the
🛂emoji-sentinel placeholders thatremove_sp_charused to protect\n/\r/\tfrom its control-character stripping (final step of the #1845 refactor). The real characters are now kept and handled per output format — escaped byserde_jsonfor JSON, collapsed to spaces for CSV/search.csv-timelineoutput is byte-identical;json-timelineoutput changes: an interior newline/tab/CR in a value now serializes as a proper\n/\t/\rescape instead of the old visible\\ntext, and leading/trailing newlines inDetailsvalues are now trimmed. Verified on the sample-evtx corpus (JSON differs only by those two things; no interior content lost). (#1849) (@YamatoSecurity) - Split the ~4,092-line
src/results.rsinto asrc/results/module directory (mod.rspluscsv.rs,json.rs,display.rs,summary.rs,html_stock.rs), separating writer construction, CSV/JSON/terminal-display emit, the summary renderers/statistics, and HTML-stock assembly. Pure code movement — output is byte-identical (verified on the sample-evtx corpus) and the full test suite passes. (#1856) (@YamatoSecurity) - Removed the last
🛂emoji-sentinel marker — the🛂🛂separator used to join multi-authorRuleAuthorvalues for--multiline/--tab-separatorCSV (and terminal display). The author string is now kept raw and split/joined per output mode at the output boundary instead of round-tripping through a marker. Output is byte-identical (verified on the sample-evtx corpus for default/multiline/tab CSV, JSON, and terminal display). Completes the emoji-sentinel removal begun in #1849. (#1860) (@YamatoSecurity) - Normalized
DefaultMatcher's pipe-modifier handling: the fast matcher was built from a hand-enumerated, order- and index-sensitive table of pipe-count / pipe-tuple cases (pipes[0] == Base64 && pipes[1] == Contains,utf16|base64offset|contains,|contains|all|windash, …); it now folds the pipe list into a canonicalMatchPlan { wrap, encoding, utf16, cased, windash, all }in one O(n) pass and builds the matcher from that, and the now-subsumedcreate_fast_matchhelper was removed. All 157 matcher tests and the full lib suite pass, and detection output is byte-identical on the sample-evtx corpus (CSV and JSON). The new dispatch is order- and count-independent: canonically-ordered rules (every real Sigma rule) are unchanged, while a non-canonical modifier order that the old table silently dropped to a regex fallback — ignoring the missed modifier (e.g.|cased|containsmatched case-insensitively) — is now normalized like its canonical form. Part 1 of #1861; thematchers.rsmodule split is a follow-up. (#1862) (@YamatoSecurity) - Split the ~4,900-line
src/detections/rule/matchers.rsinto asrc/detections/rule/matchers/module directory (mod.rsplusleaf_matchers.rs,default_matcher.rs,pipe_element.rs,tests.rs), separating theLeafMatchertrait, the file-based matchers,DefaultMatcher/MatchPlan, thePipeElementenum, and the unit tests. Pure code movement — output is byte-identical (verified on the sample-evtx corpus for CSV and JSON) and the full test suite passes. Part 2 of #1861; the per-modifier trait reorganization is a follow-up. (#1863) (@YamatoSecurity) - De-duplicated the detection batch loops in
main.rs: the evtx and JSON scan loops shared a copy-pasted tail (build records → timeline → run rules → stream-or-buffer the results), and the "emit each batch vs buffer for a final sorted output" branch was repeated at three sites. Extracted a singleemit_or_bufferpolicy helper and a sharedprocess_detection_batchfor the tail (parameterized by a namedBatchPolicyfor the two points where the loops differ). Behavior is unchanged — sorted output is byte-identical on the sample-evtx corpus for CSV and JSON, and the full lib + bin test suites pass. (#1865) (@YamatoSecurity) - Split the ~1,030-line
App::execgod function inmain.rsinto per-subcommand methods so it reads as a dispatcher:run_update_rules,run_level_tuning,run_set_default_profile,run_list_profiles,run_expand_list(the five early-returning match arms),run_pivot_keywords_output(the pivot-keywords-list output block),prompt_critical_systems_config(the config-critical-systems interactive prompt, incl. the 37-lineColorfulThemeliteral — extracted but still called in place so the prompt keeps appearing before "Start time:"), andprint_closing_summary(the elapsed-time / report-links / HTML-render / error-log / debug-stats tail). The method bodies are the original code verbatim; to let them borrowstored_staticfreely the dispatch now matches on an ownedstored_static.config.action.clone()instead of a borrow. Behavior-preserving —csv-timeline/json-timeline/pivot-keywords-listoutput is byte-identical on the sample-evtx corpus. (#1877) (@YamatoSecurity) - De-duplicated repeated clap arguments in
src/detections/configs.rs:--clobber(declared in 8 subcommand structs),--timeline-start/--timeline-end(4 each), and--disable-abbreviations(4) were extracted into sharedClobberOption/TimeRangeOption/DisableAbbreviationsOptionstructs and#[clap(flatten)]-ed into each subcommand, so a new subcommand flattens the shared arg instead of re-declaring it. No CLI change — every subcommand's--helpis byte-identical, and detection output is byte-identical on the sample-evtx corpus (CSV and JSON). (#1867) (@YamatoSecurity) - Retired the
STORED_EKEY_ALIASglobal mutable (alazy_static RwLock<Option<EventKeyAliasConfig>>) in favor of passing the alias config explicitly:create_log_record/create_agg_log_recordnow read&stored_static.eventkey_aliasdirectly,count/countup/create_rec_infotake an explicit&EventKeyAliasConfig(grouped with the rule keys into aRecordBuildContextbuilt once per scanned file andArc-shared into the per-record parallel tasks), and the ~33*STORED_EKEY_ALIAS.write() = Some(...)test primings are gone. Behavior-preserving — detection output is byte-identical on the sample-evtx corpus (CSV and JSON). First increment of retiring the process-global mutable state; the remaining globals are follow-ups. (#1869) (@YamatoSecurity) - Retired the four geo-IP global mutables (
GEOIP_DB_PARSER/GEOIP_DB_YAML/GEOIP_FILTERand theIP_MAPlookup cache): the cache became aMutexfield onGeoIPSearch(used byconvert_ip_to_geo), and the three DB/config globals becameStoredStaticfields (geo_ip_search: Option<Arc<GeoIPSearch>>,geo_ip_db_yaml,geo_ip_filter), read viastored_static.geo_ip_*at every site (create_log_record,emit_csv_inner,load_profile). Behavior-preserving — with--GeoIP(exercising the geo path), detection output is byte-identical on the sample-evtx corpus (CSV and JSON). Second increment of retiring the process-global mutable state. (#1870) (@YamatoSecurity) - Retired the
CHECKPOINTglobal mutable (alazy_static Mutex<CheckPointProcessTimer>, the phase-timing stopwatch): it was only ever used fromApp::exec/App::analysis_files(both&mut self) and never from the parallel tasks, so it became a plaincheckpointfield onApp(no global, noMutex), with the 11CHECKPOINT.lock()...sites replaced byself.checkpoint. Behavior-preserving — the timer only records phase laps and prints them under--debug; a--debugrun still shows the Rule Parse / Analysis / Output processing laps and the elapsed total. Third increment of retiring the process-global mutable state. (#1871) (@YamatoSecurity) - Retired the
HTML_REPORTERglobal mutable (alazy_static RwLock<HtmlReporter>, the HTML-report accumulator):add_md_databecame a method onHtmlReporterandcreate_htmlnow takes&self, andApp::execowns a singleHtmlReporterthreaded as&mutdown every path that records report sections (rule-load info, results summary, profile/overview) before rendering it — replacing the global writes and the finalread().to_owned().create_html(). TheHTML_REPORTER_TEST_LOCK(only needed because tests shared the global) is also gone, and the report-section keys now use theGENERAL_OVERVIEW_SECTION/RESULTS_SUMMARY_SECTIONconstants everywhere. Behavior-preserving — with--html-reportthe generated HTML is byte-identical on the sample-evtx corpus (aside from the non-deterministic elapsed time). Fourth increment of retiring the process-global mutable state. (#1872) (@YamatoSecurity)
改善:
csv-timelineコマンドとjson-timelineコマンドを統合し、単一のdfir-timelineコマンドにしました。出力形式は-t, --output-typeで指定します。指定なしまたは-t csvで CSV 出力(デフォルト)、-t jsonで JSON 出力、-t jsonlで JSONL 出力となります。値の大文字・小文字は区別されません(例:-t JSONL)。これまで--threadsの短縮形だった-tは--output-typeに割り当てられました(--threadsは長い形式のまま使用できます)。CSV 専用のオプションである-M, --multiline、-S, --tab-separator、-R, --remove-duplicate-dataは、ヘルプ内の新しい「CSV Output」カテゴリにまとめられ、CSV 以外の-tと併用した場合はエラーになるようになりました。旧json-timelineの-L, --JSONL-outputフラグは廃止されました(代わりに-t jsonlをご利用ください)。(#1906) (@YamatoSecurity)- Linux aarch64(ARM64)の musl リリースバイナリ(
hayabusa-<ver>-lin-aarch64-musl)を追加した。正しく動作するよう、適切な aarch64 musl クロスツールチェインでビルドしている。(これまで報告されていた実行失敗は、musl ターゲットを glibc のaarch64-linux-gnu-gccでリンクしていたためで、起動時に kill されるバイナリが生成されていた。) (#1332) (@YamatoSecurity) logon-summaryコマンドが、RDP のセッションイベントも集計するようにした。既存の LocalSessionManager21と Gateway302に加えて、Security4778/4779(セッションの再接続/切断。RDP クライアントのワークステーション名とIPを含む)と、Terminal Services の運用ログの RemoteConnectionManager/Operational1149(ネットワークレベル認証)・LocalSessionManager/Operational25(セッション再接続)を対象に追加した。これにより、対応する Security4624がログからあふれて消えている場合でも RDP ログオンを集計でき、再接続/切断では接続元クライアントのホスト名も表示される。 (#1893) (@YamatoSecurity)extract-base64コマンドに、PowerShellのイベントID4100/4102(Microsoft-Windows-PowerShell/Operational および PowerShellCore/Operational)と、クラシックの403/600(Windows PowerShell)を追加した。4100/4102はContextInfo(Host Application = powershell -encodedcommand ...)とPayloadフィールドを、403/600は既存の400と同様にEventData.Dataの詳細ブロックをスキャンする。 (#1889) (@YamatoSecurity)logon-summaryの成功ログオンテーブルにFirst Logon/Last Logon列を、失敗ログオンテーブルにFirst Attempt/Last Attempt列を追加した。各アカウント/ソースの組み合わせがログオン(または試行)した時間の範囲を表示する。 (#1883) (@YamatoSecurity)
バグ修正:
eid-metrics・logon-summary・pivot-keywords-listの出力順が実行のたびに変わり、同じログをスキャンした結果同士を差分比較できない問題を修正した。eid-metricsとlogon-summaryは件数のみでソートしていたため、件数が同じ行はプロセスごとに再シードされるHashMapの反復順のままになっていた。同数の場合はチャンネルとイベントID、およびログオンのグルーピングキーで順序を決めるようにした。pivot-keywords-listは各カテゴリのキーワードをIndexSetの挿入順で書き出していたが、値はレコードごとの並列タスクから挿入されるため順序が変動していた。ソートしてから出力するようにした。変わるのは順序だけで、行やキーワードの内容は同じである。 (#1912) (@YamatoSecurity)- 結果サマリのテーブルで、上位5件のルールタイトルや作成者名にマルチバイト UTF-8(例: 日本語のルールタイトル)が含まれる場合にパニックする問題を修正した。タイトル/作成者をバイト単位のスライス(
&title[..32]・&author[0..24])で切り詰めていたため、バイト位置がマルチバイト文字の途中に来るとパニックしていた。出力をパイプ/リダイレクトした場合(幅の既定値により32バイトを超えるタイトルが切り詰められる)、スキャン完了後に実行全体がクラッシュし、サマリが失われていた。切り詰めを文字境界で行うようにし、幅の上限計算にも飽和減算を用いて、極端に狭いターミナルでもアンダーフローしないようにした。 (#1904) (@YamatoSecurity) ComplexDataのイベントフィールド(例: Kernel-Processor-Power EID26のIdleState/PerfStateの値)が正しく抽出されない問題を修正した。2つのName属性がNameの配列にまとめられ、フィールドの値が失われていた。通常の<Data>フィールドと同様に、Name属性をキーとして展開するようにした(同梱のhayabusa-evtxを0.9.10に更新して修正)。 (#1520) (@YamatoSecurity)pivot-keywords-listで-c(カスタムのルール設定ディレクトリ)が無視され、常に実行ファイル同梱のpivot_keywords.txtを読み込んでいた問題を修正した。他の設定ファイルと同様に、pivot_keywords.txtを-cディレクトリ経由で解決するようにした(存在しない場合は同梱コピーにフォールバック)。 (#1902) (@YamatoSecurity)read_jsonl_to_value/read_json_to_valueのファイルオープンエラーが、ファイルパスの代わりにプレースホルダー{path}をそのまま出力していた問題を修正した(エラー文字列がformat!ではなく通常の文字列リテラルだった)。 (#1897) (@YamatoSecurity)eid-metricsテーブルの「Event」列の幅計算で、55桁未満のターミナルでu16のアンダーフローが発生する問題を修正した。terminal_width - 55が45文字の下限を適用する前にアンダーフローし、オーバーフローチェック有効のビルドではパニックし、リリースビルドでは巨大な値にラップしていた(列の上限が実質無効になっていた)。飽和減算を使うようにした。 (#1897) (@YamatoSecurity)- 明示的な UTC オフセット(例:
+09:00)を持つ Splunk-JSON のタイムスタンプがNaiveDateTimeで解析されてオフセットが破棄され、ローカルの時計時刻がそのまま UTC として保存されていた問題を修正した。これによりlog-metricsの First/Last Timestamp 列に加えてeid-metrics/logon-summaryの時刻範囲(EventMetrics::stats_time_cntとparse_evtx_datetime)もずれていた。タイムラインの集計処理は、オフセットを適用する共通のパーサー(utils::parse_evtx_timestamp)を使うようにした。 (#1897) (@YamatoSecurity) - カスタムのルール設定ディレクトリ(
-c)を指定した-G(GeoIP エンリッチメント)が、そのディレクトリのgeoip_field_mapping.yamlを無視する問題を修正した。ルックアップが拡張子なしのgeoip_field_mappingを使っていたため実ファイルに一致せず、常にデフォルトのバンドルされたマッピングにフォールバックしていた。 (#1897) (@YamatoSecurity) count(field)相関ルールがルールのtimeframeより長い範囲にまたがる誤検知アラートを生成する問題を修正した。judge_timeframeのスライディングウィンドウで、条件を満たさなかったウィンドウを処理する分岐が左端を外す前にチェックなしのadd_data(right)を行っていたため、ウィンドウの左端からタイムフレーム外であるとすでに判明しているrecords[right]を次のウィンドウに取り込んでいた。そのレコードのフィールド値が新規の場合、タイムフレームより長い範囲でユニーク値のカウントがしきい値を超えることがあった。スライド時は左端を外すだけにして、内側のループがrightをタイムフレームチェック付きで再度伸ばすようにしたため、タイムフレーム外のレコードはカウントされない。フィールド指定なしのcount()は影響を受けない。 (#1896) (@YamatoSecurity)- 結果サマリーの狭いターミナルでの表示に関する問題を修正した:
_print_timeline_histがusizeの減算をチェックせずに行っていたため、極端に狭いターミナルでアンダーフローして巨大なメモリ確保やハングを引き起こす可能性があった(描画スペースが無い場合はヒストグラムをスキップし、saturating_subを使うようにした)。ルール作者テーブルの行数を実際の列数ではなくハードコードされたis_multiple_of(4)で計算していたため多くの幅で不揃いなテーブルになっていた(authors_num.div_ceil(table_column_num)に変更)。また、ルール作者テーブルが非常に狭いターミナル(72文字以下)ではみ出す/折り返す代わりに1列にフォールバックするようにした。タイムライン出力には影響しない。 (#1858) (@YamatoSecurity) update-rules実行後に表示される文法的に誤ったメッセージ「Successed submodule update」を「Submodule update succeeded」に修正した。 (#1840) (@YamatoSecurity)temporal/temporal_ordered相関が、参照ルールのマッチを結合する際にルールのgroup-by値を検証していなかった問題を修正した。各参照ルールは group-by 値ごとに集計されるものの、タイムスタンプがタイムフレームに収まってさえいれば異なるグループ(例: 異なる Computer)のマッチ同士が相関してしまい、誤検知を生んでいた。参照ルールのマッチは、ベースとなるマッチと同じ group-by 値を持つことを要求するようにした。 (#1841) (@YamatoSecurity)temporal_ordered相関が最初の参照ルール以降のイベント順序を検証できていなかった問題を修正した。無意味な代入(last_base = base)により、以降のすべてのルールがベースイベントのウィンドウとしか照合されず、順序が入れ替わった攻撃チェーンのイベント(例: A→B→C を要求するルールに対する A→C→B)がマッチしてしまっていた。各参照ルールは、ベースイベントを起点とするタイムフレームウィンドウ内に収まりつつ、直前のルールがマッチしたイベント以降にマッチすることを要求するようにした。 (#1841) (@YamatoSecurity)
その他:
- Rustのクレート依存関係をすべて最新版に更新した。同梱の
hayabusa-evtxクレートも0.9.11に更新している(hayabusa-evtx#93)。このリリースは依存関係の更新のみで.rsの変更を含まないため、evtxの解析挙動は変わらない。60個のevtxファイルからなるコーパスで、dfir-timelineの CSV/JSON/JSONL 出力(-x, --recover-records併用時を含む)とlog-metricsの出力がバイト単位で同一であることを確認した。 (#1912) (@YamatoSecurity) DefaultMatcherのパイプ修飾子の処理を、src/detections/rule/matchers/modifiers/配下のカテゴリ別モジュール(string・regex・numeric・cidr・fieldref・encoding)に再編した。各修飾子のロジック(パターンのラップ/ワイルドカード変換、正規表現フラグ、数値比較、cidr、フィールド参照、base64/utf16 エンコード)は、これまでpipe_element.rsとdefault_matcher.rsに散らばった手書きのmatch分岐にあったが、グループごとに1つのカテゴリモジュールにまとめ、値を直接マッチする修飾子(cidr/numeric/fieldref)は小さなValueMatcherトレイトの背後に置いたため、DefaultMatcher::is_match_innerはvalue_matchの1回の呼び出しになった。PipeElement列挙型と #1862 のMatchPlanによる正規化は変更していないため、Sigma 修飾子の追加は複数のディスパッチ箇所ではなく1つのカテゴリモジュールの編集で済む。純粋な再編で、157個のマッチャーテストがパスし、検知結果の出力はバイト単位で同一(サンプルevtxの CSV/JSON/search と、2,838個のevtxに対するcsv-timelineコーパス)。 (#1895) (@YamatoSecurity)src/detections/rule/mod.rs(モジュール宣言と、コアとなるルール型や大きなテストモジュールが混在した約1,200行)を、RuleNode・DetectionNode・CorrelationType・create_rule/get_detection_keys・テストを収めた新しいrulenode.rsに分離し、AggResultは(それを生成する集計ロジックと、依存先のAggRecordTimeInfoの隣である)count.rsに移動した。mod.rsはモジュールの取りまとめと再エクスポートのみになり、既存のインポートパスはすべて変更なしで動作する。純粋なコード移動で、検知結果の出力はバイト単位で同一。 (#1890) (@YamatoSecurity)src/main.rsの約860行に及ぶApp::analysis_files関数をリファクタリングした。この関数は対話式のルールセットウィザード全体、可読性の低い5つの位置引数を取るカウント用クロージャ、アドホックなチャンネルフィルタ、プログレスバーの構築をインラインで抱え込んでいた。4つの名前付きユニットを抽出した:run_scan_wizard()(対話式ウィザード全体。選択されたルールセットのラベルを返す)、calculate_wizard_rule_count()(先頭のboolと3つのVec<&str>という位置引数の代わりにWizardCountFilter構造体を取る自由関数に昇格したクロージャ。ユニットテスト付き)、apply_channel_filters()(logon-summary/config-critical-systems/log-metricsのアドホックなチャンネルフィルタ)、build_progress_bar()。analysis_filesは、合計の表示・ウィザード・ルール読み込み・フィルタ・スキャンループ・出力、という流れで読めるようになった。挙動に変更はない — 影響を受けるすべてのサブコマンドで出力はバイト単位で同一であり、対話式ウィザードの経路(--no-wizardのスキャンでは通らない)は敵対的レビューによって忠実な行単位の移動であることを確認した。 (#1885) (@YamatoSecurity)src/detections/configs.rsの約700行に及ぶStoredStatic::create_static_data関数をリファクタリングした。この関数は実際には決してNoneにならないOption<Config>を受け取り、それを33回.unwrap()していた。シグネチャをConfigを受け取るように変更し、アクションを一度だけ束縛する(let action = config.action.as_ref())ようにして、繰り返しの unwrap とNoneでパニックする危険を取り除いた(すべての呼び出し側を更新)。また、コピペを解消するために3つのヘルパーを抽出した: 9個の設定ファイルで重複していた12行の設定ファイルフォールバックチェーンのためのresolve_config_file()、繰り返されていたLEVEL::iter().any(...)のレベル検証のためのis_valid_level()、include/exclude の computer・EID の.iter().map(CompactString::from).collect()という約120行のコピペのためのcompact_string_set()。configs.rsを正味 −250 行削減し、挙動に変更はない — 影響を受けるすべてのサブコマンド(csv-timeline、json-timeline、logon-summary、computer-metrics、log-metrics、search)で出力はバイト単位で同一(eid-metricsとlogon-summaryの成功テーブルも一致する。ただし未改変のバイナリにも存在するグループ内の同点時の並び順の非決定性を除く)。 (#1884) (@YamatoSecurity)searchコマンドのヒット処理ロジックの重複を解消した。search_keywordとsearch_regexは、マッチ判定の後に完全に同一な約65行の末尾処理(イベント情報の抽出、Provider_EventIDによるフィールド略称テーブルの参照、AllFieldInfo の空白の正規化、そしてソート用にヒットを収集するか逐次書き出すかの分岐)を重複して持っており、ヒット処理への修正を2箇所に適用する必要があって、2つのコピーが乖離する恐れがあった。両方のループがマッチ判定の後に呼び出す単一のemit_hitメソッドに抽出した。出力は、キーワード検索・正規表現検索それぞれのソートあり/逐次出力の両方でバイト単位で同一。 (#1881) (@YamatoSecurity)- グローバルミュータブルな
PIVOT_KEYWORD(lazy_static RwLock<IndexMap<String, PivotKeyword>>で、pivot-keywords-listのキーワードアキュムレータ)を廃止し、StoredStaticのpivot_keyword: Arc<RwLock<PivotKeywordMap>>フィールドにした。キーワードの値はレコードごとの並列タスク(execute_rule内のinsert_pivot_keyword。STORED_STATICグローバルクローンからstored_staticを読み取る)で埋められ、App::exec(別インスタンス)で読み戻されるため、ArcによりStoredStatic::clone()が両者で単一のマップを共有できる(既存のgeo_ip_searchフィールドと同様)。load_pivot_keywords/insert_pivot_keywordはマップを明示的に受け取るようになり、main.rsの読み取り箇所はstored_static.pivot_keywordを使用する。PIVOT_KEYWORD_TEST_LOCK(テストがグローバルを共有していたためにのみ必要だった)は削除された。各pivotテストは独自のローカルマップを構築し、完全な並列実行下でもパスする。挙動は保存される。pivot-keywords-listはサンプルevtxコーパスで同一のカテゴリファイルをバイト単位で同一のソート済み内容とともに生成する(カテゴリ内の行順は実行ごとに本質的に非決定的であり、これはmainでも同様で、キーワードが並列タスクから挿入されるためである)。 (#1873) (@YamatoSecurity) - グローバルミュータブルな
ERROR_LOG_STACK(lazy_static Mutex<Nested<String>>で、./logs/errorlog-<timestamp>.logにフラッシュされる実行全体のエラーログ蓄積用)を廃止し、StoredStaticのerror_log_stack: Arc<Mutex<Nested<String>>>フィールドにした。ArcによりStoredStatic::clone()がSTORED_STATICのグローバルクローンと単一のスタックを共有できる。これはエラーがレコードごとの並列タスクからもpushされるためである(count(field)のミスはRuleNode::select内で発生し、これはexecute_ruleからルールごとに呼び出される)。既にstored_staticがスコープ内にあった約28箇所のpush箇所はstored_static.error_log_stackを使うようにし、quiet_errors_flagのみを受け取っていた4つの関数(create_error_log、get_file_size、peek_channel_from_evtx_first_record/create_channel_filter、およびcount→create_count_key→get_alias_value_in_recordのチェーン)は、呼び出し元から引き渡される明示的な&Mutex<Nested<String>>を受け取るようにした。これにはfilter_evtx_files→apply_channel_filter経由も含まれ、log-metricsのチャンネルフィルタリングが引き続きevtxオープンエラーを報告できるようにした。挙動は保存される。collect-and-flush のセマンティクスは同一で、エンドツーエンドで検証済み(破損したevtxは引き続きFailed to open evtx fileエントリを含むエラーログを書き出す)。 (#1875) (@YamatoSecurity) - グローバルミュータブルな
STORED_STATIC(lazy_static RwLock<Option<StoredStatic>>、巨大構造体そのものであり、プロセスグローバルの最後の1つ)を廃止した。Detection::execute_ruleは各ルールを自身の spawn したタスク内で実行し、共有されるStoredStaticをこのグローバルから読み戻していた。これはスキャン前にanalysis_filesが一度だけ公開したものである。プロダクションでの読み手はちょうど1つ(execute_rule)、書き手も1つだけであり、純粋に'staticな spawn したタスクへstored_staticを渡すためのフォールバックにすぎなかった。現在はanalysis_filesがstored_staticを一度だけArc<StoredStatic>にスナップショットし(グローバルが保持していたのと同じ単一のクローンであり、Arcでラップされた内部フィールド(error_log_stack、pivot_keyword)は生きているstored_staticと共有されたままなので、並列パスでの蓄積は引き続き可視である)、それをanalysis_file/analysis_json_file/process_detection_batch経由でDetection::start/execute_rulesに通し、各 spawn したタスクには自身の安価なArc::cloneを渡す(既存のrecords_arcと同様)。execute_ruleは自身のstored_static引数を読む。唯一のプロダクションでの書き込み、グローバル定義、および冗長なテスト専用の初期化は削除された。挙動は保存される。検知出力はサンプルevtxコーパスでバイト単位で同一(CSV、super-verbose プロファイル)。 (#1876) (@YamatoSecurity) - 最後の共有ミュータブルなプロセスグローバル、
COMPUTER_MITRE_ATTCK_MAP(DashMap)とCOMPUTER_MITRE_ATTCK_UNIQUE_KEYS(DashSet)——HTMLレポート用のコンピュータごとのMITRE ATT&CKタクティクのアキュムレータ——を廃止した。これらは(pivot_keyword/error_log_stackに倣って)StoredStaticのcomputer_mitre_attck_map: Arc<DashMap<..>>/computer_mitre_attck_unique_keys: Arc<DashSet<..>>フィールドにした。レコードごとの並列タスク(execute_rule内のcreate_log_record、共有Arc経由)から書き込まれ、レポートのレンダリング時に読み取り/クリアされる(_output_html_computer_by_mitre_attckはstored_staticを受け取るようになった)。STORED_STATICが既に廃止されたことで、テストの直列化を強制する共有ミュータブルな状態がなくなったため、rust.ymlとcoverage.ymlワークフローからRUST_TEST_THREADS: 1を削除した——lib + bin のテストスイートは完全な並列実行でパスする。挙動は保存される——csv-timelineの出力とHTMLレポートのMITRE ATT&CK Tacticsテーブル(コンピュータごとのunique|total件数)はサンプルevtxコーパスでバイト単位で同一。 (#1878) (@YamatoSecurity) - HTMLレポートのMITRE ATT&CKタクティクの集計用グローバル変数(
COMPUTER_MITRE_ATTCK_MAPとCOMPUTER_MITRE_ATTCK_UNIQUE_KEYS)を、テーブル出力後にクリアすることで単一レポートの範囲に限定した。これにより、同一プロセス内で後続のレポートを生成してもキーが残留してタクティクごとのユニーク数が過少カウントされることがなくなった。あわせて、タクティクのセルを結合する際の中間Vecを削除した。通常の(単一レポートの)実行では挙動に変更はない。 (#1840) (@YamatoSecurity) - 3つのほぼ同一な検知セレクションノード型(
AndSelectionNode、AllSelectionNode、OrSelectionNode)を、論理演算子All/Anyでパラメータ化した単一のNarySelectionNodeに統合し、重複コード約105行を削除した。純粋なリファクタリングで、出力がバイト単位で同一であることを確認済み。 (#1843) (@YamatoSecurity) afterfact.rsの手書きのJSON文字列エスケープ(_convert_valid_json_strの.replace('🛂', "\\").replace('\\', "\\\\").replace('"', "\\\"")の連鎖)をserde_jsonベースのヘルパーに置き換え、バックスラッシュ・引用符・制御文字のエスケープを手作業ではなくシリアライザに任せるようにした。csv-timelineとjson-timelineの出力はバイト単位で同一(サンプルevtxコーパスで検証、新しいリグレッションテストと既存の文字列一致テストで担保)。#1845 の最初のステップ。 (#1850) (@YamatoSecurity)- #1845 で追跡しているJSONシリアライズのリファクタリングの第2段階として、
json-timelineのレコード生成をserde_jsonベースに書き換えた: 各レコードを順序を保持したserde_jsonの値として組み立て、ライブラリにシリアライズさせるようにし、手書きの文字列連結(_create_json_output_format、_convert_valid_json_str、json_escape_body、process_target_stock)を削除した。csv-timelineの出力はバイト単位で同一。json-timelineのデータも同一(サンプルevtxコーパスに対する順序保持のカノニカル比較で検証済み)だが、繰り返し出現するDetailsの配列が複数行で出力されるようになり、JSONLは標準的なコンパクト形式("k":v)になった。さらに、旧JSONL経路が値の中の連続スペースを除去して(著者一覧やEvtxFileのパスを壊して)いたデータ破損バグを修正し、値をそのまま保持するようにした。 (#1851) (@YamatoSecurity) - JSONシリアライズのリファクタリング(#1845)の第3段階として、
json-timelineの出力をテキストシンクとして流用していたcsv::Writerを経由せず、JSON/JSONLのレコードを(ResultWriterenum を介して)出力先へ直接書き込むようにした。csv-timelineは引き続きcsv::Writerを使用する。csv-timeline・json-timeline(-o)・json-timeline(-L)の出力はバイト単位で同一(サンプルevtxコーパスで検証済み)。 (#1852) (@YamatoSecurity) - 分かりにくい
afterfactモジュールとその識別子を、内容を表す結果出力向けの名前にリネームした:src/afterfact.rs→src/results.rs、AfterfactInfo→ResultOutputState、AfterfactWriter→OutputWriter、output_afterfact→output_results、output_additional_afterfact→output_result_summary、ローカル変数のafterfact_info/afterfact_writer→result_state/output_writer。純粋なリネームで挙動に変更はない(全テストがパス)。 (#1853) (@YamatoSecurity) remove_sp_charが制御文字の除去から\n/\r/\tを守るために使っていた🛂絵文字のセンチネル(プレースホルダ)を削除した(#1845 リファクタリングの最終段階)。実際の文字をそのまま保持し、出力形式ごとに処理するようにした(JSONはserde_jsonがエスケープ、CSV/searchはスペースに畳み込む)。csv-timelineの出力はバイト単位で同一。json-timelineの出力は変化する: 値の内部の改行/タブ/CRが従来の可視テキスト\\nではなく本来の\n/\t/\rエスケープとしてシリアライズされ、Details値の先頭・末尾の改行はトリムされるようになった。サンプルevtxコーパスで検証済み(JSONの差分はこの2点のみで、内部の内容は失われない)。 (#1849) (@YamatoSecurity)- 約4,092行の
src/results.rsをsrc/results/モジュールディレクトリ(mod.rsとcsv.rs・json.rs・display.rs・summary.rs・html_stock.rs)に分割し、ライターの構築、CSV/JSON/ターミナル表示の出力、サマリーレンダラーと統計、HTMLストックの組み立てを分離した。純粋なコードの移動で、出力はバイト単位で同一(サンプルevtxコーパスで検証済み)、全テストがパスする。 (#1856) (@YamatoSecurity) - 最後に残っていた
🛂絵文字センチネルマーカー(--multiline/--tab-separatorのCSV(およびターミナル表示)で複数のルール作者RuleAuthorを結合するために使っていた🛂🛂区切り)を削除した。作者文字列をそのまま保持し、マーカーを経由する代わりに出力の段階で出力モードごとに分割・結合するようにした。出力はバイト単位で同一(サンプルevtxコーパスの default/multiline/tab CSV・JSON・ターミナル表示で検証済み)。#1849 で始めた絵文字センチネルの削除を完了した。 (#1860) (@YamatoSecurity) DefaultMatcherのパイプ修飾子の処理を正規化した: 高速マッチャーはパイプ数・パイプの組み合わせを手作業で列挙し、順序とインデックスに依存したテーブル(pipes[0] == Base64 && pipes[1] == Contains、utf16|base64offset|contains、|contains|all|windashなど)で構築していたが、パイプのリストを1回の O(n) パスで正規のMatchPlan { wrap, encoding, utf16, cased, windash, all }にまとめ、そこからマッチャーを構築するようにした。不要になったcreate_fast_matchヘルパーは削除した。157個のマッチャーテストと全ライブラリテストがパスし、検出結果はサンプルevtxコーパス(CSV・JSON)でバイト単位で同一。新しいディスパッチは順序・パイプ数に依存しない: 正規の順序のルール(実在するすべての Sigma ルール)は変わらず、旧テーブルが正規表現フォールバックに落として修飾子を無視していた非正規の順序(例:|cased|containsは大文字小文字を区別せずにマッチしていた)は、正規の順序と同じように正規化されるようになった。#1861 のパート1で、matchers.rsのモジュール分割は今後の対応。 (#1862) (@YamatoSecurity)- 約4,900行の
src/detections/rule/matchers.rsをsrc/detections/rule/matchers/モジュールディレクトリ(mod.rsとleaf_matchers.rs・default_matcher.rs・pipe_element.rs・tests.rs)に分割し、LeafMatcherトレイト、ファイルベースのマッチャー、DefaultMatcher/MatchPlan、PipeElement列挙型、ユニットテストを分離した。純粋なコードの移動で、出力はバイト単位で同一(サンプルevtxコーパスの CSV・JSON で検証済み)、全テストがパスする。#1861 のパート2で、修飾子ごとのトレイト化は今後の対応。 (#1863) (@YamatoSecurity) main.rsの検出バッチループの重複を解消した: evtx と JSON のスキャンループにコピー&ペーストされた末尾処理(レコード構築 → タイムライン → ルール実行 → 結果のストリーム出力またはバッファリング)があり、「バッチごとに出力するか最終的にソート出力するためにバッファするか」の分岐が3箇所に重複していた。単一のemit_or_bufferポリシーヘルパーと、末尾処理を共通化したprocess_detection_batch(ループ間で異なる2点を名前付きのBatchPolicyでパラメータ化)を抽出した。動作は不変で、ソートされた出力はサンプルevtxコーパスの CSV・JSON でバイト単位で同一、ライブラリとバイナリの全テストがパスする。 (#1865) (@YamatoSecurity)main.rsの約1,030行の巨大な関数App::execをサブコマンドごとのメソッドに分割し、ディスパッチャとして読めるようにした:run_update_rules、run_level_tuning、run_set_default_profile、run_list_profiles、run_expand_list(早期リターンする5つの match アーム)、run_pivot_keywords_output(pivot-keywords-list の出力ブロック)、prompt_critical_systems_config(config-critical-systems の対話プロンプト。37行のColorfulThemeリテラルを含むが、抽出後も同じ位置で呼び出しており、"Start time:" の前にプロンプトが表示される挙動を維持)、print_closing_summary(経過時間/レポートリンク/HTMLレンダリング/エラーログ/デバッグ統計の末尾処理)。各メソッドの本体は元のコードをそのまま移したもので、stored_staticを自由に借用できるよう、ディスパッチは借用ではなく所有されたstored_static.config.action.clone()に対して match するようにした。挙動は保存され、csv-timeline/json-timeline/pivot-keywords-listの出力はサンプルevtxコーパスでバイト単位で同一であることを確認済み。 (#1877) (@YamatoSecurity)src/detections/configs.rsの重複した clap 引数を解消した:--clobber(8個のサブコマンド構造体で宣言)、--timeline-start/--timeline-end(各4個)、--disable-abbreviations(4個)を、共有のClobberOption/TimeRangeOption/DisableAbbreviationsOption構造体に抽出し、各サブコマンドに#[clap(flatten)]した。これにより、新しいサブコマンドはこれらの引数を再宣言する代わりに共有構造体をフラット化するだけでよくなった。CLI の変更はなく、各サブコマンドの--helpはバイト単位で同一、検出結果もサンプルevtxコーパス(CSV・JSON)でバイト単位で同一。 (#1867) (@YamatoSecurity)STORED_EKEY_ALIASグローバル可変状態(lazy_static RwLock<Option<EventKeyAliasConfig>>)を廃止し、エイリアス設定を明示的に渡すようにした:create_log_record/create_agg_log_recordは&stored_static.eventkey_aliasを直接読み、count/countup/create_rec_infoは明示的な&EventKeyAliasConfigを受け取る(ルールキーとともにRecordBuildContextにまとめ、ファイルごとに一度だけ構築して各レコードの並列タスクへArcで共有)。約33箇所の*STORED_EKEY_ALIAS.write() = Some(...)のテスト用初期化を削除した。動作は不変で、検出結果はサンプルevtxコーパス(CSV・JSON)でバイト単位で同一。プロセスグローバルな可変状態を廃止する最初の段階(残りのグローバルは今後の対応)。 (#1869) (@YamatoSecurity)- ジオIP関連の4つのグローバル可変状態(
GEOIP_DB_PARSER/GEOIP_DB_YAML/GEOIP_FILTERと ルックアップキャッシュIP_MAP)を廃止した: キャッシュはGeoIPSearchのMutexフィールドになり(convert_ip_to_geoで使用)、3つのDB/設定グローバルはStoredStaticのフィールド(geo_ip_search: Option<Arc<GeoIPSearch>>、geo_ip_db_yaml、geo_ip_filter)になり、各箇所(create_log_record、emit_csv_inner、load_profile)でstored_static.geo_ip_*として読むようにした。動作は不変で、--GeoIP(ジオIP経路を実行)で検出結果はサンプルevtxコーパス(CSV・JSON)でバイト単位で同一。プロセスグローバルな可変状態を廃止する2番目の段階。 (#1870) (@YamatoSecurity) CHECKPOINTグローバル可変状態(lazy_static Mutex<CheckPointProcessTimer>、フェーズ計測用のストップウォッチ)を廃止した:App::exec/App::analysis_files(どちらも&mut self)からのみ使われ、並列タスクからは使われないため、Appの素のcheckpointフィールドにした(グローバルもMutexも不要)。11箇所のCHECKPOINT.lock()...をself.checkpointに置き換えた。動作は不変で、タイマーはフェーズのラップを記録し--debug時に出力するだけであり、--debug実行では引き続き Rule Parse / Analysis / Output の処理時間と合計経過時間が表示される。プロセスグローバルな可変状態を廃止する3番目の段階。 (#1871) (@YamatoSecurity)HTML_REPORTERグローバル可変状態(lazy_static RwLock<HtmlReporter>、HTMLレポートのアキュムレータ)を廃止した:add_md_dataをHtmlReporterのメソッドにし、create_htmlは&selfを取るようにした。App::execが単一のHtmlReporterを所有し、レポートのセクション(ルール読み込み情報、結果サマリー、プロファイル/概要)を記録する各経路に&mutで渡してから描画するようにした(グローバルへの書き込みと最後のread().to_owned().create_html()を置き換え)。テストがグローバルを共有するためだけに存在していたHTML_REPORTER_TEST_LOCKも削除し、レポートセクションのキーはすべてGENERAL_OVERVIEW_SECTION/RESULTS_SUMMARY_SECTION定数を使うようにした。動作は不変で、--html-reportで生成されるHTMLはサンプルevtxコーパスでバイト単位で同一(非決定的な経過時間を除く)。プロセスグローバルな可変状態を廃止する4番目の段階。 (#1872) (@YamatoSecurity)