github Yamato-Security/hayabusa v4.0.0
v4.0.0 - BlackHat Arsenal USA Release

3 hours ago

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-timeline and json-timeline commands into a single dfir-timeline command whose output format is chosen with -t, --output-type: unspecified or -t csv outputs CSV (default), -t json outputs JSON, and -t jsonl outputs JSONL. The value is case-insensitive (e.g. -t JSONL). The -t shorthand, previously --threads, now belongs to --output-type (--threads keeps its long form). The CSV-only options -M, --multiline, -S, --tab-separator, and -R, --remove-duplicate-data are grouped under a new "CSV Output" help category and now error if used with a non-CSV -t. The old json-timeline -L, --JSONL-output flag 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 (--GeoIP was 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-data now 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 glibc aarch64-linux-gnu-gcc, which produced a binary killed at startup.) (#1332) (@YamatoSecurity)
  • The logon-summary command now also counts RDP session events: Security 4778/4779 (session reconnect/disconnect, which carry the RDP client's workstation name and IP) and the Terminal Services operational events RemoteConnectionManager/Operational 1149 (network-level authentication) and LocalSessionManager/Operational 25 (session reconnect) — on top of the existing LocalSessionManager 21 and Gateway 302. This keeps RDP logons summarized even when the matching Security 4624 has 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 classic 403/600 (Windows PowerShell) to the extract-base64 command. 4100/4102 scan the ContextInfo (Host Application = powershell -encodedcommand ...) and Payload fields; 403/600 scan the EventData.Data detail blob like the existing 400. (#1889) (@YamatoSecurity)
  • Added First Logon/Last Logon columns to the logon-summary successful-logons table and First Attempt/Last Attempt columns 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-summary and pivot-keywords-list producing differently ordered output on every run, so two scans of the same logs could not be diffed. eid-metrics and logon-summary sorted only by count, leaving equal-count rows in HashMap iteration order, which is reseeded per process; ties are now broken by channel and event ID, and by the logon grouping key. pivot-keywords-list wrote each category's keywords in IndexSet insertion 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 ComplexData event fields (e.g. the IdleState/PerfState values in Kernel-Processor-Power EID 26) not being extracted correctly: the two Name attributes collapsed into a Name array and the field values were dropped entirely. They are now keyed by their Name attribute like normal <Data> fields (fixed in the bundled hayabusa-evtx, bumped to 0.9.10). (#1520) (@YamatoSecurity)
  • Fixed -c (custom rules config directory) being ignored by pivot-keywords-list, which always loaded pivot_keywords.txt from the bundled config next to the executable. It now resolves pivot_keywords.txt through the -c directory (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_value file-open error printing the literal placeholder {path} instead of the file path (the error string was a plain string literal rather than a format!). (#1897) (@YamatoSecurity)
  • Fixed a u16 underflow in the eid-metrics table's "Event" column width on terminals narrower than 55 columns: terminal_width - 55 underflowed 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 a NaiveDateTime, which discards the offset and stored the local wall-clock time as if it were UTC — skewing the log-metrics First/Last Timestamp columns as well as the eid-metrics/logon-summary time ranges (EventMetrics::stats_time_cnt and parse_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's geoip_field_mapping.yaml: the lookup used the extensionless name geoip_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's timeframe. In judge_timeframe's sliding window, the branch handling a window that did not satisfy the count condition did an unchecked add_data(right) before dropping the left edge, pulling records[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-extend right with the timeframe check, so no record beyond the timeframe is counted. Bare count() was unaffected. (#1896) (@YamatoSecurity)
  • Fixed narrow-terminal issues in the results summary: _print_timeline_hist did unchecked usize subtraction that could underflow on a very narrow terminal (huge allocation / hang) — it now skips the histogram when there is no room and uses saturating_sub; the rule-authors table computed its row count with a hard-coded is_multiple_of(4) instead of the actual column count (uneven tables at most widths) — now authors_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_ordered correlations not enforcing the rule's group-by value 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_ordered correlations 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-evtx crate to 0.9.11 (hayabusa-evtx#93). That release is a dependency refresh with no .rs changes, so evtx parsing behavior is unchanged: dfir-timeline CSV/JSON/JSONL output (including with -x, --recover-records) and log-metrics output 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 from Cargo.toml at compile time via env!("CARGO_PKG_VERSION"), instead of being hard-coded in each help_template. The release name lives in a single RELEASE_NAME constant in src/detections/configs.rs. Cutting a release now means bumping the version in Cargo.toml and editing that one constant, rather than hand-updating the banner in every subcommand. Help output is byte-identical. (#1909) (@YamatoSecurity)
  • Bumped the compact_str dependency from 0.9 to 0.10. (#1909) (@YamatoSecurity)
  • Renamed 292 one-character local variables — let/for/if let bindings, 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 now computer, a File::open result is file, a record row in a .map is row, 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 the csv-timeline/json-timeline output over the 599-file sample-evtx corpus is byte-for-byte unchanged. (#1905) (@YamatoSecurity)
  • Reorganized DefaultMatcher's pipe-modifier handling into per-category modules under src/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-enumerated match arms spread across pipe_element.rs and default_matcher.rs; it now lives in one category module per group, with the value-matching modifiers (cidr/numeric/fieldref) behind a small ValueMatcher trait so DefaultMatcher::is_match_inner is a single value_match call. The PipeElement enum and the MatchPlan normalization 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-evtx csv-timeline corpus). (#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 new rulenode.rs holding RuleNode, DetectionNode, CorrelationType, create_rule/get_detection_keys, and the tests, and moved AggResult to count.rs (next to the aggregation logic and the AggRecordTimeInfo it depends on); mod.rs is 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_files function in src/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 a WizardCountFilter struct instead of a leading bool plus three Vec<&str>, now unit-tested), apply_channel_filters() (the logon-summary/config-critical-systems/log-metrics ad-hoc channel filters), and build_progress_bar(). analysis_files now 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-wizard scans) was confirmed a faithful line-by-line move by an adversarial review. (#1885) (@YamatoSecurity)
  • Refactored the ~700-line StoredStatic::create_static_data function in src/detections/configs.rs. It took an Option<Config> that was never None and .unwrap()ed it 33 times; the signature now takes Config and binds the action once (let action = config.action.as_ref()), removing the repeated unwraps and the panic-on-None footgun (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 repeated LEVEL::iter().any(...) level validation, and compact_string_set() for the ~120 lines of include/exclude computer and EID .iter().map(CompactString::from).collect() copy-paste. Net −250 lines in configs.rs with no behavior change — output is byte-identical across every affected subcommand (csv-timeline, json-timeline, logon-summary, computer-metrics, log-metrics, search; eid-metrics and the logon-summary successful 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_keyword and search_regex carried a verbatim-identical ~65-line tail (extract the event info, look up the Provider_EventID field-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 single emit_hit method 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_KEYWORD global mutable (a lazy_static RwLock<IndexMap<String, PivotKeyword>>, the pivot-keywords-list keyword accumulator): it became a pivot_keyword: Arc<RwLock<PivotKeywordMap>> field on StoredStatic. Because the keyword values are filled in from the per-record parallel tasks (insert_pivot_keyword in execute_rule, which reads stored_static from the STORED_STATIC global clone) and read back in App::exec (a separate instance), the Arc lets StoredStatic::clone() share one map across both (mirroring the existing geo_ip_search field). load_pivot_keywords/insert_pivot_keyword now take the map explicitly, and the main.rs read sites use stored_static.pivot_keyword. The PIVOT_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-list produces 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, on main too, because the keywords are inserted from the parallel tasks). (#1873) (@YamatoSecurity)
  • Retired the ERROR_LOG_STACK global mutable (a lazy_static Mutex<Nested<String>>, the run-wide error-log accumulator flushed to ./logs/errorlog-<timestamp>.log): it became an error_log_stack: Arc<Mutex<Nested<String>>> field on StoredStatic. The Arc lets StoredStatic::clone() share one stack with the STORED_STATIC global clone, because errors are pushed from the per-record parallel tasks too (a count(field) miss runs inside RuleNode::select, called per rule from execute_rule). The ~28 push sites that already had stored_static in scope now use stored_static.error_log_stack; the four functions that only took a bare quiet_errors_flag (create_error_log, get_file_size, peek_channel_from_evtx_first_record/create_channel_filter, and the countcreate_count_keyget_alias_value_in_record chain) take an explicit &Mutex<Nested<String>> threaded from their callers — including through filter_evtx_filesapply_channel_filter so log-metrics channel 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 its Failed to open evtx file entry). (#1875) (@YamatoSecurity)
  • Retired the STORED_STATIC global mutable (a lazy_static RwLock<Option<StoredStatic>>, the god-struct itself and the last of the process globals): Detection::execute_rule runs each rule in its own spawned task and read the shared StoredStatic back out of this global, which analysis_files published once before the scan. It had exactly one production reader (execute_rule) and one production writer, so it was purely the fallback that handed stored_static to the 'static spawned tasks. Now analysis_files snapshots stored_static once into an Arc<StoredStatic> — the same single clone the global held, and its Arc-wrapped inner fields (error_log_stack, pivot_keyword) stay shared with the live stored_static, so parallel-path accumulation is still visible — and threads it through analysis_file/analysis_json_file/process_detection_batch into Detection::start/execute_rules, which hands each spawned task its own cheap Arc::clone (mirroring the existing records_arc); execute_rule reads its stored_static argument. 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 (a DashMap) and COMPUTER_MITRE_ATTCK_UNIQUE_KEYS (a DashSet), the per-computer MITRE ATT&CK tactic accumulators for the HTML report. They became computer_mitre_attck_map: Arc<DashMap<..>> / computer_mitre_attck_unique_keys: Arc<DashSet<..>> fields on StoredStatic (mirroring pivot_keyword/error_log_stack): written from the per-record parallel tasks (create_log_record in execute_rule, via the shared Arc) and read/cleared when the report is rendered (_output_html_computer_by_mitre_attck now takes stored_static). With STORED_STATIC already gone, no shared-mutable state remains to force serialized tests, so RUST_TEST_THREADS: 1 is dropped from the rust.yml and coverage.yml workflows — the lib + bin suites pass under full parallelism. Behavior-preserving — csv-timeline output 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_MAP and COMPUTER_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 intermediate Vec when 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 single NarySelectionNode parameterised by a logical All/Any operator, 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 a serde_json-backed helper, so backslash/quote/control-character escaping is handled by the serializer instead of by inspection. Byte-identical csv-timeline and json-timeline output (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-timeline record assembly on serde_json — each record is now an ordered serde_json value serialized by the library instead of hand-rolled string concatenation, deleting _create_json_output_format, _convert_valid_json_str, json_escape_body, and process_target_stock. csv-timeline output is byte-identical; json-timeline data is identical (verified with an order-preserving canonical compare on the sample-evtx corpus) except that repeated Details arrays 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 and EvtxFile paths), now preserved verbatim. (#1851) (@YamatoSecurity)
  • Third step of the JSON-serialization refactor (#1845): the json-timeline output no longer goes through a csv::Writer abused as a plain-text sink — JSON/JSONL records are now written directly to the target (via a ResultWriter enum), while csv-timeline keeps using csv::Writer. Byte-identical output for csv-timeline, json-timeline (-o) and json-timeline (-L), verified on the sample-evtx corpus. (#1852) (@YamatoSecurity)
  • Renamed the obscure afterfact module and its identifiers to self-describing result-output names: src/afterfact.rssrc/results.rs, AfterfactInfoResultOutputState, AfterfactWriterOutputWriter, output_afterfactoutput_results, output_additional_afterfactoutput_result_summary, and the local afterfact_info/afterfact_writerresult_state/output_writer. Pure rename; no behavior change (full test suite passes). (#1853) (@YamatoSecurity)
  • Removed the 🛂 emoji-sentinel placeholders that remove_sp_char used to protect \n/\r/\t from its control-character stripping (final step of the #1845 refactor). The real characters are now kept and handled per output format — escaped by serde_json for JSON, collapsed to spaces for CSV/search. csv-timeline output is byte-identical; json-timeline output changes: an interior newline/tab/CR in a value now serializes as a proper \n/\t/\r escape instead of the old visible \\n text, and leading/trailing newlines in Details values 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.rs into a src/results/ module directory (mod.rs plus csv.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-author RuleAuthor values for --multiline/--tab-separator CSV (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 canonical MatchPlan { wrap, encoding, utf16, cased, windash, all } in one O(n) pass and builds the matcher from that, and the now-subsumed create_fast_match helper 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|contains matched case-insensitively) — is now normalized like its canonical form. Part 1 of #1861; the matchers.rs module split is a follow-up. (#1862) (@YamatoSecurity)
  • Split the ~4,900-line src/detections/rule/matchers.rs into a src/detections/rule/matchers/ module directory (mod.rs plus leaf_matchers.rs, default_matcher.rs, pipe_element.rs, tests.rs), separating the LeafMatcher trait, the file-based matchers, DefaultMatcher/MatchPlan, the PipeElement enum, 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 single emit_or_buffer policy helper and a shared process_detection_batch for the tail (parameterized by a named BatchPolicy for 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::exec god function in main.rs into 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-line ColorfulTheme literal — extracted but still called in place so the prompt keeps appearing before "Start time:"), and print_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 borrow stored_static freely the dispatch now matches on an owned stored_static.config.action.clone() instead of a borrow. Behavior-preserving — csv-timeline/json-timeline/pivot-keywords-list output 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 shared ClobberOption/TimeRangeOption/DisableAbbreviationsOption structs 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 --help is byte-identical, and detection output is byte-identical on the sample-evtx corpus (CSV and JSON). (#1867) (@YamatoSecurity)
  • Retired the STORED_EKEY_ALIAS global mutable (a lazy_static RwLock<Option<EventKeyAliasConfig>>) in favor of passing the alias config explicitly: create_log_record/create_agg_log_record now read &stored_static.eventkey_alias directly, count/countup/create_rec_info take an explicit &EventKeyAliasConfig (grouped with the rule keys into a RecordBuildContext built once per scanned file and Arc-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_FILTER and the IP_MAP lookup cache): the cache became a Mutex field on GeoIPSearch (used by convert_ip_to_geo), and the three DB/config globals became StoredStatic fields (geo_ip_search: Option<Arc<GeoIPSearch>>, geo_ip_db_yaml, geo_ip_filter), read via stored_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 CHECKPOINT global mutable (a lazy_static Mutex<CheckPointProcessTimer>, the phase-timing stopwatch): it was only ever used from App::exec/App::analysis_files (both &mut self) and never from the parallel tasks, so it became a plain checkpoint field on App (no global, no Mutex), with the 11 CHECKPOINT.lock()... sites replaced by self.checkpoint. Behavior-preserving — the timer only records phase laps and prints them under --debug; a --debug run 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_REPORTER global mutable (a lazy_static RwLock<HtmlReporter>, the HTML-report accumulator): add_md_data became a method on HtmlReporter and create_html now takes &self, and App::exec owns a single HtmlReporter threaded as &mut down every path that records report sections (rule-load info, results summary, profile/overview) before rendering it — replacing the global writes and the final read().to_owned().create_html(). The HTML_REPORTER_TEST_LOCK (only needed because tests shared the global) is also gone, and the report-section keys now use the GENERAL_OVERVIEW_SECTION/RESULTS_SUMMARY_SECTION constants everywhere. Behavior-preserving — with --html-report the 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 のセッションイベントも集計するようにした。既存の LocalSessionManager 21 と Gateway 302 に加えて、Security 4778/4779(セッションの再接続/切断。RDP クライアントのワークステーション名とIPを含む)と、Terminal Services の運用ログの RemoteConnectionManager/Operational 1149(ネットワークレベル認証)・LocalSessionManager/Operational 25(セッション再接続)を対象に追加した。これにより、対応する Security 4624 がログからあふれて消えている場合でも RDP ログオンを集計でき、再接続/切断では接続元クライアントのホスト名も表示される。 (#1893) (@YamatoSecurity)
  • extract-base64 コマンドに、PowerShellのイベントID 4100/4102(Microsoft-Windows-PowerShell/Operational および PowerShellCore/Operational)と、クラシックの 403/600(Windows PowerShell)を追加した。4100/4102ContextInfoHost Application = powershell -encodedcommand ...)と Payload フィールドを、403/600 は既存の 400 と同様に EventData.Data の詳細ブロックをスキャンする。 (#1889) (@YamatoSecurity)
  • logon-summary の成功ログオンテーブルに First Logon/Last Logon 列を、失敗ログオンテーブルに First Attempt/Last Attempt 列を追加した。各アカウント/ソースの組み合わせがログオン(または試行)した時間の範囲を表示する。 (#1883) (@YamatoSecurity)

バグ修正:

  • eid-metricslogon-summarypivot-keywords-list の出力順が実行のたびに変わり、同じログをスキャンした結果同士を差分比較できない問題を修正した。eid-metricslogon-summary は件数のみでソートしていたため、件数が同じ行はプロセスごとに再シードされる HashMap の反復順のままになっていた。同数の場合はチャンネルとイベントID、およびログオンのグルーピングキーで順序を決めるようにした。pivot-keywords-list は各カテゴリのキーワードを IndexSet の挿入順で書き出していたが、値はレコードごとの並列タスクから挿入されるため順序が変動していた。ソートしてから出力するようにした。変わるのは順序だけで、行やキーワードの内容は同じである。 (#1912) (@YamatoSecurity)
  • 結果サマリのテーブルで、上位5件のルールタイトルや作成者名にマルチバイト UTF-8(例: 日本語のルールタイトル)が含まれる場合にパニックする問題を修正した。タイトル/作成者をバイト単位のスライス(&title[..32]&author[0..24])で切り詰めていたため、バイト位置がマルチバイト文字の途中に来るとパニックしていた。出力をパイプ/リダイレクトした場合(幅の既定値により32バイトを超えるタイトルが切り詰められる)、スキャン完了後に実行全体がクラッシュし、サマリが失われていた。切り詰めを文字境界で行うようにし、幅の上限計算にも飽和減算を用いて、極端に狭いターミナルでもアンダーフローしないようにした。 (#1904) (@YamatoSecurity)
  • ComplexData のイベントフィールド(例: Kernel-Processor-Power EID 26IdleState/PerfState の値)が正しく抽出されない問題を修正した。2つの Name 属性が Name の配列にまとめられ、フィールドの値が失われていた。通常の <Data> フィールドと同様に、Name 属性をキーとして展開するようにした(同梱の hayabusa-evtx0.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_cntparse_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_histusize の減算をチェックせずに行っていたため、極端に狭いターミナルでアンダーフローして巨大なメモリ確保やハングを引き起こす可能性があった(描画スペースが無い場合はヒストグラムをスキップし、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/ 配下のカテゴリ別モジュール(stringregexnumericcidrfieldrefencoding)に再編した。各修飾子のロジック(パターンのラップ/ワイルドカード変換、正規表現フラグ、数値比較、cidr、フィールド参照、base64/utf16 エンコード)は、これまで pipe_element.rsdefault_matcher.rs に散らばった手書きの match 分岐にあったが、グループごとに1つのカテゴリモジュールにまとめ、値を直接マッチする修飾子(cidr/numeric/fieldref)は小さな ValueMatcher トレイトの背後に置いたため、DefaultMatcher::is_match_innervalue_match の1回の呼び出しになった。PipeElement 列挙型と #1862MatchPlan による正規化は変更していないため、Sigma 修飾子の追加は複数のディスパッチ箇所ではなく1つのカテゴリモジュールの編集で済む。純粋な再編で、157個のマッチャーテストがパスし、検知結果の出力はバイト単位で同一(サンプルevtxの CSV/JSON/search と、2,838個のevtxに対する csv-timeline コーパス)。 (#1895) (@YamatoSecurity)
  • src/detections/rule/mod.rs(モジュール宣言と、コアとなるルール型や大きなテストモジュールが混在した約1,200行)を、RuleNodeDetectionNodeCorrelationTypecreate_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-timelinejson-timelinelogon-summarycomputer-metricslog-metricssearch)で出力はバイト単位で同一(eid-metricslogon-summary の成功テーブルも一致する。ただし未改変のバイナリにも存在するグループ内の同点時の並び順の非決定性を除く)。 (#1884) (@YamatoSecurity)
  • search コマンドのヒット処理ロジックの重複を解消した。search_keywordsearch_regex は、マッチ判定の後に完全に同一な約65行の末尾処理(イベント情報の抽出、Provider_EventID によるフィールド略称テーブルの参照、AllFieldInfo の空白の正規化、そしてソート用にヒットを収集するか逐次書き出すかの分岐)を重複して持っており、ヒット処理への修正を2箇所に適用する必要があって、2つのコピーが乖離する恐れがあった。両方のループがマッチ判定の後に呼び出す単一の emit_hit メソッドに抽出した。出力は、キーワード検索・正規表現検索それぞれのソートあり/逐次出力の両方でバイト単位で同一。 (#1881) (@YamatoSecurity)
  • グローバルミュータブルな PIVOT_KEYWORDlazy_static RwLock<IndexMap<String, PivotKeyword>> で、pivot-keywords-list のキーワードアキュムレータ)を廃止し、StoredStaticpivot_keyword: Arc<RwLock<PivotKeywordMap>> フィールドにした。キーワードの値はレコードごとの並列タスク(execute_rule 内の insert_pivot_keywordSTORED_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_STACKlazy_static Mutex<Nested<String>> で、./logs/errorlog-<timestamp>.log にフラッシュされる実行全体のエラーログ蓄積用)を廃止し、StoredStaticerror_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_logget_file_sizepeek_channel_from_evtx_first_record/create_channel_filter、および countcreate_count_keyget_alias_value_in_record のチェーン)は、呼び出し元から引き渡される明示的な &Mutex<Nested<String>> を受け取るようにした。これには filter_evtx_filesapply_channel_filter 経由も含まれ、log-metrics のチャンネルフィルタリングが引き続きevtxオープンエラーを報告できるようにした。挙動は保存される。collect-and-flush のセマンティクスは同一で、エンドツーエンドで検証済み(破損したevtxは引き続き Failed to open evtx file エントリを含むエラーログを書き出す)。 (#1875) (@YamatoSecurity)
  • グローバルミュータブルな STORED_STATIClazy_static RwLock<Option<StoredStatic>>、巨大構造体そのものであり、プロセスグローバルの最後の1つ)を廃止した。Detection::execute_rule は各ルールを自身の spawn したタスク内で実行し、共有される StoredStatic をこのグローバルから読み戻していた。これはスキャン前に analysis_files が一度だけ公開したものである。プロダクションでの読み手はちょうど1つ(execute_rule)、書き手も1つだけであり、純粋に 'static な spawn したタスクへ stored_static を渡すためのフォールバックにすぎなかった。現在は analysis_filesstored_static を一度だけ Arc<StoredStatic> にスナップショットし(グローバルが保持していたのと同じ単一のクローンであり、Arc でラップされた内部フィールド(error_log_stackpivot_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_MAPDashMap)と COMPUTER_MITRE_ATTCK_UNIQUE_KEYSDashSet)——HTMLレポート用のコンピュータごとのMITRE ATT&CKタクティクのアキュムレータ——を廃止した。これらは(pivot_keyword/error_log_stack に倣って)StoredStaticcomputer_mitre_attck_map: Arc<DashMap<..>> / computer_mitre_attck_unique_keys: Arc<DashSet<..>> フィールドにした。レコードごとの並列タスク(execute_rule 内の create_log_record、共有 Arc 経由)から書き込まれ、レポートのレンダリング時に読み取り/クリアされる(_output_html_computer_by_mitre_attckstored_static を受け取るようになった)。STORED_STATIC が既に廃止されたことで、テストの直列化を強制する共有ミュータブルな状態がなくなったため、rust.ymlcoverage.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_MAPCOMPUTER_MITRE_ATTCK_UNIQUE_KEYS)を、テーブル出力後にクリアすることで単一レポートの範囲に限定した。これにより、同一プロセス内で後続のレポートを生成してもキーが残留してタクティクごとのユニーク数が過少カウントされることがなくなった。あわせて、タクティクのセルを結合する際の中間 Vec を削除した。通常の(単一レポートの)実行では挙動に変更はない。 (#1840) (@YamatoSecurity)
  • 3つのほぼ同一な検知セレクションノード型(AndSelectionNodeAllSelectionNodeOrSelectionNode)を、論理演算子 All/Any でパラメータ化した単一の NarySelectionNode に統合し、重複コード約105行を削除した。純粋なリファクタリングで、出力がバイト単位で同一であることを確認済み。 (#1843) (@YamatoSecurity)
  • afterfact.rs の手書きのJSON文字列エスケープ(_convert_valid_json_str.replace('🛂', "\\").replace('\\', "\\\\").replace('"', "\\\"") の連鎖)を serde_json ベースのヘルパーに置き換え、バックスラッシュ・引用符・制御文字のエスケープを手作業ではなくシリアライザに任せるようにした。csv-timelinejson-timeline の出力はバイト単位で同一(サンプルevtxコーパスで検証、新しいリグレッションテストと既存の文字列一致テストで担保)。#1845 の最初のステップ。 (#1850) (@YamatoSecurity)
  • #1845 で追跡しているJSONシリアライズのリファクタリングの第2段階として、json-timeline のレコード生成を serde_json ベースに書き換えた: 各レコードを順序を保持した serde_json の値として組み立て、ライブラリにシリアライズさせるようにし、手書きの文字列連結(_create_json_output_format_convert_valid_json_strjson_escape_bodyprocess_target_stock)を削除した。csv-timeline の出力はバイト単位で同一。json-timeline のデータも同一(サンプルevtxコーパスに対する順序保持のカノニカル比較で検証済み)だが、繰り返し出現する Details の配列が複数行で出力されるようになり、JSONLは標準的なコンパクト形式("k":v)になった。さらに、旧JSONL経路が値の中の連続スペースを除去して(著者一覧や EvtxFile のパスを壊して)いたデータ破損バグを修正し、値をそのまま保持するようにした。 (#1851) (@YamatoSecurity)
  • JSONシリアライズのリファクタリング(#1845)の第3段階として、json-timeline の出力をテキストシンクとして流用していた csv::Writer を経由せず、JSON/JSONLのレコードを(ResultWriter enum を介して)出力先へ直接書き込むようにした。csv-timeline は引き続き csv::Writer を使用する。csv-timelinejson-timeline-o)・json-timeline-L)の出力はバイト単位で同一(サンプルevtxコーパスで検証済み)。 (#1852) (@YamatoSecurity)
  • 分かりにくい afterfact モジュールとその識別子を、内容を表す結果出力向けの名前にリネームした: src/afterfact.rssrc/results.rsAfterfactInfoResultOutputStateAfterfactWriterOutputWriteroutput_afterfactoutput_resultsoutput_additional_afterfactoutput_result_summary、ローカル変数の afterfact_info/afterfact_writerresult_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.rssrc/results/ モジュールディレクトリ(mod.rscsv.rsjson.rsdisplay.rssummary.rshtml_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] == Containsutf16|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.rssrc/detections/rule/matchers/ モジュールディレクトリ(mod.rsleaf_matchers.rsdefault_matcher.rspipe_element.rstests.rs)に分割し、LeafMatcher トレイト、ファイルベースのマッチャー、DefaultMatcher/MatchPlanPipeElement 列挙型、ユニットテストを分離した。純粋なコードの移動で、出力はバイト単位で同一(サンプル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_rulesrun_level_tuningrun_set_default_profilerun_list_profilesrun_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-timelinejson-timelinepivot-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)を廃止した: キャッシュは GeoIPSearchMutex フィールドになり(convert_ip_to_geo で使用)、3つのDB/設定グローバルは StoredStatic のフィールド(geo_ip_search: Option<Arc<GeoIPSearch>>geo_ip_db_yamlgeo_ip_filter)になり、各箇所(create_log_recordemit_csv_innerload_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_dataHtmlReporter のメソッドにし、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)

Don't miss a new hayabusa release

NewReleases is sending notifications on new releases.