Skip to main content

forensic_catalog/
artifact.rs

1//! Universal forensic artifact catalog.
2//!
3//! Provides a self-describing, queryable registry of forensic artifact locations
4//! (registry keys, files, event logs) with embedded decode logic. Consumers
5//! query the catalog by id, hive, scope, or MITRE technique and receive fully
6//! decoded [`ArtifactRecord`] values -- never raw bytes.
7//!
8//! # Design principles
9//!
10//! - **Zero mandatory external deps** -- FILETIME conversion and ROT13 are pure
11//!   math/ASCII. Timestamps are ISO 8601 `String`s.
12//! - **`const`/`static`-friendly** -- [`ArtifactDescriptor`] and its constituent
13//!   enums are all constructible in `const` context. [`Decoder`] is flat (no
14//!   recursive `&'static Decoder`).
15//! - **Additive** -- existing modules (`ports`, `persistence`, ...) are untouched.
16//!   This module is purely additive.
17//! - **Single source of truth** -- artifact paths, decode logic, field schemas,
18//!   and MITRE mappings live here. Consumers never hardcode them.
19
20// ── Core enums ───────────────────────────────────────────────────────────────
21
22/// The kind of forensic artifact location.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum ArtifactType {
25    /// A registry key (container of values).
26    RegistryKey,
27    /// A specific registry value.
28    RegistryValue,
29    /// A file on disk.
30    File,
31    /// A directory on disk.
32    Directory,
33    /// A Windows Event Log channel.
34    EventLog,
35    /// A region of process/physical memory.
36    MemoryRegion,
37}
38
39/// Which Windows registry hive an artifact lives in.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum HiveTarget {
42    HklmSystem,
43    HklmSoftware,
44    HklmSam,
45    HklmSecurity,
46    NtUser,
47    UsrClass,
48    Amcache,
49    Bcd,
50    /// Non-registry artifacts (files, event logs, memory).
51    None,
52}
53
54/// Whether the artifact is per-user, system-wide, or mixed.
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
56pub enum DataScope {
57    User,
58    System,
59    Network,
60    Mixed,
61}
62
63/// Minimum OS version / platform required for the artifact to exist.
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
65pub enum OsScope {
66    // ── Windows ──────────────────────────────────────────────────────────
67    All,
68    Win7Plus,
69    Win8Plus,
70    Win10Plus,
71    Win11Plus,
72    Win11_22H2,
73    // ── Linux ────────────────────────────────────────────────────────────
74    /// All Linux distributions (kernel + standard POSIX userland).
75    Linux,
76    /// systemd-based distros (Ubuntu 16.04+, Fedora 15+, Debian 8+, Arch).
77    LinuxSystemd,
78    /// Debian / Ubuntu specific paths or tools.
79    LinuxDebian,
80    /// Red Hat / CentOS / Fedora specific paths.
81    LinuxRhel,
82}
83
84// ── Binary field layout ──────────────────────────────────────────────────────
85
86/// Primitive type of a field inside a fixed-layout binary record.
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum BinaryFieldType {
89    U16Le,
90    U32Le,
91    U64Le,
92    I32Le,
93    I64Le,
94    FiletimeLe,
95    Bytes { len: usize },
96}
97
98/// One field inside a fixed-layout binary record (e.g. the 72-byte UserAssist
99/// value). Fully `const`-constructible.
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub struct BinaryField {
102    pub name: &'static str,
103    pub offset: usize,
104    pub field_type: BinaryFieldType,
105    pub description: &'static str,
106}
107
108// ── Decoder ──────────────────────────────────────────────────────────────────
109
110/// Describes how to decode raw bytes (and/or a registry value name) into
111/// structured fields.
112///
113/// This enum is intentionally **flat** -- no recursive `&'static Decoder` --
114/// so every variant is usable in `const`/`static` context.
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum Decoder {
117    /// Pass-through: interpret raw bytes as UTF-8 text. Single field "value".
118    Identity,
119    /// ROT13-decode the *name* parameter. Single field "program".
120    Rot13Name,
121    /// Read an 8-byte little-endian FILETIME at the given byte offset.
122    FiletimeAt { offset: usize },
123    /// Interpret raw bytes as UTF-16LE text.
124    Utf16Le,
125    /// Split the *name* (or raw as UTF-8) on `|` and zip with field names.
126    PipeDelimited { fields: &'static [&'static str] },
127    /// Read a little-endian u32 from raw bytes.
128    DwordLe,
129    /// REG_MULTI_SZ: NUL-separated UTF-16LE strings terminated by double NUL.
130    MultiSz,
131    /// MRUListEx: u32-LE index list terminated by 0xFFFFFFFF.
132    MruListEx,
133    /// Parse a fixed-layout binary record using the given field descriptors.
134    BinaryRecord(&'static [BinaryField]),
135    /// ROT13-decode the *name*, then parse the binary *value* using field
136    /// descriptors. Combined output has "program" plus all binary fields.
137    Rot13NameWithBinaryValue(&'static [BinaryField]),
138}
139
140// ── Field schema (describes output fields) ───────────────────────────────────
141
142/// The semantic type of a decoded output field value.
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
144pub enum ValueType {
145    Text,
146    Integer,
147    UnsignedInt,
148    Timestamp,
149    Bytes,
150    Bool,
151    List,
152}
153
154/// Describes one field in a decoded artifact record -- purely metadata, no data.
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub struct FieldSchema {
157    pub name: &'static str,
158    pub value_type: ValueType,
159    pub description: &'static str,
160    /// If `true`, this field participates in the record's unique identifier.
161    pub is_uid_component: bool,
162}
163
164/// Triage collection priority for this artifact during live incident response.
165#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
166pub enum TriagePriority {
167    /// Must collect immediately — volatile, high forensic value, or credential exposure.
168    Critical = 3,
169    /// Collect in first pass — strong execution/persistence evidence.
170    High = 2,
171    /// Collect when time permits — useful but less time-sensitive.
172    Medium = 1,
173    /// Collect last — low volatility, supporting evidence only.
174    Low = 0,
175}
176
177// ── ArtifactDescriptor (the catalog entry) ───────────────────────────────────
178
179/// A single entry in the forensic artifact catalog. Fully `const`-constructible
180/// so it can live in a `static`.
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub struct ArtifactDescriptor {
183    /// Short machine-readable identifier, e.g. `"userassist"`.
184    pub id: &'static str,
185    /// Human-readable display name.
186    pub name: &'static str,
187    /// What kind of artifact location this is.
188    pub artifact_type: ArtifactType,
189    /// Which registry hive, or `None` for non-registry artifacts.
190    pub hive: Option<HiveTarget>,
191    /// Registry key path relative to the hive root (empty for non-registry).
192    pub key_path: &'static str,
193    /// Specific registry value name, if targeting a single value.
194    pub value_name: Option<&'static str>,
195    /// Filesystem path, for file/directory artifacts.
196    pub file_path: Option<&'static str>,
197    /// User vs System vs Mixed scope.
198    pub scope: DataScope,
199    /// Minimum OS version required.
200    pub os_scope: OsScope,
201    /// How to decode the raw data.
202    pub decoder: Decoder,
203    /// Forensic meaning / significance of this artifact.
204    pub meaning: &'static str,
205    /// MITRE ATT&CK technique IDs.
206    pub mitre_techniques: &'static [&'static str],
207    /// Schema of the decoded output fields.
208    pub fields: &'static [FieldSchema],
209    /// How long this artifact typically persists before being overwritten or rotated.
210    /// `None` means indefinite (registry keys, most files until explicitly deleted).
211    pub retention: Option<&'static str>,
212    /// Live triage collection priority.
213    pub triage_priority: TriagePriority,
214    /// IDs of related catalog descriptors useful for cross-correlation.
215    pub related_artifacts: &'static [&'static str],
216    /// Authoritative external references for this artifact (SANS, Harlan Carvey,
217    /// Brian Carrier, Red Canary, Microsoft docs, MITRE ATT&CK, etc.).
218    /// Every production entry should have at least one URL.
219    pub sources: &'static [&'static str],
220}
221
222// ── ArtifactValue (universal decoded value) ──────────────────────────────────
223
224/// A decoded value produced by the catalog's decode logic. Uses only `std` types.
225#[derive(Debug, Clone, PartialEq)]
226pub enum ArtifactValue {
227    Text(String),
228    Integer(i64),
229    UnsignedInt(u64),
230    Timestamp(String),
231    Bytes(Vec<u8>),
232    Bool(bool),
233    List(Vec<ArtifactValue>),
234    Map(Vec<(String, ArtifactValue)>),
235    Null,
236}
237
238// ── ArtifactRecord (universal decoded output) ────────────────────────────────
239
240/// A fully decoded forensic artifact record. This is the universal output type
241/// that all consumers receive -- no raw bytes, no hardcoded field names.
242#[derive(Debug, Clone, PartialEq)]
243pub struct ArtifactRecord {
244    /// Globally unique URI, e.g. `winreg://HKCU/Software/.../value_name` or
245    /// `file:///path/to/file#line`.
246    pub uid: String,
247    /// The catalog entry id that produced this record.
248    pub artifact_id: &'static str,
249    /// Human-readable artifact name.
250    pub artifact_name: &'static str,
251    /// Data scope (User/System/...).
252    pub scope: DataScope,
253    /// OS scope.
254    pub os_scope: OsScope,
255    /// Primary timestamp in ISO 8601 UTC, if the artifact has one.
256    pub timestamp: Option<String>,
257    /// Ordered decoded field name-value pairs.
258    pub fields: Vec<(&'static str, ArtifactValue)>,
259    /// Human-readable meaning, possibly with interpolated field values.
260    pub meaning: String,
261    /// MITRE ATT&CK technique IDs applicable to this record.
262    pub mitre_techniques: Vec<&'static str>,
263    /// Confidence score 0.0-1.0, set by the decoder or classifier.
264    pub confidence: f32,
265}
266
267// ── ArtifactQuery (filter parameters) ────────────────────────────────────────
268
269/// Filter parameters for querying the catalog. All fields are optional --
270/// `None` means "match any".
271#[derive(Debug, Clone, Default)]
272pub struct ArtifactQuery {
273    pub scope: Option<DataScope>,
274    pub os_scope: Option<OsScope>,
275    pub artifact_type: Option<ArtifactType>,
276    pub hive: Option<HiveTarget>,
277    pub mitre_technique: Option<&'static str>,
278    pub id: Option<&'static str>,
279}
280
281// ── DecodeError ──────────────────────────────────────────────────────────────
282
283/// Errors that can occur during artifact decoding.
284#[derive(Debug, Clone, PartialEq, Eq)]
285pub enum DecodeError {
286    /// The raw data buffer is too short for the decoder to operate.
287    BufferTooShort { expected: usize, actual: usize },
288    /// The raw data is not valid UTF-8 where UTF-8 was expected.
289    InvalidUtf8,
290    /// The raw data is not valid UTF-16LE.
291    InvalidUtf16,
292    /// A binary field offset+size exceeds the buffer length.
293    FieldOutOfBounds {
294        field: &'static str,
295        offset: usize,
296        size: usize,
297        buf_len: usize,
298    },
299    /// The decoder variant does not apply to this data shape.
300    UnsupportedDecoder(&'static str),
301}
302
303impl core::fmt::Display for DecodeError {
304    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
305        match self {
306            Self::BufferTooShort { expected, actual } => {
307                write!(f, "buffer too short: need {expected} bytes, got {actual}")
308            }
309            Self::InvalidUtf8 => write!(f, "invalid UTF-8 in raw data"),
310            Self::InvalidUtf16 => write!(f, "invalid UTF-16LE in raw data"),
311            Self::FieldOutOfBounds {
312                field,
313                offset,
314                size,
315                buf_len,
316            } => write!(
317                f,
318                "field '{field}' at offset {offset} size {size} exceeds buffer length {buf_len}"
319            ),
320            Self::UnsupportedDecoder(msg) => write!(f, "unsupported decoder: {msg}"),
321        }
322    }
323}
324
325impl std::error::Error for DecodeError {}
326
327// ── ForensicCatalog ──────────────────────────────────────────────────────────
328
329/// A queryable collection of [`ArtifactDescriptor`]s with built-in decode logic.
330pub struct ForensicCatalog {
331    entries: &'static [ArtifactDescriptor],
332}
333
334impl ForensicCatalog {
335    /// Create a new catalog from a static slice of descriptors.
336    pub const fn new(entries: &'static [ArtifactDescriptor]) -> Self {
337        Self { entries }
338    }
339
340    /// Return all descriptors in the catalog.
341    pub fn list(&self) -> &[ArtifactDescriptor] {
342        self.entries
343    }
344
345    /// Look up a descriptor by its `id` field.
346    pub fn by_id(&self, id: &str) -> Option<&ArtifactDescriptor> {
347        self.entries.iter().find(|d| d.id == id)
348    }
349
350    /// Return all descriptors matching the given query. Every `Some` field in
351    /// the query must match; `None` fields are wildcards.
352    pub fn filter(&self, query: &ArtifactQuery) -> Vec<&ArtifactDescriptor> {
353        self.entries
354            .iter()
355            .filter(|d| {
356                if let Some(scope) = query.scope {
357                    if d.scope != scope {
358                        return false;
359                    }
360                }
361                if let Some(os) = query.os_scope {
362                    if d.os_scope != os {
363                        return false;
364                    }
365                }
366                if let Some(at) = query.artifact_type {
367                    if d.artifact_type != at {
368                        return false;
369                    }
370                }
371                if let Some(hive) = query.hive {
372                    if d.hive != Some(hive) {
373                        return false;
374                    }
375                }
376                if let Some(tech) = query.mitre_technique {
377                    if !d.mitre_techniques.contains(&tech) {
378                        return false;
379                    }
380                }
381                if let Some(id) = query.id {
382                    if d.id != id {
383                        return false;
384                    }
385                }
386                true
387            })
388            .collect()
389    }
390
391    /// Return all descriptors associated with the given MITRE ATT&CK technique ID.
392    pub fn by_mitre(&self, technique: &str) -> Vec<&ArtifactDescriptor> {
393        self.entries
394            .iter()
395            .filter(|d| d.mitre_techniques.contains(&technique))
396            .collect()
397    }
398
399    /// Return all descriptors sorted by triage priority descending (Critical first).
400    /// Within the same priority, original catalog order is preserved.
401    pub fn for_triage(&self) -> Vec<&ArtifactDescriptor> {
402        let mut v: Vec<&ArtifactDescriptor> = self.entries.iter().collect();
403        v.sort_by_key(|d| std::cmp::Reverse(d.triage_priority));
404        v
405    }
406
407    /// Return all descriptors whose `meaning` or `name` contains `keyword`
408    /// (case-insensitive).
409    pub fn filter_by_keyword(&self, keyword: &str) -> Vec<&ArtifactDescriptor> {
410        let kw = keyword.to_ascii_lowercase();
411        self.entries
412            .iter()
413            .filter(|d| {
414                d.meaning.to_ascii_lowercase().contains(&kw)
415                    || d.name.to_ascii_lowercase().contains(&kw)
416            })
417            .collect()
418    }
419
420    /// Decode raw data using the descriptor's embedded decoder.
421    ///
422    /// # Parameters
423    /// - `descriptor` -- the catalog entry describing the artifact
424    /// - `name` -- the registry value name (or filename), used by ROT13 and
425    ///   PipeDelimited decoders
426    /// - `raw` -- the raw byte payload of the registry value or file content
427    pub fn decode(
428        &self,
429        descriptor: &ArtifactDescriptor,
430        name: &str,
431        raw: &[u8],
432    ) -> Result<ArtifactRecord, DecodeError> {
433        decode_artifact(descriptor, name, raw)
434    }
435}
436
437// ── Decode implementation ────────────────────────────────────────────────────
438
439/// ROT13-decode an ASCII string: rotate A-Z and a-z by 13, leave other chars.
440fn rot13(s: &str) -> String {
441    s.chars()
442        .map(|c| match c {
443            'A'..='Z' => (b'A' + (c as u8 - b'A' + 13) % 26) as char,
444            'a'..='z' => (b'a' + (c as u8 - b'a' + 13) % 26) as char,
445            other => other,
446        })
447        .collect()
448}
449
450/// Convert a Windows FILETIME (100ns ticks since 1601-01-01) to ISO 8601 UTC.
451///
452/// Returns `None` for zero or negative Unix epoch values.
453fn filetime_to_iso8601(ft: u64) -> Option<String> {
454    // FILETIME epoch is 1601-01-01. Unix epoch offset in 100ns ticks:
455    const EPOCH_DIFF: u64 = 116_444_736_000_000_000;
456    if ft == 0 {
457        return None;
458    }
459    if ft < EPOCH_DIFF {
460        return None;
461    }
462    let unix_secs = (ft - EPOCH_DIFF) / 10_000_000;
463
464    // Convert unix_secs to calendar date/time via pure arithmetic.
465    // Algorithm: days since epoch -> year/month/day; remainder -> H:M:S.
466    let secs_per_day: u64 = 86400;
467    let mut days = unix_secs / secs_per_day;
468    let day_secs = unix_secs % secs_per_day;
469    let hours = day_secs / 3600;
470    let minutes = (day_secs % 3600) / 60;
471    let seconds = day_secs % 60;
472
473    // Civil date from days since 1970-01-01 (Euclidean affine algorithm).
474    // Shift epoch to 0000-03-01 to make leap-year logic simpler.
475    days += 719_468; // days from 0000-03-01 to 1970-01-01
476    let era = days / 146_097;
477    let doe = days - era * 146_097; // day of era [0, 146096]
478    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365;
479    let y = yoe + era * 400;
480    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
481    let mp = (5 * doy + 2) / 153;
482    let d = doy - (153 * mp + 2) / 5 + 1;
483    let m = if mp < 10 { mp + 3 } else { mp - 9 };
484    let y = if m <= 2 { y + 1 } else { y };
485
486    Some(format!(
487        "{y:04}-{m:02}-{d:02}T{hours:02}:{minutes:02}:{seconds:02}Z"
488    ))
489}
490
491/// Read a u16 LE at `offset`, returning 0 if out of bounds.
492fn read_u16_le(data: &[u8], offset: usize) -> u16 {
493    if offset + 2 > data.len() {
494        return 0;
495    }
496    u16::from_le_bytes([data[offset], data[offset + 1]])
497}
498
499/// Read a u32 LE at `offset`, returning 0 if out of bounds.
500fn read_u32_le(data: &[u8], offset: usize) -> u32 {
501    if offset + 4 > data.len() {
502        return 0;
503    }
504    u32::from_le_bytes([
505        data[offset],
506        data[offset + 1],
507        data[offset + 2],
508        data[offset + 3],
509    ])
510}
511
512/// Read a u64 LE at `offset`, returning 0 if out of bounds.
513fn read_u64_le(data: &[u8], offset: usize) -> u64 {
514    if offset + 8 > data.len() {
515        return 0;
516    }
517    u64::from_le_bytes([
518        data[offset],
519        data[offset + 1],
520        data[offset + 2],
521        data[offset + 3],
522        data[offset + 4],
523        data[offset + 5],
524        data[offset + 6],
525        data[offset + 7],
526    ])
527}
528
529/// Read an i32 LE at `offset`, returning 0 if out of bounds.
530fn read_i32_le(data: &[u8], offset: usize) -> i32 {
531    if offset + 4 > data.len() {
532        return 0;
533    }
534    i32::from_le_bytes([
535        data[offset],
536        data[offset + 1],
537        data[offset + 2],
538        data[offset + 3],
539    ])
540}
541
542/// Read an i64 LE at `offset`, returning 0 if out of bounds.
543fn read_i64_le(data: &[u8], offset: usize) -> i64 {
544    if offset + 8 > data.len() {
545        return 0;
546    }
547    i64::from_le_bytes([
548        data[offset],
549        data[offset + 1],
550        data[offset + 2],
551        data[offset + 3],
552        data[offset + 4],
553        data[offset + 5],
554        data[offset + 6],
555        data[offset + 7],
556    ])
557}
558
559/// Decode a single [`BinaryField`] from a raw buffer into an [`ArtifactValue`].
560fn decode_binary_field(field: &BinaryField, raw: &[u8]) -> Result<ArtifactValue, DecodeError> {
561    let size = match field.field_type {
562        BinaryFieldType::U16Le => 2,
563        BinaryFieldType::U32Le | BinaryFieldType::I32Le => 4,
564        BinaryFieldType::U64Le | BinaryFieldType::I64Le | BinaryFieldType::FiletimeLe => 8,
565        BinaryFieldType::Bytes { len } => len,
566    };
567    if field.offset + size > raw.len() {
568        return Err(DecodeError::FieldOutOfBounds {
569            field: field.name,
570            offset: field.offset,
571            size,
572            buf_len: raw.len(),
573        });
574    }
575    Ok(match field.field_type {
576        BinaryFieldType::U16Le => {
577            ArtifactValue::UnsignedInt(u64::from(read_u16_le(raw, field.offset)))
578        }
579        BinaryFieldType::U32Le => {
580            ArtifactValue::UnsignedInt(u64::from(read_u32_le(raw, field.offset)))
581        }
582        BinaryFieldType::U64Le => ArtifactValue::UnsignedInt(read_u64_le(raw, field.offset)),
583        BinaryFieldType::I32Le => ArtifactValue::Integer(i64::from(read_i32_le(raw, field.offset))),
584        BinaryFieldType::I64Le => ArtifactValue::Integer(read_i64_le(raw, field.offset)),
585        BinaryFieldType::FiletimeLe => {
586            let ft = read_u64_le(raw, field.offset);
587            match filetime_to_iso8601(ft) {
588                Some(ts) => ArtifactValue::Timestamp(ts),
589                None => ArtifactValue::Null,
590            }
591        }
592        BinaryFieldType::Bytes { len } => {
593            ArtifactValue::Bytes(raw[field.offset..field.offset + len].to_vec())
594        }
595    })
596}
597
598/// Build the default UID for a registry artifact.
599fn build_registry_uid(descriptor: &ArtifactDescriptor, name: &str) -> String {
600    let hive_prefix = match descriptor.hive {
601        Some(HiveTarget::NtUser) => "HKCU",
602        Some(HiveTarget::UsrClass) => "HKCU_Classes",
603        Some(HiveTarget::HklmSoftware) => "HKLM\\SOFTWARE",
604        Some(HiveTarget::HklmSystem) => "HKLM\\SYSTEM",
605        Some(HiveTarget::HklmSam) => "HKLM\\SAM",
606        Some(HiveTarget::HklmSecurity) => "HKLM\\SECURITY",
607        Some(HiveTarget::Amcache) => "Amcache",
608        Some(HiveTarget::Bcd) => "BCD",
609        Some(HiveTarget::None) | None => "unknown",
610    };
611    if name.is_empty() {
612        format!("winreg://{}/{}", hive_prefix, descriptor.key_path)
613    } else {
614        format!("winreg://{}/{}/{}", hive_prefix, descriptor.key_path, name)
615    }
616}
617
618/// Build the default UID for a file artifact.
619fn build_file_uid(descriptor: &ArtifactDescriptor, name: &str) -> String {
620    let path = descriptor.file_path.unwrap_or("");
621    if name.is_empty() {
622        format!("file://{path}")
623    } else {
624        format!("file://{path}#{name}")
625    }
626}
627
628/// Decode a slice of [`BinaryField`]s from raw bytes, returning field values
629/// and the first FILETIME timestamp encountered (if any).
630#[allow(clippy::type_complexity)]
631fn decode_binary_fields(
632    binary_fields: &[BinaryField],
633    raw: &[u8],
634) -> Result<(Vec<(&'static str, ArtifactValue)>, Option<String>), DecodeError> {
635    let mut decoded = Vec::new();
636    let mut ts = None;
637    for bf in binary_fields {
638        let val = decode_binary_field(bf, raw)?;
639        if bf.field_type == BinaryFieldType::FiletimeLe {
640            if let ArtifactValue::Timestamp(ref s) = val {
641                if ts.is_none() {
642                    ts = Some(s.clone());
643                }
644            }
645        }
646        decoded.push((bf.name, val));
647    }
648    Ok((decoded, ts))
649}
650
651/// Core decode function: routes to the appropriate decoder variant.
652#[allow(clippy::too_many_lines)]
653fn decode_artifact(
654    descriptor: &ArtifactDescriptor,
655    name: &str,
656    raw: &[u8],
657) -> Result<ArtifactRecord, DecodeError> {
658    let (fields, timestamp) = match descriptor.decoder {
659        Decoder::Identity => {
660            let text = std::str::from_utf8(raw)
661                .map_err(|_| DecodeError::InvalidUtf8)?
662                .to_string();
663            (vec![("value", ArtifactValue::Text(text))], None)
664        }
665
666        Decoder::Rot13Name => {
667            let decoded = rot13(name);
668            (vec![("program", ArtifactValue::Text(decoded))], None)
669        }
670
671        Decoder::FiletimeAt { offset } => {
672            if offset + 8 > raw.len() {
673                return Err(DecodeError::BufferTooShort {
674                    expected: offset + 8,
675                    actual: raw.len(),
676                });
677            }
678            let ft = read_u64_le(raw, offset);
679            let ts = filetime_to_iso8601(ft);
680            (
681                vec![(
682                    "timestamp",
683                    match ts {
684                        Some(ref s) => ArtifactValue::Timestamp(s.clone()),
685                        None => ArtifactValue::Null,
686                    },
687                )],
688                ts,
689            )
690        }
691
692        Decoder::Utf16Le => {
693            if raw.len() % 2 != 0 {
694                return Err(DecodeError::InvalidUtf16);
695            }
696            let u16s: Vec<u16> = raw
697                .chunks_exact(2)
698                .map(|c| u16::from_le_bytes([c[0], c[1]]))
699                .collect();
700            // Trim trailing NUL(s).
701            let trimmed: &[u16] = match u16s.iter().position(|&c| c == 0) {
702                Some(pos) => &u16s[..pos],
703                None => &u16s,
704            };
705            let text = String::from_utf16(trimmed).map_err(|_| DecodeError::InvalidUtf16)?;
706            (vec![("value", ArtifactValue::Text(text))], None)
707        }
708
709        Decoder::PipeDelimited {
710            fields: field_names,
711        } => {
712            // Try name first; fall back to raw as UTF-8.
713            let source = if name.is_empty() {
714                std::str::from_utf8(raw)
715                    .map_err(|_| DecodeError::InvalidUtf8)?
716                    .to_string()
717            } else {
718                name.to_string()
719            };
720            let parts: Vec<&str> = source.split('|').collect();
721            let decoded_fields: Vec<(&'static str, ArtifactValue)> = field_names
722                .iter()
723                .enumerate()
724                .map(|(i, &fname)| {
725                    let val = match parts.get(i) {
726                        Some(s) => ArtifactValue::Text((*s).to_string()),
727                        None => ArtifactValue::Null,
728                    };
729                    (fname, val)
730                })
731                .collect();
732            (decoded_fields, None)
733        }
734
735        Decoder::DwordLe => {
736            if raw.len() < 4 {
737                return Err(DecodeError::BufferTooShort {
738                    expected: 4,
739                    actual: raw.len(),
740                });
741            }
742            let val = read_u32_le(raw, 0);
743            (
744                vec![("value", ArtifactValue::UnsignedInt(u64::from(val)))],
745                None,
746            )
747        }
748
749        Decoder::MultiSz => {
750            // REG_MULTI_SZ: UTF-16LE, NUL-separated, double NUL terminated.
751            if raw.len() < 2 {
752                return Ok(make_record(
753                    descriptor,
754                    name,
755                    vec![("values", ArtifactValue::List(vec![]))],
756                    None,
757                ));
758            }
759            if raw.len() % 2 != 0 {
760                return Err(DecodeError::InvalidUtf16);
761            }
762            let u16s: Vec<u16> = raw
763                .chunks_exact(2)
764                .map(|c| u16::from_le_bytes([c[0], c[1]]))
765                .collect();
766            // Split on NUL, dropping the final empty string(s) from the double NUL.
767            let strings: Vec<ArtifactValue> = u16s
768                .split(|&c| c == 0)
769                .filter(|s| !s.is_empty())
770                .map(|s| ArtifactValue::Text(String::from_utf16_lossy(s)))
771                .collect();
772            (vec![("values", ArtifactValue::List(strings))], None)
773        }
774
775        Decoder::MruListEx => {
776            // u32 LE index list terminated by 0xFFFFFFFF.
777            let mut indices = Vec::new();
778            let mut offset = 0;
779            while offset + 4 <= raw.len() {
780                let idx = read_u32_le(raw, offset);
781                if idx == 0xFFFF_FFFF {
782                    break;
783                }
784                indices.push(ArtifactValue::UnsignedInt(u64::from(idx)));
785                offset += 4;
786            }
787            (vec![("indices", ArtifactValue::List(indices))], None)
788        }
789
790        Decoder::BinaryRecord(binary_fields) => decode_binary_fields(binary_fields, raw)?,
791
792        Decoder::Rot13NameWithBinaryValue(binary_fields) => {
793            let (mut fields, ts) = decode_binary_fields(binary_fields, raw)?;
794            fields.insert(0, ("program", ArtifactValue::Text(rot13(name))));
795            (fields, ts)
796        }
797    };
798
799    Ok(make_record(descriptor, name, fields, timestamp))
800}
801
802/// Construct an [`ArtifactRecord`] from decoded fields.
803fn make_record(
804    descriptor: &ArtifactDescriptor,
805    name: &str,
806    fields: Vec<(&'static str, ArtifactValue)>,
807    timestamp: Option<String>,
808) -> ArtifactRecord {
809    let uid = match descriptor.artifact_type {
810        ArtifactType::File | ArtifactType::Directory => build_file_uid(descriptor, name),
811        _ => build_registry_uid(descriptor, name),
812    };
813    ArtifactRecord {
814        uid,
815        artifact_id: descriptor.id,
816        artifact_name: descriptor.name,
817        scope: descriptor.scope,
818        os_scope: descriptor.os_scope,
819        timestamp,
820        fields,
821        meaning: descriptor.meaning.to_string(),
822        mitre_techniques: descriptor.mitre_techniques.to_vec(),
823        confidence: 1.0,
824    }
825}
826
827// ── Static descriptor instances ──────────────────────────────────────────────
828
829/// UserAssist 72-byte binary value fields (Win7+ EXE GUID).
830static USERASSIST_BINARY_FIELDS: &[BinaryField] = &[
831    BinaryField {
832        name: "run_count",
833        offset: 4,
834        field_type: BinaryFieldType::U32Le,
835        description: "Number of times the program was launched",
836    },
837    BinaryField {
838        name: "focus_count",
839        offset: 8,
840        field_type: BinaryFieldType::U32Le,
841        description: "Number of times the program received input focus",
842    },
843    BinaryField {
844        name: "focus_duration_ms",
845        offset: 12,
846        field_type: BinaryFieldType::U32Le,
847        description: "Total focus time in milliseconds",
848    },
849    BinaryField {
850        name: "last_run",
851        offset: 60,
852        field_type: BinaryFieldType::FiletimeLe,
853        description: "FILETIME of the last execution",
854    },
855];
856
857/// UserAssist field schema (decoded output description).
858static USERASSIST_FIELDS: &[FieldSchema] = &[
859    FieldSchema {
860        name: "program",
861        value_type: ValueType::Text,
862        description: "ROT13-decoded program path or name",
863        is_uid_component: true,
864    },
865    FieldSchema {
866        name: "run_count",
867        value_type: ValueType::UnsignedInt,
868        description: "Number of times launched",
869        is_uid_component: false,
870    },
871    FieldSchema {
872        name: "focus_count",
873        value_type: ValueType::UnsignedInt,
874        description: "Number of times received focus",
875        is_uid_component: false,
876    },
877    FieldSchema {
878        name: "focus_duration_ms",
879        value_type: ValueType::UnsignedInt,
880        description: "Total focus time in milliseconds",
881        is_uid_component: false,
882    },
883    FieldSchema {
884        name: "last_run",
885        value_type: ValueType::Timestamp,
886        description: "FILETIME of last execution as ISO 8601",
887        is_uid_component: false,
888    },
889];
890
891/// UserAssist EXE entries (NTUSER.DAT).
892///
893/// GUID: `{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}`
894/// Key: `Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist\{GUID}\Count`
895/// Decoder: ROT13 the value name + parse 72-byte binary value.
896pub static USERASSIST_EXE: ArtifactDescriptor = ArtifactDescriptor {
897    id: "userassist_exe",
898    name: "UserAssist (EXE)",
899    artifact_type: ArtifactType::RegistryValue,
900    hive: Some(HiveTarget::NtUser),
901    key_path: r"Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist\{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}\Count",
902    value_name: None, // enumerate all values
903    file_path: None,
904    scope: DataScope::User,
905    os_scope: OsScope::Win7Plus,
906    decoder: Decoder::Rot13NameWithBinaryValue(USERASSIST_BINARY_FIELDS),
907    meaning: "Program execution history with launch counts and timestamps",
908    mitre_techniques: &["T1059", "T1204.002"],
909    fields: USERASSIST_FIELDS,
910    retention: None,
911    triage_priority: TriagePriority::High,
912    related_artifacts: &["prefetch_dir", "shimcache", "srum_app_resource"],
913    sources: &[
914        "https://attack.mitre.org/techniques/T1059/",
915        "https://attack.mitre.org/techniques/T1204/002/",
916        "https://www.sans.org/blog/computer-forensic-artifacts-windows-7-userassist/",
917        "https://windowsir.blogspot.com/2004/02/userassist.html",
918        "http://windowsir.blogspot.com/2007/09/more-on-userassist-keys.html",
919        "https://www.magnetforensics.com/blog/artifact-profile-userassist/",
920    ],
921};
922
923/// Run key field schema.
924static RUN_KEY_FIELDS: &[FieldSchema] = &[FieldSchema {
925    name: "value",
926    value_type: ValueType::Text,
927    description: "Autostart command or path",
928    is_uid_component: false,
929}];
930
931/// HKLM SOFTWARE Run key -- system-wide autostart persistence.
932pub static RUN_KEY_HKLM_RUN: ArtifactDescriptor = ArtifactDescriptor {
933    id: "run_key_hklm",
934    name: "Run Key (HKLM)",
935    artifact_type: ArtifactType::RegistryKey,
936    hive: Some(HiveTarget::HklmSoftware),
937    key_path: r"Microsoft\Windows\CurrentVersion\Run",
938    value_name: None,
939    file_path: None,
940    scope: DataScope::System,
941    os_scope: OsScope::All,
942    decoder: Decoder::Identity,
943    meaning: "System-wide autostart entry executed at every user logon",
944    mitre_techniques: &["T1547.001"],
945    fields: RUN_KEY_FIELDS,
946    retention: None,
947    triage_priority: TriagePriority::High,
948    related_artifacts: &[
949        "run_key_hklm_run",
950        "services_imagepath",
951        "scheduled_tasks_dir",
952    ],
953    sources: &[
954        "https://attack.mitre.org/techniques/T1547/001/",
955        "https://learn.microsoft.com/en-us/windows/win32/setupapi/run-and-runonce-registry-keys",
956        "https://windowsir.blogspot.com/2013/01/run-mru.html",
957    ],
958};
959
960/// TypedURLs field schema.
961static TYPED_URLS_FIELDS: &[FieldSchema] = &[FieldSchema {
962    name: "value",
963    value_type: ValueType::Text,
964    description: "URL typed into the IE/Edge address bar",
965    is_uid_component: true,
966}];
967
968/// Internet Explorer / Edge TypedURLs (NTUSER.DAT).
969pub static TYPED_URLS: ArtifactDescriptor = ArtifactDescriptor {
970    id: "typed_urls",
971    name: "TypedURLs (IE/Edge)",
972    artifact_type: ArtifactType::RegistryKey,
973    hive: Some(HiveTarget::NtUser),
974    key_path: r"Software\Microsoft\Internet Explorer\TypedURLs",
975    value_name: None,
976    file_path: None,
977    scope: DataScope::User,
978    os_scope: OsScope::All,
979    decoder: Decoder::Identity,
980    meaning: "URLs manually typed into the Internet Explorer or Edge address bar",
981    mitre_techniques: &["T1071.001"],
982    fields: TYPED_URLS_FIELDS,
983    retention: None,
984    triage_priority: TriagePriority::Medium,
985    related_artifacts: &[],
986    sources: &[
987        "https://attack.mitre.org/techniques/T1071/001/",
988        "https://www.sans.org/blog/digital-forensics-windows-registry-forensics-part-6-internet-explorer-user-typed-urls/",
989        "https://windowsir.blogspot.com/2006/04/typed-urls.html",
990        "https://crucialsecurity.wordpress.com/2011/03/14/typedurls-part-1/",
991    ],
992};
993
994/// PCA AppLaunch.dic pipe-delimited fields.
995static PCA_FIELDS_SCHEMA: &[FieldSchema] = &[
996    FieldSchema {
997        name: "exe_path",
998        value_type: ValueType::Text,
999        description: "Full path to the executable",
1000        is_uid_component: true,
1001    },
1002    FieldSchema {
1003        name: "timestamp",
1004        value_type: ValueType::Text,
1005        description: "Launch timestamp string",
1006        is_uid_component: false,
1007    },
1008];
1009
1010static PCA_PIPE_FIELDS: &[&str] = &["exe_path", "timestamp"];
1011
1012/// Program Compatibility Assistant AppLaunch.dic (Win11 22H2+).
1013///
1014/// A pipe-delimited text file where each line records an application launch.
1015pub static PCA_APPLAUNCH_DIC: ArtifactDescriptor = ArtifactDescriptor {
1016    id: "pca_applaunch_dic",
1017    name: "PCA AppLaunch.dic",
1018    artifact_type: ArtifactType::File,
1019    hive: None,
1020    key_path: "",
1021    value_name: None,
1022    file_path: Some(r"C:\Windows\appcompat\pca\AppLaunch.dic"),
1023    scope: DataScope::System,
1024    os_scope: OsScope::Win11_22H2,
1025    decoder: Decoder::PipeDelimited {
1026        fields: PCA_PIPE_FIELDS,
1027    },
1028    meaning: "Program execution evidence from the Program Compatibility Assistant",
1029    mitre_techniques: &["T1059", "T1204.002"],
1030    fields: PCA_FIELDS_SCHEMA,
1031    retention: None,
1032    triage_priority: TriagePriority::High,
1033    related_artifacts: &[],
1034    sources: &[
1035        "https://attack.mitre.org/techniques/T1059/",
1036        "https://attack.mitre.org/techniques/T1204/002/",
1037        "https://aboutdfir.com/new-windows-11-pro-22h2-evidence-of-execution-artifact/",
1038        "https://www.sygnia.co/blog/new-windows-11-pca-artifact/",
1039        "https://github.com/Psmths/windows-forensic-artifacts/blob/main/execution/program-compatibility-assistant.md",
1040    ],
1041};
1042
1043// ── Run key HKCU variants ────────────────────────────────────────────────────
1044
1045/// HKCU Run key — per-user autostart persistence.
1046pub static RUN_KEY_HKCU_RUN: ArtifactDescriptor = ArtifactDescriptor {
1047    id: "run_key_hkcu",
1048    name: "Run Key (HKCU)",
1049    artifact_type: ArtifactType::RegistryKey,
1050    hive: Some(HiveTarget::NtUser),
1051    key_path: r"Software\Microsoft\Windows\CurrentVersion\Run",
1052    value_name: None,
1053    file_path: None,
1054    scope: DataScope::User,
1055    os_scope: OsScope::All,
1056    decoder: Decoder::Identity,
1057    meaning: "Per-user autostart programs executed at every logon without elevation. \
1058              Lower-privilege than HKLM Run — writable by the user account itself, \
1059              making it a common unprivileged persistence location that survives password resets.",
1060    mitre_techniques: &["T1547.001"],
1061    fields: RUN_KEY_FIELDS,
1062    retention: None,
1063    triage_priority: TriagePriority::High,
1064    related_artifacts: &["run_key_hklm_run", "startup_folder_user"],
1065    sources: &[
1066        "https://attack.mitre.org/techniques/T1547/001/",
1067        "https://learn.microsoft.com/en-us/windows/win32/setupapi/run-and-runonce-registry-keys",
1068        "https://windowsir.blogspot.com/2013/01/run-mru.html",
1069    ],
1070};
1071
1072/// HKCU RunOnce — per-user one-shot autostart (deleted after execution).
1073pub static RUN_KEY_HKCU_RUNONCE: ArtifactDescriptor = ArtifactDescriptor {
1074    id: "run_key_hkcu_once",
1075    name: "RunOnce Key (HKCU)",
1076    artifact_type: ArtifactType::RegistryKey,
1077    hive: Some(HiveTarget::NtUser),
1078    key_path: r"Software\Microsoft\Windows\CurrentVersion\RunOnce",
1079    value_name: None,
1080    file_path: None,
1081    scope: DataScope::User,
1082    os_scope: OsScope::All,
1083    decoder: Decoder::Identity,
1084    meaning: "Per-user one-time autostart, deleted after first execution",
1085    mitre_techniques: &["T1547.001"],
1086    fields: RUN_KEY_FIELDS,
1087    retention: None,
1088    triage_priority: TriagePriority::High,
1089    related_artifacts: &[],
1090    sources: &[
1091        "https://attack.mitre.org/techniques/T1547/001/",
1092        "https://learn.microsoft.com/en-us/windows/win32/setupapi/run-and-runonce-registry-keys",
1093    ],
1094};
1095
1096/// HKLM RunOnce — system-wide one-shot autostart.
1097pub static RUN_KEY_HKLM_RUNONCE: ArtifactDescriptor = ArtifactDescriptor {
1098    id: "run_key_hklm_once",
1099    name: "RunOnce Key (HKLM)",
1100    artifact_type: ArtifactType::RegistryKey,
1101    hive: Some(HiveTarget::HklmSoftware),
1102    key_path: r"Microsoft\Windows\CurrentVersion\RunOnce",
1103    value_name: None,
1104    file_path: None,
1105    scope: DataScope::System,
1106    os_scope: OsScope::All,
1107    decoder: Decoder::Identity,
1108    meaning: "System-wide one-time autostart, deleted after first execution",
1109    mitre_techniques: &["T1547.001"],
1110    fields: RUN_KEY_FIELDS,
1111    retention: None,
1112    triage_priority: TriagePriority::High,
1113    related_artifacts: &[],
1114    sources: &[
1115        "https://attack.mitre.org/techniques/T1547/001/",
1116        "https://learn.microsoft.com/en-us/windows/win32/setupapi/run-and-runonce-registry-keys",
1117    ],
1118};
1119
1120// ── IFEO ──────────────────────────────────────────────────────────────────────
1121
1122static IFEO_FIELDS: &[FieldSchema] = &[FieldSchema {
1123    name: "debugger",
1124    value_type: ValueType::Text,
1125    description: "Debugger path that hijacks the target process launch",
1126    is_uid_component: false,
1127}];
1128
1129/// Image File Execution Options — Debugger value hijack (T1546.012).
1130///
1131/// Attacker sets `Debugger` under a target EXE's IFEO key to redirect
1132/// its launch to an arbitrary binary (e.g., `cmd.exe`).
1133pub static IFEO_DEBUGGER: ArtifactDescriptor = ArtifactDescriptor {
1134    id: "ifeo_debugger",
1135    name: "IFEO Debugger Hijack",
1136    artifact_type: ArtifactType::RegistryValue,
1137    hive: Some(HiveTarget::HklmSoftware),
1138    key_path: r"Microsoft\Windows NT\CurrentVersion\Image File Execution Options",
1139    value_name: Some("Debugger"),
1140    file_path: None,
1141    scope: DataScope::System,
1142    os_scope: OsScope::All,
1143    decoder: Decoder::Identity,
1144    meaning: "Redirects target-process launch to an attacker-controlled binary",
1145    mitre_techniques: &["T1546.012"],
1146    fields: IFEO_FIELDS,
1147    retention: None,
1148    triage_priority: TriagePriority::Medium,
1149    related_artifacts: &[],
1150    sources: &[
1151        "https://attack.mitre.org/techniques/T1546/012/",
1152        "https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/enabling-postmortem-debugging",
1153        "https://www.sans.org/blog/malware-persistence-without-the-windows-registry/",
1154    ],
1155};
1156
1157// ── UserAssist (Folder GUID) ─────────────────────────────────────────────────
1158
1159/// UserAssist Folder GUID entries (NTUSER.DAT).
1160///
1161/// GUID: `{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}` — records folder access.
1162pub static USERASSIST_FOLDER: ArtifactDescriptor = ArtifactDescriptor {
1163    id: "userassist_folder",
1164    name: "UserAssist (Folder)",
1165    artifact_type: ArtifactType::RegistryValue,
1166    hive: Some(HiveTarget::NtUser),
1167    key_path: r"Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\Count",
1168    value_name: None,
1169    file_path: None,
1170    scope: DataScope::User,
1171    os_scope: OsScope::Win7Plus,
1172    decoder: Decoder::Rot13NameWithBinaryValue(USERASSIST_BINARY_FIELDS),
1173    meaning: "Folder navigation history with access counts and timestamps",
1174    mitre_techniques: &["T1083"],
1175    fields: USERASSIST_FIELDS,
1176    retention: None,
1177    triage_priority: TriagePriority::High,
1178    related_artifacts: &[],
1179    sources: &[
1180        "https://attack.mitre.org/techniques/T1083/",
1181        "https://www.sans.org/blog/computer-forensic-artifacts-windows-7-userassist/",
1182        "https://windowsir.blogspot.com/2004/02/userassist.html",
1183        "http://windowsir.blogspot.com/2007/09/more-on-userassist-keys.html",
1184        "https://www.magnetforensics.com/blog/artifact-profile-userassist/",
1185    ],
1186};
1187
1188// ── ShellBags ─────────────────────────────────────────────────────────────────
1189
1190static SHELLBAGS_FIELDS: &[FieldSchema] = &[FieldSchema {
1191    name: "indices",
1192    value_type: ValueType::List,
1193    description: "MRU order of accessed shell folder slots",
1194    is_uid_component: false,
1195}];
1196
1197/// ShellBags — folder navigation history in UsrClass.dat.
1198///
1199/// Records folders the user browsed via Explorer, including deleted, network,
1200/// and removable-media paths. Survives folder deletion.
1201pub static SHELLBAGS_USER: ArtifactDescriptor = ArtifactDescriptor {
1202    id: "shellbags_user",
1203    name: "ShellBags (User)",
1204    artifact_type: ArtifactType::RegistryKey,
1205    hive: Some(HiveTarget::UsrClass),
1206    key_path: r"Local Settings\Software\Microsoft\Windows\Shell\Bags",
1207    value_name: None,
1208    file_path: None,
1209    scope: DataScope::User,
1210    os_scope: OsScope::Win7Plus,
1211    decoder: Decoder::MruListEx,
1212    meaning: "Folder access history; persists paths even after folder deletion",
1213    mitre_techniques: &["T1083", "T1005"],
1214    fields: SHELLBAGS_FIELDS,
1215    retention: None,
1216    triage_priority: TriagePriority::High,
1217    related_artifacts: &[],
1218    sources: &[
1219        "https://attack.mitre.org/techniques/T1083/",
1220        "https://attack.mitre.org/techniques/T1005/",
1221        "https://www.sans.org/blog/shell-bag-forensics/",
1222        "https://windowsir.blogspot.com/2009/07/shellbag-analysis.html",
1223        "https://ericzimmerman.github.io/#!index.md",
1224        "https://www.sans.org/white-papers/34545/",
1225        "https://www.magnetforensics.com/blog/forensic-analysis-of-windows-shellbags/",
1226    ],
1227};
1228
1229// ── Amcache ───────────────────────────────────────────────────────────────────
1230
1231static AMCACHE_FIELDS: &[FieldSchema] = &[
1232    FieldSchema {
1233        name: "file_id",
1234        value_type: ValueType::Text,
1235        description: "Volume GUID + MFT file reference (unique file identity)",
1236        is_uid_component: true,
1237    },
1238    FieldSchema {
1239        name: "sha1",
1240        value_type: ValueType::Text,
1241        description: "SHA1 of the first 31.25 MB (0000-prefixed)",
1242        is_uid_component: false,
1243    },
1244];
1245
1246/// Amcache InventoryApplicationFile — program execution evidence with hashes.
1247pub static AMCACHE_APP_FILE: ArtifactDescriptor = ArtifactDescriptor {
1248    id: "amcache_app_file",
1249    name: "Amcache InventoryApplicationFile",
1250    artifact_type: ArtifactType::RegistryKey,
1251    hive: Some(HiveTarget::Amcache),
1252    key_path: r"Root\InventoryApplicationFile",
1253    value_name: None,
1254    file_path: None,
1255    scope: DataScope::System,
1256    os_scope: OsScope::Win8Plus,
1257    decoder: Decoder::Identity,
1258    meaning: "Program execution evidence with file hash; persists after binary deletion",
1259    mitre_techniques: &["T1218", "T1204.002"],
1260    fields: AMCACHE_FIELDS,
1261    retention: None,
1262    triage_priority: TriagePriority::High,
1263    related_artifacts: &["shimcache", "prefetch_dir", "srum_app_resource"],
1264    sources: &[
1265        "https://attack.mitre.org/techniques/T1218/",
1266        "https://attack.mitre.org/techniques/T1204/002/",
1267        "https://www.sans.org/blog/new-amcache-hve-in-windows-8-1-update-1/",
1268        "https://www.sansforensics.com/blog/amcache-hive-forensics/",
1269        "https://www.researchgate.net/publication/317258237_Leveraging_the_Windows_Amcachehve_File_in_Forensic_Investigations",
1270        "https://www.magnetforensics.com/blog/shimcache-vs-amcache-key-windows-forensic-artifacts/",
1271    ],
1272};
1273
1274// ── ShimCache (AppCompatCache) ────────────────────────────────────────────────
1275
1276static SHIMCACHE_FIELDS: &[FieldSchema] = &[FieldSchema {
1277    name: "raw",
1278    value_type: ValueType::Bytes,
1279    description: "Raw AppCompatCache binary blob (parsed by shimcache module)",
1280    is_uid_component: false,
1281}];
1282
1283/// ShimCache — application compatibility cache with executable metadata.
1284///
1285/// Stored as a single binary value `AppCompatCache` under the SYSTEM hive.
1286/// Contains executable paths and last-modified timestamps (NOT execution times
1287/// on Win8+). Parsed by the shimcache module.
1288pub static SHIMCACHE: ArtifactDescriptor = ArtifactDescriptor {
1289    id: "shimcache",
1290    name: "ShimCache (AppCompatCache)",
1291    artifact_type: ArtifactType::RegistryValue,
1292    hive: Some(HiveTarget::HklmSystem),
1293    key_path: r"CurrentControlSet\Control\Session Manager\AppCompatCache",
1294    value_name: Some("AppCompatCache"),
1295    file_path: None,
1296    scope: DataScope::System,
1297    os_scope: OsScope::All,
1298    decoder: Decoder::Identity,
1299    meaning: "Executable metadata cache; presence proves binary existed on disk",
1300    mitre_techniques: &["T1218", "T1059"],
1301    fields: SHIMCACHE_FIELDS,
1302    retention: Some("written at clean shutdown only; lost on crash/hard-power-off"),
1303    triage_priority: TriagePriority::Critical,
1304    related_artifacts: &["amcache_app_file", "prefetch_dir", "bam_user"],
1305    sources: &[
1306        "https://attack.mitre.org/techniques/T1218/",
1307        "https://attack.mitre.org/techniques/T1059/",
1308        "https://www.sans.org/blog/digital-forensics-shimcache/",
1309        "https://redcanary.com/blog/threat-detection/appcompatcache/",
1310        "https://www.sans.org/blog/mass-triage-part-4-processing-returned-files-appcache-shimcache/",
1311        "https://www.magnetforensics.com/blog/shimcache-vs-amcache-key-windows-forensic-artifacts/",
1312    ],
1313};
1314
1315// ── BAM / DAM ─────────────────────────────────────────────────────────────────
1316
1317static BAM_FIELDS: &[FieldSchema] = &[FieldSchema {
1318    name: "last_exec",
1319    value_type: ValueType::Timestamp,
1320    description: "FILETIME of last background execution",
1321    is_uid_component: false,
1322}];
1323
1324/// Background Activity Moderator — per-user background process execution times.
1325///
1326/// Each value under a SID sub-key is the executable path; value data is an
1327/// 8-byte FILETIME of the last execution. Win10 1709+.
1328pub static BAM_USER: ArtifactDescriptor = ArtifactDescriptor {
1329    id: "bam_user",
1330    name: "BAM (Background Activity Moderator)",
1331    artifact_type: ArtifactType::RegistryValue,
1332    hive: Some(HiveTarget::HklmSystem),
1333    key_path: r"CurrentControlSet\Services\bam\State\UserSettings",
1334    value_name: None,
1335    file_path: None,
1336    scope: DataScope::Mixed,
1337    os_scope: OsScope::Win10Plus,
1338    decoder: Decoder::FiletimeAt { offset: 0 },
1339    meaning: "Last execution time of background/UWP processes per-user SID",
1340    mitre_techniques: &["T1059", "T1204"],
1341    fields: BAM_FIELDS,
1342    retention: Some("~7 days rolling window"),
1343    triage_priority: TriagePriority::Critical,
1344    related_artifacts: &["dam_user", "shimcache", "prefetch_dir"],
1345    sources: &[
1346        "https://attack.mitre.org/techniques/T1059/",
1347        "https://attack.mitre.org/techniques/T1204/",
1348        "https://www.sans.org/blog/background-activity-moderator-bam-forensics/",
1349        "https://www.13cubed.com/downloads/windows10_forensics_cheat_sheet.pdf",
1350        "https://forensafe.com/blogs/bam.html",
1351        "https://github.com/Psmths/windows-forensic-artifacts/blob/main/execution/bam-dam.md",
1352    ],
1353};
1354
1355static DAM_FIELDS: &[FieldSchema] = &[FieldSchema {
1356    name: "last_exec",
1357    value_type: ValueType::Timestamp,
1358    description: "FILETIME of last desktop application execution",
1359    is_uid_component: false,
1360}];
1361
1362/// Desktop Activity Moderator — per-user desktop application execution times.
1363pub static DAM_USER: ArtifactDescriptor = ArtifactDescriptor {
1364    id: "dam_user",
1365    name: "DAM (Desktop Activity Moderator)",
1366    artifact_type: ArtifactType::RegistryValue,
1367    hive: Some(HiveTarget::HklmSystem),
1368    key_path: r"CurrentControlSet\Services\dam\State\UserSettings",
1369    value_name: None,
1370    file_path: None,
1371    scope: DataScope::Mixed,
1372    os_scope: OsScope::Win10Plus,
1373    decoder: Decoder::FiletimeAt { offset: 0 },
1374    meaning: "Last execution time of desktop applications per-user SID",
1375    mitre_techniques: &["T1059", "T1204"],
1376    fields: DAM_FIELDS,
1377    retention: Some("~7 days rolling window"),
1378    triage_priority: TriagePriority::Critical,
1379    related_artifacts: &["bam_user", "shimcache"],
1380    sources: &[
1381        "https://attack.mitre.org/techniques/T1059/",
1382        "https://attack.mitre.org/techniques/T1204/",
1383        "https://www.sans.org/blog/background-activity-moderator-bam-forensics/",
1384        "https://forensafe.com/blogs/bam.html",
1385        "https://github.com/Psmths/windows-forensic-artifacts/blob/main/execution/bam-dam.md",
1386    ],
1387};
1388
1389// ── SAM ───────────────────────────────────────────────────────────────────────
1390
1391static SAM_FIELDS: &[FieldSchema] = &[FieldSchema {
1392    name: "username",
1393    value_type: ValueType::Text,
1394    description: "Local account username (sub-key name)",
1395    is_uid_component: true,
1396}];
1397
1398/// SAM local user account enumeration.
1399///
1400/// Each sub-key under `Names` is a local account username. The adjacent
1401/// `Users\<RID>` keys contain F/V binary records with password hash metadata.
1402pub static SAM_USERS: ArtifactDescriptor = ArtifactDescriptor {
1403    id: "sam_users",
1404    name: "SAM User Accounts",
1405    artifact_type: ArtifactType::RegistryKey,
1406    hive: Some(HiveTarget::HklmSam),
1407    key_path: r"SAM\Domains\Account\Users\Names",
1408    value_name: None,
1409    file_path: None,
1410    scope: DataScope::System,
1411    os_scope: OsScope::All,
1412    decoder: Decoder::Identity,
1413    meaning: "Local Windows accounts; F/V records contain login counts and NTLM hash metadata",
1414    mitre_techniques: &["T1003.002", "T1087.001"],
1415    fields: SAM_FIELDS,
1416    retention: None,
1417    triage_priority: TriagePriority::Critical,
1418    related_artifacts: &["lsa_secrets", "dcc2_cache"],
1419    sources: &[
1420        "https://attack.mitre.org/techniques/T1003/002/",
1421        "https://attack.mitre.org/techniques/T1087/001/",
1422        "https://www.sans.org/blog/windows-credential-storage-for-penetration-testers/",
1423        "https://windowsir.blogspot.com/2010/11/recovering-passwords.html",
1424        "http://windowsir.blogspot.com/2013/07/howto-determine-users-on-system.html",
1425    ],
1426};
1427
1428// ── LSA Secrets / DCC2 ───────────────────────────────────────────────────────
1429
1430static LSA_FIELDS: &[FieldSchema] = &[FieldSchema {
1431    name: "secret_name",
1432    value_type: ValueType::Text,
1433    description: "LSA secret key name (e.g. _SC_*, DPAPI_SYSTEM, DefaultPassword)",
1434    is_uid_component: true,
1435}];
1436
1437/// LSA Secrets — encrypted service credentials and DPAPI material.
1438pub static LSA_SECRETS: ArtifactDescriptor = ArtifactDescriptor {
1439    id: "lsa_secrets",
1440    name: "LSA Secrets",
1441    artifact_type: ArtifactType::RegistryKey,
1442    hive: Some(HiveTarget::HklmSecurity),
1443    key_path: r"Policy\Secrets",
1444    value_name: None,
1445    file_path: None,
1446    scope: DataScope::System,
1447    os_scope: OsScope::All,
1448    decoder: Decoder::Identity,
1449    meaning: "Encrypted service credentials, auto-logon passwords, and DPAPI master key",
1450    mitre_techniques: &["T1003.004", "T1552.002"],
1451    fields: LSA_FIELDS,
1452    retention: None,
1453    triage_priority: TriagePriority::Critical,
1454    related_artifacts: &["sam_users", "dpapi_system_masterkey", "dcc2_cache"],
1455    sources: &[
1456        "https://attack.mitre.org/techniques/T1003/004/",
1457        "https://attack.mitre.org/techniques/T1552/002/",
1458        "https://www.sans.org/blog/lsa-secrets/",
1459    ],
1460};
1461
1462static DCC2_FIELDS: &[FieldSchema] = &[FieldSchema {
1463    name: "slot_name",
1464    value_type: ValueType::Text,
1465    description: "Cache slot name (NL$1 through NL$25)",
1466    is_uid_component: true,
1467}];
1468
1469/// Domain Cached Credentials 2 (MS-Cache v2 / DCC2).
1470///
1471/// PBKDF2-SHA1 hashes of the last N domain logons, enabling offline logon
1472/// when no DC is reachable. Crackable offline.
1473pub static DCC2_CACHE: ArtifactDescriptor = ArtifactDescriptor {
1474    id: "dcc2_cache",
1475    name: "Domain Cached Credentials 2 (DCC2)",
1476    artifact_type: ArtifactType::RegistryKey,
1477    hive: Some(HiveTarget::HklmSecurity),
1478    key_path: r"Cache",
1479    value_name: None,
1480    file_path: None,
1481    scope: DataScope::System,
1482    os_scope: OsScope::All,
1483    decoder: Decoder::Identity,
1484    meaning: "MS-Cache v2 (PBKDF2-SHA1) hashes enabling offline domain logon",
1485    mitre_techniques: &["T1003.005"],
1486    fields: DCC2_FIELDS,
1487    retention: None,
1488    triage_priority: TriagePriority::Critical,
1489    related_artifacts: &[],
1490    sources: &[
1491        "https://attack.mitre.org/techniques/T1003/005/",
1492        "https://www.sans.org/blog/windows-credential-storage-for-penetration-testers/",
1493    ],
1494};
1495
1496// ── TypedURLsTime ─────────────────────────────────────────────────────────────
1497
1498static TYPED_URLS_TIME_FIELDS: &[FieldSchema] = &[FieldSchema {
1499    name: "timestamp",
1500    value_type: ValueType::Timestamp,
1501    description: "FILETIME when the URL slot was typed",
1502    is_uid_component: false,
1503}];
1504
1505/// IE/Edge TypedURLsTime — FILETIME timestamps parallel to TypedURLs.
1506pub static TYPED_URLS_TIME: ArtifactDescriptor = ArtifactDescriptor {
1507    id: "typed_urls_time",
1508    name: "TypedURLsTime (IE/Edge)",
1509    artifact_type: ArtifactType::RegistryKey,
1510    hive: Some(HiveTarget::NtUser),
1511    key_path: r"Software\Microsoft\Internet Explorer\TypedURLsTime",
1512    value_name: None,
1513    file_path: None,
1514    scope: DataScope::User,
1515    os_scope: OsScope::All,
1516    decoder: Decoder::FiletimeAt { offset: 0 },
1517    meaning: "Timestamps of URLs typed into IE/Edge address bar (paired with TypedURLs)",
1518    mitre_techniques: &["T1071.001"],
1519    fields: TYPED_URLS_TIME_FIELDS,
1520    retention: None,
1521    triage_priority: TriagePriority::Medium,
1522    related_artifacts: &[],
1523    sources: &[
1524        "https://attack.mitre.org/techniques/T1071/001/",
1525        "https://www.sans.org/blog/digital-forensics-windows-registry-forensics-part-6-internet-explorer-user-typed-urls/",
1526    ],
1527};
1528
1529// ── MRU RecentDocs ────────────────────────────────────────────────────────────
1530
1531static MRU_RECENT_DOCS_FIELDS: &[FieldSchema] = &[FieldSchema {
1532    name: "indices",
1533    value_type: ValueType::List,
1534    description: "MRUListEx order indices of recently accessed documents",
1535    is_uid_component: false,
1536}];
1537
1538/// Explorer RecentDocs MRU — most-recently-used document list.
1539pub static MRU_RECENT_DOCS: ArtifactDescriptor = ArtifactDescriptor {
1540    id: "mru_recent_docs",
1541    name: "MRU RecentDocs",
1542    artifact_type: ArtifactType::RegistryKey,
1543    hive: Some(HiveTarget::NtUser),
1544    key_path: r"Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs",
1545    value_name: None,
1546    file_path: None,
1547    scope: DataScope::User,
1548    os_scope: OsScope::All,
1549    decoder: Decoder::MruListEx,
1550    meaning: "Most-recently-used documents list (MRUListEx order of shell32 items)",
1551    mitre_techniques: &["T1005", "T1083"],
1552    fields: MRU_RECENT_DOCS_FIELDS,
1553    retention: None,
1554    triage_priority: TriagePriority::Medium,
1555    related_artifacts: &[],
1556    sources: &[
1557        "https://attack.mitre.org/techniques/T1005/",
1558        "https://attack.mitre.org/techniques/T1083/",
1559        "https://windowsir.blogspot.com/2006/11/recent-docs-mru.html",
1560        "https://www.sans.org/blog/windows-mru-registry-keys/",
1561        "https://www.sans.org/blog/opensavemru-and-lastvisitedmru/",
1562        "https://forensics.wiki/opensavemru/",
1563    ],
1564};
1565
1566// ── USB device enumeration ────────────────────────────────────────────────────
1567
1568static USB_FIELDS: &[FieldSchema] = &[FieldSchema {
1569    name: "device_id",
1570    value_type: ValueType::Text,
1571    description: "USB device instance ID (VID&PID sub-key name)",
1572    is_uid_component: true,
1573}];
1574
1575/// USBSTOR — USB storage device connection history.
1576///
1577/// Each sub-key records a device that was ever connected. Survives device removal.
1578pub static USB_ENUM: ArtifactDescriptor = ArtifactDescriptor {
1579    id: "usb_enum",
1580    name: "USB Device Enumeration (USBSTOR)",
1581    artifact_type: ArtifactType::RegistryKey,
1582    hive: Some(HiveTarget::HklmSystem),
1583    key_path: r"CurrentControlSet\Enum\USBSTOR",
1584    value_name: None,
1585    file_path: None,
1586    scope: DataScope::System,
1587    os_scope: OsScope::All,
1588    decoder: Decoder::Identity,
1589    meaning: "USB storage device connection history; persists after device removal",
1590    mitre_techniques: &["T1200", "T1052.001"],
1591    fields: USB_FIELDS,
1592    retention: None,
1593    triage_priority: TriagePriority::Medium,
1594    related_artifacts: &[],
1595    sources: &[
1596        "https://attack.mitre.org/techniques/T1200/",
1597        "https://attack.mitre.org/techniques/T1052/001/",
1598        "https://www.sans.org/blog/computer-forensic-artifacts-windows-7-usb-device-tracking/",
1599        "https://windowsir.blogspot.com/2013/07/usb-device-tracking-in-windows-7.html",
1600        "https://www.magnetforensics.com/blog/artifact-profile-usb-devices/",
1601    ],
1602};
1603
1604// ── MUICache ──────────────────────────────────────────────────────────────────
1605
1606static MUICACHE_FIELDS: &[FieldSchema] = &[FieldSchema {
1607    name: "display_name",
1608    value_type: ValueType::Text,
1609    description: "Localized display name of the executed application",
1610    is_uid_component: false,
1611}];
1612
1613/// MUICache — cached display names of executed applications.
1614///
1615/// Value name is the full executable path; data is the localized display name
1616/// (UTF-16 LE). Program execution evidence that survives log clearing.
1617pub static MUICACHE: ArtifactDescriptor = ArtifactDescriptor {
1618    id: "muicache",
1619    name: "MUICache",
1620    artifact_type: ArtifactType::RegistryKey,
1621    hive: Some(HiveTarget::UsrClass),
1622    key_path: r"Local Settings\MuiCache",
1623    value_name: None,
1624    file_path: None,
1625    scope: DataScope::User,
1626    os_scope: OsScope::All,
1627    decoder: Decoder::Utf16Le,
1628    meaning: "Cached display names keyed by executable path; program execution evidence",
1629    mitre_techniques: &["T1059", "T1204.002"],
1630    fields: MUICACHE_FIELDS,
1631    retention: Some("persists until registry cleanup"),
1632    triage_priority: TriagePriority::Medium,
1633    related_artifacts: &[],
1634    sources: &[
1635        "https://attack.mitre.org/techniques/T1059/",
1636        "https://attack.mitre.org/techniques/T1204/002/",
1637        "https://windowsir.blogspot.com/2012/08/no-more-mr-nice-guy.html",
1638        "https://www.sans.org/blog/digital-forensics-windows-muicache/",
1639        "http://windowsir.blogspot.com/2005/12/mystery-of-muicachesolved.html",
1640        "https://www.magnetforensics.com/blog/forensic-analysis-of-muicache-files-in-windows/",
1641        "https://forensafe.com/blogs/muicache.html",
1642    ],
1643};
1644
1645// ── AppInit_DLLs ──────────────────────────────────────────────────────────────
1646
1647static APPINIT_FIELDS: &[FieldSchema] = &[FieldSchema {
1648    name: "dll_list",
1649    value_type: ValueType::Text,
1650    description: "Comma/space-separated DLL paths injected into user32.dll consumers",
1651    is_uid_component: false,
1652}];
1653
1654/// AppInit_DLLs — DLL injection into every user-mode process (T1546.010).
1655///
1656/// Disabled by Secure Boot; still active on systems without it.
1657pub static APPINIT_DLLS: ArtifactDescriptor = ArtifactDescriptor {
1658    id: "appinit_dlls",
1659    name: "AppInit_DLLs",
1660    artifact_type: ArtifactType::RegistryValue,
1661    hive: Some(HiveTarget::HklmSoftware),
1662    key_path: r"Microsoft\Windows NT\CurrentVersion\Windows",
1663    value_name: Some("AppInit_DLLs"),
1664    file_path: None,
1665    scope: DataScope::System,
1666    os_scope: OsScope::All,
1667    decoder: Decoder::Identity,
1668    meaning: "DLLs injected into every process that loads user32.dll",
1669    mitre_techniques: &["T1546.010"],
1670    fields: APPINIT_FIELDS,
1671    retention: None,
1672    triage_priority: TriagePriority::Medium,
1673    related_artifacts: &[],
1674    sources: &[
1675        "https://attack.mitre.org/techniques/T1546/010/",
1676        "https://learn.microsoft.com/en-us/windows/win32/dlls/registry-keys-for-appinit-dlls",
1677    ],
1678};
1679
1680// ── Winlogon Userinit ─────────────────────────────────────────────────────────
1681
1682static WINLOGON_FIELDS: &[FieldSchema] = &[FieldSchema {
1683    name: "userinit",
1684    value_type: ValueType::Text,
1685    description: "Comma-separated executables launched by Winlogon at logon",
1686    is_uid_component: false,
1687}];
1688
1689/// Winlogon Userinit — process launched after user authentication (T1547.004).
1690///
1691/// Default value: `C:\Windows\System32\userinit.exe,`
1692/// Attackers append `,malware.exe` or replace entirely.
1693pub static WINLOGON_USERINIT: ArtifactDescriptor = ArtifactDescriptor {
1694    id: "winlogon_userinit",
1695    name: "Winlogon Userinit",
1696    artifact_type: ArtifactType::RegistryValue,
1697    hive: Some(HiveTarget::HklmSoftware),
1698    key_path: r"Microsoft\Windows NT\CurrentVersion\Winlogon",
1699    value_name: Some("Userinit"),
1700    file_path: None,
1701    scope: DataScope::System,
1702    os_scope: OsScope::All,
1703    decoder: Decoder::Identity,
1704    meaning: "Process(es) launched by Winlogon at logon; default is userinit.exe,",
1705    mitre_techniques: &["T1547.004"],
1706    fields: WINLOGON_FIELDS,
1707    retention: None,
1708    triage_priority: TriagePriority::High,
1709    related_artifacts: &[],
1710    sources: &[
1711        "https://attack.mitre.org/techniques/T1547/004/",
1712        "https://learn.microsoft.com/en-us/windows/win32/secauthn/winlogon-and-gina",
1713    ],
1714};
1715
1716// ── Screensaver persistence ───────────────────────────────────────────────────
1717
1718static SCREENSAVER_FIELDS: &[FieldSchema] = &[FieldSchema {
1719    name: "path",
1720    value_type: ValueType::Text,
1721    description: "Path to the screensaver executable (.scr)",
1722    is_uid_component: false,
1723}];
1724
1725/// Screensaver executable persistence (T1546.002).
1726///
1727/// `.scr` files are PE executables; an attacker can replace the screensaver
1728/// path with a malicious binary that executes when the screen locks.
1729pub static SCREENSAVER_EXE: ArtifactDescriptor = ArtifactDescriptor {
1730    id: "screensaver_exe",
1731    name: "Screensaver Executable",
1732    artifact_type: ArtifactType::RegistryValue,
1733    hive: Some(HiveTarget::NtUser),
1734    key_path: r"Control Panel\Desktop",
1735    value_name: Some("SCRNSAVE.EXE"),
1736    file_path: None,
1737    scope: DataScope::User,
1738    os_scope: OsScope::All,
1739    decoder: Decoder::Identity,
1740    meaning: "Screensaver path; malicious .scr enables persistence on screen lock",
1741    mitre_techniques: &["T1546.002"],
1742    fields: SCREENSAVER_FIELDS,
1743    retention: None,
1744    triage_priority: TriagePriority::Medium,
1745    related_artifacts: &[],
1746    sources: &[
1747        "https://attack.mitre.org/techniques/T1546/002/",
1748        "https://www.sans.org/blog/screensaver-registry-key-for-persistence/",
1749    ],
1750};
1751
1752// ═══════════════════════════════════════════════════════════════════════════
1753// Batch C — Windows persistence / execution / credential artifacts
1754// ═══════════════════════════════════════════════════════════════════════════
1755
1756// ── Shared field schemas (reused across multiple descriptors) ─────────────
1757
1758/// Generic "command or path" field — suitable for persistence value descriptors.
1759static PERSIST_CMD_FIELDS: &[FieldSchema] = &[FieldSchema {
1760    name: "command",
1761    value_type: ValueType::Text,
1762    description: "Command, DLL path, or executable registered for execution",
1763    is_uid_component: false,
1764}];
1765
1766/// Generic "DLL path" field.
1767static DLL_FIELDS: &[FieldSchema] = &[FieldSchema {
1768    name: "dll_path",
1769    value_type: ValueType::Text,
1770    description: "Path to the DLL registered for injection or loading",
1771    is_uid_component: false,
1772}];
1773
1774/// Generic "directory listing" field for filesystem directory artifacts.
1775static DIR_ENTRY_FIELDS: &[FieldSchema] = &[FieldSchema {
1776    name: "entry_name",
1777    value_type: ValueType::Text,
1778    description: "Name of the file or shortcut present in this directory",
1779    is_uid_component: true,
1780}];
1781
1782/// Generic "file path" for single-file artifacts.
1783static FILE_PATH_FIELDS: &[FieldSchema] = &[FieldSchema {
1784    name: "path",
1785    value_type: ValueType::Text,
1786    description: "Full path to the artifact file",
1787    is_uid_component: true,
1788}];
1789
1790// ── Windows persistence: advanced registry ────────────────────────────────
1791
1792/// Winlogon Shell value — replaceable Windows Explorer shell (T1547.004).
1793///
1794/// Default: `explorer.exe`. Attackers replace or append to gain persistence
1795/// that launches their binary as the user's shell at logon.
1796pub static WINLOGON_SHELL: ArtifactDescriptor = ArtifactDescriptor {
1797    id: "winlogon_shell",
1798    name: "Winlogon Shell",
1799    artifact_type: ArtifactType::RegistryValue,
1800    hive: Some(HiveTarget::HklmSoftware),
1801    key_path: r"Microsoft\Windows NT\CurrentVersion\Winlogon",
1802    value_name: Some("Shell"),
1803    file_path: None,
1804    scope: DataScope::System,
1805    os_scope: OsScope::All,
1806    decoder: Decoder::Identity,
1807    meaning: "Windows shell process(es) launched by Winlogon; default is explorer.exe",
1808    mitre_techniques: &["T1547.004"],
1809    fields: PERSIST_CMD_FIELDS,
1810    retention: None,
1811    triage_priority: TriagePriority::High,
1812    related_artifacts: &[],
1813    sources: &[
1814        "https://attack.mitre.org/techniques/T1547/004/",
1815        "https://learn.microsoft.com/en-us/windows/win32/secauthn/winlogon-and-gina",
1816    ],
1817};
1818
1819/// Windows Services — ImagePath value indicates binary launched as a service.
1820///
1821/// Each sub-key under `Services\*` has `ImagePath` (the executable) and
1822/// `Start` (0=Boot, 1=System, 2=Auto, 3=Manual, 4=Disabled).
1823pub static SERVICES_IMAGEPATH: ArtifactDescriptor = ArtifactDescriptor {
1824    id: "services_imagepath",
1825    name: "Services ImagePath",
1826    artifact_type: ArtifactType::RegistryKey,
1827    hive: Some(HiveTarget::HklmSystem),
1828    key_path: r"CurrentControlSet\Services",
1829    value_name: Some("ImagePath"),
1830    file_path: None,
1831    scope: DataScope::System,
1832    os_scope: OsScope::All,
1833    decoder: Decoder::Identity,
1834    meaning: "Executable path of a Windows service; auto-started services persist across reboots",
1835    mitre_techniques: &["T1543.003"],
1836    fields: PERSIST_CMD_FIELDS,
1837    retention: None,
1838    triage_priority: TriagePriority::High,
1839    related_artifacts: &[],
1840    sources: &[
1841        "https://attack.mitre.org/techniques/T1543/003/",
1842        "https://learn.microsoft.com/en-us/windows/win32/services/service-control-manager",
1843        "https://redcanary.com/threat-detection-report/techniques/t1543/",
1844    ],
1845};
1846
1847static ACTIVE_SETUP_FIELDS: &[FieldSchema] = &[FieldSchema {
1848    name: "stub_path",
1849    value_type: ValueType::Text,
1850    description: "StubPath command executed once per user at logon for new installs",
1851    is_uid_component: false,
1852}];
1853
1854/// Active Setup HKLM — system-side component registration (T1547.014).
1855///
1856/// Each CLSID sub-key has `StubPath`. Windows compares HKLM and HKCU versions;
1857/// if HKCU is missing or older, StubPath is executed as the user at logon.
1858pub static ACTIVE_SETUP_HKLM: ArtifactDescriptor = ArtifactDescriptor {
1859    id: "active_setup_hklm",
1860    name: "Active Setup (HKLM)",
1861    artifact_type: ArtifactType::RegistryKey,
1862    hive: Some(HiveTarget::HklmSoftware),
1863    key_path: r"Microsoft\Active Setup\Installed Components",
1864    value_name: Some("StubPath"),
1865    file_path: None,
1866    scope: DataScope::System,
1867    os_scope: OsScope::All,
1868    decoder: Decoder::Identity,
1869    meaning: "Per-user setup command executed by HKLM Active Setup; malicious StubPath = user-context persistence",
1870    mitre_techniques: &["T1547.014"],
1871    fields: ACTIVE_SETUP_FIELDS,
1872    retention: None,
1873    triage_priority: TriagePriority::High,
1874    related_artifacts: &[],
1875    sources: &[
1876        "https://attack.mitre.org/techniques/T1547/014/",
1877        "https://www.sans.org/blog/active-setup-registry-persistence/",
1878    ],
1879};
1880
1881/// Active Setup HKCU — user-side Active Setup version tracking.
1882///
1883/// Attacker may delete HKCU entry to trigger HKLM StubPath re-execution.
1884pub static ACTIVE_SETUP_HKCU: ArtifactDescriptor = ArtifactDescriptor {
1885    id: "active_setup_hkcu",
1886    name: "Active Setup (HKCU)",
1887    artifact_type: ArtifactType::RegistryKey,
1888    hive: Some(HiveTarget::NtUser),
1889    key_path: r"Software\Microsoft\Active Setup\Installed Components",
1890    value_name: Some("Version"),
1891    file_path: None,
1892    scope: DataScope::User,
1893    os_scope: OsScope::All,
1894    decoder: Decoder::Identity,
1895    meaning: "User-side Active Setup version; mismatch with HKLM triggers StubPath re-execution",
1896    mitre_techniques: &["T1547.014"],
1897    fields: RUN_KEY_FIELDS,
1898    retention: None,
1899    triage_priority: TriagePriority::High,
1900    related_artifacts: &[],
1901    sources: &["https://attack.mitre.org/techniques/T1547/014/"],
1902};
1903
1904/// COM Hijacking via HKCU CLSID registration (T1546.015).
1905///
1906/// When an application resolves a CLSID, Windows checks HKCU\Classes before
1907/// HKLM. Registering a malicious InprocServer32 in HKCU wins the race
1908/// without requiring admin privileges.
1909pub static COM_HIJACK_CLSID_HKCU: ArtifactDescriptor = ArtifactDescriptor {
1910    id: "com_hijack_clsid_hkcu",
1911    name: "COM Hijack CLSID (HKCU)",
1912    artifact_type: ArtifactType::RegistryKey,
1913    hive: Some(HiveTarget::UsrClass),
1914    key_path: r"CLSID",
1915    value_name: Some("InprocServer32"),
1916    file_path: None,
1917    scope: DataScope::User,
1918    os_scope: OsScope::Win7Plus,
1919    decoder: Decoder::Identity,
1920    meaning: "User-space CLSID registration overriding system COM server; no admin needed",
1921    mitre_techniques: &["T1546.015"],
1922    fields: DLL_FIELDS,
1923    retention: None,
1924    triage_priority: TriagePriority::Medium,
1925    related_artifacts: &[],
1926    sources: &[
1927        "https://attack.mitre.org/techniques/T1546/015/",
1928        "https://redcanary.com/threat-detection-report/techniques/t1546/",
1929    ],
1930};
1931
1932/// AppCert DLLs — DLL injected into every process calling CreateProcess (T1546.009).
1933///
1934/// Unlike AppInit_DLLs, these are loaded into more process types. Rarely
1935/// legitimate; any non-empty value is highly suspicious.
1936pub static APPCERT_DLLS: ArtifactDescriptor = ArtifactDescriptor {
1937    id: "appcert_dlls",
1938    name: "AppCertDlls",
1939    artifact_type: ArtifactType::RegistryKey,
1940    hive: Some(HiveTarget::HklmSystem),
1941    key_path: r"CurrentControlSet\Control\Session Manager\AppCertDlls",
1942    value_name: None,
1943    file_path: None,
1944    scope: DataScope::System,
1945    os_scope: OsScope::All,
1946    decoder: Decoder::Identity,
1947    meaning: "DLLs injected into every process that calls CreateProcess-family APIs",
1948    mitre_techniques: &["T1546.009"],
1949    fields: DLL_FIELDS,
1950    retention: None,
1951    triage_priority: TriagePriority::Medium,
1952    related_artifacts: &[],
1953    sources: &[
1954        "https://attack.mitre.org/techniques/T1546/009/",
1955        "https://learn.microsoft.com/en-us/windows/win32/devnotes/appcertdlls",
1956    ],
1957};
1958
1959static BOOT_EXECUTE_FIELDS: &[FieldSchema] = &[FieldSchema {
1960    name: "commands",
1961    value_type: ValueType::List,
1962    description: "Commands executed by Session Manager before Win32 subsystem starts",
1963    is_uid_component: false,
1964}];
1965
1966/// Boot Execute — commands run by smss.exe before Win32 subsystem (T1547.001).
1967///
1968/// Default: `autocheck autochk *`. Additional entries run native NT executables
1969/// at boot, before antivirus and most defences are loaded.
1970pub static BOOT_EXECUTE: ArtifactDescriptor = ArtifactDescriptor {
1971    id: "boot_execute",
1972    name: "Boot Execute",
1973    artifact_type: ArtifactType::RegistryValue,
1974    hive: Some(HiveTarget::HklmSystem),
1975    key_path: r"CurrentControlSet\Control\Session Manager",
1976    value_name: Some("BootExecute"),
1977    file_path: None,
1978    scope: DataScope::System,
1979    os_scope: OsScope::All,
1980    decoder: Decoder::MultiSz,
1981    meaning: "Native executables run by smss.exe at boot; executes before most security software",
1982    mitre_techniques: &["T1547.001"],
1983    fields: BOOT_EXECUTE_FIELDS,
1984    retention: None,
1985    triage_priority: TriagePriority::High,
1986    related_artifacts: &[],
1987    sources: &[
1988        "https://attack.mitre.org/techniques/T1547/001/",
1989        "https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/boot-time-global-flag-settings",
1990    ],
1991};
1992
1993/// LSA Security Support Providers — SSPs injected into LSASS (T1547.005).
1994///
1995/// Legitimate SSPs: kerberos, msv1_0, schannel, wdigest. Extra entries
1996/// indicate credential-harvesting or persistence.
1997pub static LSA_SECURITY_PKGS: ArtifactDescriptor = ArtifactDescriptor {
1998    id: "lsa_security_pkgs",
1999    name: "LSA Security Packages",
2000    artifact_type: ArtifactType::RegistryValue,
2001    hive: Some(HiveTarget::HklmSystem),
2002    key_path: r"CurrentControlSet\Control\Lsa",
2003    value_name: Some("Security Packages"),
2004    file_path: None,
2005    scope: DataScope::System,
2006    os_scope: OsScope::All,
2007    decoder: Decoder::MultiSz,
2008    meaning: "Security Support Providers loaded into LSASS; malicious SSP = persistent LSASS credential access",
2009    mitre_techniques: &["T1547.005"],
2010    fields: BOOT_EXECUTE_FIELDS,
2011    retention: None,
2012    triage_priority: TriagePriority::High,
2013    related_artifacts: &[],
2014    sources: &[
2015        "https://attack.mitre.org/techniques/T1547/005/",
2016        "https://learn.microsoft.com/en-us/windows/win32/secauthn/lsa-authentication",
2017    ],
2018};
2019
2020/// LSA Authentication Packages — loaded by LSASS for auth (T1547.002).
2021pub static LSA_AUTH_PKGS: ArtifactDescriptor = ArtifactDescriptor {
2022    id: "lsa_auth_pkgs",
2023    name: "LSA Authentication Packages",
2024    artifact_type: ArtifactType::RegistryValue,
2025    hive: Some(HiveTarget::HklmSystem),
2026    key_path: r"CurrentControlSet\Control\Lsa",
2027    value_name: Some("Authentication Packages"),
2028    file_path: None,
2029    scope: DataScope::System,
2030    os_scope: OsScope::All,
2031    decoder: Decoder::MultiSz,
2032    meaning: "Authentication packages loaded by LSASS; extra DLLs intercept logon credentials",
2033    mitre_techniques: &["T1547.002"],
2034    fields: BOOT_EXECUTE_FIELDS,
2035    retention: None,
2036    triage_priority: TriagePriority::High,
2037    related_artifacts: &[],
2038    sources: &[
2039        "https://attack.mitre.org/techniques/T1547/002/",
2040        "https://attack.mitre.org/techniques/T1547/005/",
2041        "https://learn.microsoft.com/en-us/windows/win32/secauthn/lsa-authentication",
2042    ],
2043};
2044
2045/// Print Monitors — DLL loaded by the spooler service (T1547.010).
2046///
2047/// Requires admin. DLL runs as SYSTEM inside spoolsv.exe across reboots.
2048pub static PRINT_MONITORS: ArtifactDescriptor = ArtifactDescriptor {
2049    id: "print_monitors",
2050    name: "Print Monitors",
2051    artifact_type: ArtifactType::RegistryKey,
2052    hive: Some(HiveTarget::HklmSystem),
2053    key_path: r"CurrentControlSet\Control\Print\Monitors",
2054    value_name: Some("Driver"),
2055    file_path: None,
2056    scope: DataScope::System,
2057    os_scope: OsScope::All,
2058    decoder: Decoder::Identity,
2059    meaning: "DLL loaded into spoolsv.exe (SYSTEM); extra monitors = SYSTEM persistence",
2060    mitre_techniques: &["T1547.010"],
2061    fields: DLL_FIELDS,
2062    retention: None,
2063    triage_priority: TriagePriority::Medium,
2064    related_artifacts: &[],
2065    sources: &[
2066        "https://attack.mitre.org/techniques/T1547/010/",
2067        "https://learn.microsoft.com/en-us/windows-hardware/drivers/print/print-monitor",
2068    ],
2069};
2070
2071/// Time Provider DLLs — loaded into svchost as part of W32Time (T1547.003).
2072pub static TIME_PROVIDERS: ArtifactDescriptor = ArtifactDescriptor {
2073    id: "time_providers",
2074    name: "W32Time Time Provider DLLs",
2075    artifact_type: ArtifactType::RegistryKey,
2076    hive: Some(HiveTarget::HklmSystem),
2077    key_path: r"CurrentControlSet\Services\W32Time\TimeProviders",
2078    value_name: Some("DllName"),
2079    file_path: None,
2080    scope: DataScope::System,
2081    os_scope: OsScope::All,
2082    decoder: Decoder::Identity,
2083    meaning: "DLLs loaded by the Windows Time service; malicious entry = SYSTEM persistence",
2084    mitre_techniques: &["T1547.003"],
2085    fields: DLL_FIELDS,
2086    retention: None,
2087    triage_priority: TriagePriority::Medium,
2088    related_artifacts: &[],
2089    sources: &[
2090        "https://attack.mitre.org/techniques/T1547/003/",
2091        "https://learn.microsoft.com/en-us/windows/win32/sysinfo/time-provider",
2092    ],
2093};
2094
2095/// Netsh Helper DLLs — COM-like DLLs loaded by netsh.exe (T1546.007).
2096pub static NETSH_HELPER_DLLS: ArtifactDescriptor = ArtifactDescriptor {
2097    id: "netsh_helper_dlls",
2098    name: "Netsh Helper DLLs",
2099    artifact_type: ArtifactType::RegistryKey,
2100    hive: Some(HiveTarget::HklmSoftware),
2101    key_path: r"Microsoft\NetSh",
2102    value_name: None,
2103    file_path: None,
2104    scope: DataScope::System,
2105    os_scope: OsScope::All,
2106    decoder: Decoder::Identity,
2107    meaning: "DLLs loaded whenever netsh.exe is invoked; attacker DLL runs in user's netsh context",
2108    mitre_techniques: &["T1546.007"],
2109    fields: DLL_FIELDS,
2110    retention: None,
2111    triage_priority: TriagePriority::Medium,
2112    related_artifacts: &[],
2113    sources: &[
2114        "https://attack.mitre.org/techniques/T1546/007/",
2115        "https://learn.microsoft.com/en-us/windows/win32/netmgmt/network-management-functions",
2116    ],
2117};
2118
2119static BHO_FIELDS: &[FieldSchema] = &[FieldSchema {
2120    name: "clsid",
2121    value_type: ValueType::Text,
2122    description: "CLSID of the Browser Helper Object (sub-key name)",
2123    is_uid_component: true,
2124}];
2125
2126/// Browser Helper Objects — COM components loaded by IE (T1176).
2127///
2128/// BHOs run inside iexplore.exe and can intercept HTTP traffic, steal
2129/// credentials, and maintain persistence via the COM registry.
2130pub static BROWSER_HELPER_OBJECTS: ArtifactDescriptor = ArtifactDescriptor {
2131    id: "browser_helper_objects",
2132    name: "Internet Explorer Browser Helper Objects",
2133    artifact_type: ArtifactType::RegistryKey,
2134    hive: Some(HiveTarget::HklmSoftware),
2135    key_path: r"Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects",
2136    value_name: None,
2137    file_path: None,
2138    scope: DataScope::System,
2139    os_scope: OsScope::All,
2140    decoder: Decoder::Identity,
2141    meaning: "COM components auto-loaded into IE; can intercept browsing and steal credentials",
2142    mitre_techniques: &["T1176"],
2143    fields: BHO_FIELDS,
2144    retention: None,
2145    triage_priority: TriagePriority::Medium,
2146    related_artifacts: &[],
2147    sources: &[
2148        "https://attack.mitre.org/techniques/T1176/",
2149        "https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa753582(v=vs.85)",
2150    ],
2151};
2152
2153// ── Windows persistence: filesystem ──────────────────────────────────────
2154
2155/// User Startup Folder — files/LNKs here execute at user logon (T1547.001).
2156pub static STARTUP_FOLDER_USER: ArtifactDescriptor = ArtifactDescriptor {
2157    id: "startup_folder_user",
2158    name: "User Startup Folder",
2159    artifact_type: ArtifactType::Directory,
2160    hive: None,
2161    key_path: "",
2162    value_name: None,
2163    file_path: Some(r"C:\Users\*\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup"),
2164    scope: DataScope::User,
2165    os_scope: OsScope::All,
2166    decoder: Decoder::Identity,
2167    meaning: "Executables and LNKs here run at user logon; no admin required",
2168    mitre_techniques: &["T1547.001"],
2169    fields: DIR_ENTRY_FIELDS,
2170    retention: None,
2171    triage_priority: TriagePriority::Medium,
2172    related_artifacts: &[],
2173    sources: &[
2174        "https://attack.mitre.org/techniques/T1547/001/",
2175        "https://learn.microsoft.com/en-us/windows/win32/shell/csidl",
2176    ],
2177};
2178
2179/// System Startup Folder — files/LNKs here execute for all users at logon.
2180pub static STARTUP_FOLDER_SYSTEM: ArtifactDescriptor = ArtifactDescriptor {
2181    id: "startup_folder_system",
2182    name: "System Startup Folder",
2183    artifact_type: ArtifactType::Directory,
2184    hive: None,
2185    key_path: "",
2186    value_name: None,
2187    file_path: Some(r"C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp"),
2188    scope: DataScope::System,
2189    os_scope: OsScope::All,
2190    decoder: Decoder::Identity,
2191    meaning: "Executables and LNKs run for every user at logon; requires admin to plant",
2192    mitre_techniques: &["T1547.001"],
2193    fields: DIR_ENTRY_FIELDS,
2194    retention: None,
2195    triage_priority: TriagePriority::Medium,
2196    related_artifacts: &[],
2197    sources: &[
2198        "https://attack.mitre.org/techniques/T1547/001/",
2199        "https://learn.microsoft.com/en-us/windows/win32/shell/csidl",
2200    ],
2201};
2202
2203/// Windows Task Scheduler task XML files (T1053.005).
2204///
2205/// Each task is stored as an XML file; key elements: `<Actions>` (what runs),
2206/// `<Triggers>` (when), `<Principal>` (which user/privileges).
2207pub static SCHEDULED_TASKS_DIR: ArtifactDescriptor = ArtifactDescriptor {
2208    id: "scheduled_tasks_dir",
2209    name: "Scheduled Tasks Directory",
2210    artifact_type: ArtifactType::Directory,
2211    hive: None,
2212    key_path: "",
2213    value_name: None,
2214    file_path: Some(r"C:\Windows\System32\Tasks"),
2215    scope: DataScope::System,
2216    os_scope: OsScope::Win7Plus,
2217    decoder: Decoder::Identity,
2218    meaning: "XML task definitions; malicious tasks can run at boot, logon, or arbitrary intervals",
2219    mitre_techniques: &["T1053.005"],
2220    fields: DIR_ENTRY_FIELDS,
2221    retention: None,
2222    triage_priority: TriagePriority::High,
2223    related_artifacts: &[],
2224    sources: &[
2225        "https://attack.mitre.org/techniques/T1053/005/",
2226        "https://learn.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-start-page",
2227        "https://redcanary.com/threat-detection-report/techniques/t1053/",
2228    ],
2229};
2230
2231/// WDigest credential caching control (T1003.001).
2232///
2233/// Setting `UseLogonCredential` = 1 re-enables cleartext credential caching
2234/// in LSASS memory on Windows 8.1+ (disabled by default since KB2871997).
2235pub static WDIGEST_CACHING: ArtifactDescriptor = ArtifactDescriptor {
2236    id: "wdigest_caching",
2237    name: "WDigest UseLogonCredential",
2238    artifact_type: ArtifactType::RegistryValue,
2239    hive: Some(HiveTarget::HklmSystem),
2240    key_path: r"CurrentControlSet\Control\SecurityProviders\WDigest",
2241    value_name: Some("UseLogonCredential"),
2242    file_path: None,
2243    scope: DataScope::System,
2244    os_scope: OsScope::All,
2245    decoder: Decoder::DwordLe,
2246    meaning:
2247        "1 = cleartext creds in LSASS; attackers set this before Mimikatz to harvest passwords",
2248    mitre_techniques: &["T1003.001"],
2249    fields: RUN_KEY_FIELDS,
2250    retention: None,
2251    triage_priority: TriagePriority::Medium,
2252    related_artifacts: &[],
2253    sources: &[
2254        "https://attack.mitre.org/techniques/T1003/001/",
2255        "https://redcanary.com/threat-detection-report/techniques/t1003/",
2256    ],
2257};
2258
2259// ── Windows execution evidence ────────────────────────────────────────────
2260
2261/// WordWheelQuery — Explorer search bar history (MRUListEx).
2262pub static WORDWHEEL_QUERY: ArtifactDescriptor = ArtifactDescriptor {
2263    id: "wordwheel_query",
2264    name: "WordWheelQuery (Explorer Search)",
2265    artifact_type: ArtifactType::RegistryKey,
2266    hive: Some(HiveTarget::NtUser),
2267    key_path: r"Software\Microsoft\Windows\CurrentVersion\Explorer\WordWheelQuery",
2268    value_name: None,
2269    file_path: None,
2270    scope: DataScope::User,
2271    os_scope: OsScope::Win7Plus,
2272    decoder: Decoder::MruListEx,
2273    meaning:
2274        "Search terms entered into Windows Explorer search bar; reveals attacker reconnaissance",
2275    mitre_techniques: &["T1083"],
2276    fields: MRU_RECENT_DOCS_FIELDS,
2277    retention: None,
2278    triage_priority: TriagePriority::Medium,
2279    related_artifacts: &[],
2280    sources: &[
2281        "https://attack.mitre.org/techniques/T1083/",
2282        "https://windowsir.blogspot.com/2012/08/wordwheelquery.html",
2283    ],
2284};
2285
2286/// OpenSaveMRU — files opened/saved via Windows common dialog (T1083).
2287///
2288/// Each file extension has a sub-key containing an MRU list of paths.
2289/// The `*` sub-key shows all extensions combined.
2290pub static OPENSAVE_MRU: ArtifactDescriptor = ArtifactDescriptor {
2291    id: "opensave_mru",
2292    name: "OpenSaveMRU (Common Dialog)",
2293    artifact_type: ArtifactType::RegistryKey,
2294    hive: Some(HiveTarget::NtUser),
2295    key_path: r"Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSaveMRU",
2296    value_name: None,
2297    file_path: None,
2298    scope: DataScope::User,
2299    os_scope: OsScope::All,
2300    decoder: Decoder::MruListEx,
2301    meaning: "Paths of files opened or saved via Win32 common dialog boxes; per-extension history",
2302    mitre_techniques: &["T1083"],
2303    fields: MRU_RECENT_DOCS_FIELDS,
2304    retention: None,
2305    triage_priority: TriagePriority::Medium,
2306    related_artifacts: &[],
2307    sources: &[
2308        "https://attack.mitre.org/techniques/T1083/",
2309        "https://windowsir.blogspot.com/2006/11/recent-docs-mru.html",
2310        "https://www.sans.org/blog/opensavemru-and-lastvisitedmru/",
2311        "https://forensics.wiki/opensavemru/",
2312    ],
2313};
2314
2315/// LastVisitedMRU — last folder visited in common dialog per-application.
2316pub static LASTVISITED_MRU: ArtifactDescriptor = ArtifactDescriptor {
2317    id: "lastvisited_mru",
2318    name: "LastVisitedMRU (Common Dialog)",
2319    artifact_type: ArtifactType::RegistryKey,
2320    hive: Some(HiveTarget::NtUser),
2321    key_path: r"Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedMRU",
2322    value_name: None,
2323    file_path: None,
2324    scope: DataScope::User,
2325    os_scope: OsScope::All,
2326    decoder: Decoder::MruListEx,
2327    meaning: "Application + last-used folder from common dialog; reveals programs accessing files",
2328    mitre_techniques: &["T1083"],
2329    fields: MRU_RECENT_DOCS_FIELDS,
2330    retention: None,
2331    triage_priority: TriagePriority::Medium,
2332    related_artifacts: &[],
2333    sources: &[
2334        "https://attack.mitre.org/techniques/T1083/",
2335        "https://windowsir.blogspot.com/2006/11/recent-docs-mru.html",
2336        "https://www.sans.org/blog/opensavemru-and-lastvisitedmru/",
2337    ],
2338};
2339
2340/// Windows Prefetch files directory — execution evidence (T1204.002).
2341///
2342/// Each `.pf` file records: executable name, run count, last 8 run timestamps,
2343/// and volume/file references. Requires Prefetch service enabled.
2344pub static PREFETCH_DIR: ArtifactDescriptor = ArtifactDescriptor {
2345    id: "prefetch_dir",
2346    name: "Prefetch Files Directory",
2347    artifact_type: ArtifactType::Directory,
2348    hive: None,
2349    key_path: "",
2350    value_name: None,
2351    file_path: Some(r"C:\Windows\Prefetch"),
2352    scope: DataScope::System,
2353    os_scope: OsScope::All,
2354    decoder: Decoder::Identity,
2355    meaning: "Binary .pf files recording 30-day program execution history with timestamps",
2356    mitre_techniques: &["T1204.002"],
2357    fields: DIR_ENTRY_FIELDS,
2358    retention: Some("128 entries; oldest evicted"),
2359    triage_priority: TriagePriority::High,
2360    related_artifacts: &["shimcache", "amcache_app_file", "bam_user"],
2361    sources: &[
2362        "https://attack.mitre.org/techniques/T1204/002/",
2363        "https://www.sans.org/blog/computer-forensic-artifacts-windows-7-prefetch-files/",
2364        "https://13cubed.com/downloads/Windows_Forensic_Analysis_Poster.pdf",
2365        "https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/application-verifier",
2366        "https://isc.sans.edu/diary/Forensic+Value+of+Prefetch/29168",
2367        "https://www.magnetforensics.com/blog/forensic-analysis-of-prefetch-files-in-windows/",
2368    ],
2369};
2370
2371static SRUM_FIELDS: &[FieldSchema] = &[
2372    FieldSchema {
2373        name: "app_name",
2374        value_type: ValueType::Text,
2375        description: "Application executable path or service name",
2376        is_uid_component: true,
2377    },
2378    FieldSchema {
2379        name: "user_sid",
2380        value_type: ValueType::Text,
2381        description: "SID of the user who ran the application",
2382        is_uid_component: false,
2383    },
2384];
2385
2386/// System Resource Usage Monitor database — rich execution timeline (Win8+).
2387///
2388/// SQLite database at `C:\Windows\System32\sru\SRUDB.dat`. Key tables:
2389/// `{D10CA2FE-...}` = Application Resource Usage (network, CPU per app),
2390/// `{5C8CF1C7-...}` = Network Data Usage. Retains ~30-60 days of history.
2391pub static SRUM_DB: ArtifactDescriptor = ArtifactDescriptor {
2392    id: "srum_db",
2393    name: "SRUM Database (SRUDB.dat)",
2394    artifact_type: ArtifactType::File,
2395    hive: None,
2396    key_path: "",
2397    value_name: None,
2398    file_path: Some(r"C:\Windows\System32\sru\SRUDB.dat"),
2399    scope: DataScope::System,
2400    os_scope: OsScope::Win8Plus,
2401    decoder: Decoder::Identity,
2402    meaning:
2403        "Per-app CPU, network, and energy usage records; execution timeline survives log clearing",
2404    mitre_techniques: &["T1204.002"],
2405    fields: SRUM_FIELDS,
2406    retention: Some("~30 days"),
2407    triage_priority: TriagePriority::Critical,
2408    related_artifacts: &[],
2409    sources: &[
2410        "https://attack.mitre.org/techniques/T1204/002/",
2411        "https://www.sans.org/white-papers/36660/",
2412        "https://www.sans.org/blog/srum-forensics/",
2413        "https://www.magnetforensics.com/blog/srum-forensic-analysis-of-windows-system-resource-utilization-monitor/",
2414        "https://github.com/MarkBaggett/srum-dump",
2415    ],
2416};
2417
2418/// Windows Timeline / Activities Cache — cross-device activity history (Win10+).
2419///
2420/// SQLite database; `Activity` table records application focus events,
2421/// file opens, and clipboard content with timestamps.
2422pub static WINDOWS_TIMELINE: ArtifactDescriptor = ArtifactDescriptor {
2423    id: "windows_timeline",
2424    name: "Windows Timeline (ActivitiesCache.db)",
2425    artifact_type: ArtifactType::File,
2426    hive: None,
2427    key_path: "",
2428    value_name: None,
2429    file_path: Some(r"C:\Users\*\AppData\Local\ConnectedDevicesPlatform\*\ActivitiesCache.db"),
2430    scope: DataScope::User,
2431    os_scope: OsScope::Win10Plus,
2432    decoder: Decoder::Identity,
2433    meaning:
2434        "Application activity timeline including focus time, file access, and clipboard events",
2435    mitre_techniques: &["T1059", "T1204.002"],
2436    fields: SRUM_FIELDS,
2437    retention: Some("~30 days"),
2438    triage_priority: TriagePriority::Medium,
2439    related_artifacts: &[],
2440    sources: &[
2441        "https://attack.mitre.org/techniques/T1059/",
2442        "https://attack.mitre.org/techniques/T1204/002/",
2443        "https://www.sans.org/blog/windows-10-timeline-forensic-artifacts/",
2444        "https://aboutdfir.com/windows-10-timeline/",
2445        "http://windowsir.blogspot.com/2019/11/activitescachedb-vs-ntuserdat.html",
2446        "https://kacos2000.github.io/WindowsTimeline/",
2447    ],
2448};
2449
2450/// PowerShell PSReadLine command history (T1059.001).
2451///
2452/// Plain-text file; contains full command history including sensitive strings,
2453/// filenames, and lateral movement commands typed interactively.
2454pub static POWERSHELL_HISTORY: ArtifactDescriptor = ArtifactDescriptor {
2455    id: "powershell_history",
2456    name: "PowerShell PSReadLine History",
2457    artifact_type: ArtifactType::File,
2458    hive: None,
2459    key_path: "",
2460    value_name: None,
2461    file_path: Some(
2462        r"C:\Users\*\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt",
2463    ),
2464    scope: DataScope::User,
2465    os_scope: OsScope::Win10Plus,
2466    decoder: Decoder::Identity,
2467    meaning: "Line-by-line PowerShell interactive command history; attackers often clear this",
2468    mitre_techniques: &["T1059.001", "T1552"],
2469    fields: FILE_PATH_FIELDS,
2470    retention: Some("4096 commands; oldest evicted when limit reached"),
2471    triage_priority: TriagePriority::High,
2472    related_artifacts: &[],
2473    sources: &[
2474        "https://attack.mitre.org/techniques/T1059/001/",
2475        "https://attack.mitre.org/techniques/T1552/",
2476        "https://www.sans.org/blog/powershell-forensics/",
2477        "https://redcanary.com/threat-detection-report/techniques/t1059.001/",
2478        "https://community.sophos.com/sophos-labs/b/blog/posts/powershell-command-history-forensics",
2479    ],
2480};
2481
2482/// Recycle Bin ($I metadata files) — deletion evidence (T1070.004).
2483///
2484/// Each `$I{RAND}` file (8 bytes header + original path) records file size,
2485/// deletion timestamp, and original full path of the deleted file.
2486pub static RECYCLE_BIN: ArtifactDescriptor = ArtifactDescriptor {
2487    id: "recycle_bin",
2488    name: "Recycle Bin ($I Metadata)",
2489    artifact_type: ArtifactType::Directory,
2490    hive: None,
2491    key_path: "",
2492    value_name: None,
2493    file_path: Some(r"C:\$Recycle.Bin\*"),
2494    scope: DataScope::User,
2495    os_scope: OsScope::Win7Plus,
2496    decoder: Decoder::Identity,
2497    meaning: "$I files reveal original path and deletion time even after Recycle Bin is emptied",
2498    mitre_techniques: &["T1070.004", "T1083"],
2499    fields: DIR_ENTRY_FIELDS,
2500    retention: None,
2501    triage_priority: TriagePriority::Medium,
2502    related_artifacts: &[],
2503    sources: &[
2504        "https://attack.mitre.org/techniques/T1070/004/",
2505        "https://attack.mitre.org/techniques/T1083/",
2506        "https://www.sans.org/blog/digital-forensics-recycle-bin-forensics/",
2507        "https://windowsir.blogspot.com/2010/02/more-on-recycle-bin.html",
2508        "https://www.magnetforensics.com/blog/artifact-profile-recycle-bin/",
2509        "https://andreafortuna.org/2019/09/26/windows-forensics-analysis-of-recycle-bin-artifacts/",
2510    ],
2511};
2512
2513/// Windows Explorer Thumbnail Cache — file-access and image evidence.
2514///
2515/// Proprietary binary format; contains thumbnails for files browsed via
2516/// Explorer, including since-deleted images/documents.
2517pub static THUMBCACHE: ArtifactDescriptor = ArtifactDescriptor {
2518    id: "thumbcache",
2519    name: "Explorer Thumbnail Cache",
2520    artifact_type: ArtifactType::Directory,
2521    hive: None,
2522    key_path: "",
2523    value_name: None,
2524    file_path: Some(r"C:\Users\*\AppData\Local\Microsoft\Windows\Explorer"),
2525    scope: DataScope::User,
2526    os_scope: OsScope::Win7Plus,
2527    decoder: Decoder::Identity,
2528    meaning: "Cached thumbnails including deleted files; proves files were viewed via Explorer",
2529    mitre_techniques: &["T1083"],
2530    fields: DIR_ENTRY_FIELDS,
2531    retention: None,
2532    triage_priority: TriagePriority::Medium,
2533    related_artifacts: &[],
2534    sources: &[
2535        "https://attack.mitre.org/techniques/T1083/",
2536        "https://www.sans.org/blog/thumbnail-cache-forensics/",
2537        "https://www.nirsoft.net/utils/thumbcache_viewer.html",
2538        "https://www.pentestpartners.com/security-blog/thumbnail-forensics-dfir-techniques-for-analysing-windows-thumbcache/",
2539        "https://thumbcacheviewer.github.io/",
2540        "https://forensics.wiki/windows_thumbcache/",
2541    ],
2542};
2543
2544/// Windows Search database — indexed file/content search history.
2545///
2546/// ESE/JET database at the system level recording filenames, content excerpts,
2547/// and metadata for all indexed items. Survives file deletion.
2548pub static SEARCH_DB_USER: ArtifactDescriptor = ArtifactDescriptor {
2549    id: "search_db_user",
2550    name: "Windows Search Database (Windows.db)",
2551    artifact_type: ArtifactType::File,
2552    hive: None,
2553    key_path: "",
2554    value_name: None,
2555    file_path: Some(r"C:\ProgramData\Microsoft\Search\Data\Applications\Windows\Windows.edb"),
2556    scope: DataScope::System,
2557    os_scope: OsScope::All,
2558    decoder: Decoder::Identity,
2559    meaning:
2560        "ESE database of indexed file metadata; reveals filenames and content even after deletion",
2561    mitre_techniques: &["T1083"],
2562    fields: FILE_PATH_FIELDS,
2563    retention: None,
2564    triage_priority: TriagePriority::Medium,
2565    related_artifacts: &[],
2566    sources: &[
2567        "https://attack.mitre.org/techniques/T1083/",
2568        "https://www.sans.org/blog/windows-search-index-forensics/",
2569        "https://learn.microsoft.com/en-us/windows/win32/search/windows-search",
2570        "https://cyber.aon.com/aon_cyber_labs/windows-search-index-the-forensic-artifact-youve-been-searching-for/",
2571    ],
2572};
2573
2574// ── Windows credential artifacts ──────────────────────────────────────────
2575
2576static DPAPI_FIELDS: &[FieldSchema] = &[FieldSchema {
2577    name: "guid",
2578    value_type: ValueType::Text,
2579    description: "GUID filename of the DPAPI master key or credential blob",
2580    is_uid_component: true,
2581}];
2582
2583/// DPAPI User Master Keys — key material protecting all user-encrypted data.
2584///
2585/// Each file is named by a GUID; the content is the DPAPI master key encrypted
2586/// with the user's password hash. Decrypting unlocks all DPAPI-protected secrets.
2587pub static DPAPI_MASTERKEY_USER: ArtifactDescriptor = ArtifactDescriptor {
2588    id: "dpapi_masterkey_user",
2589    name: "DPAPI User Master Keys",
2590    artifact_type: ArtifactType::Directory,
2591    hive: None,
2592    key_path: "",
2593    value_name: None,
2594    file_path: Some(r"C:\Users\*\AppData\Roaming\Microsoft\Protect\*"),
2595    scope: DataScope::User,
2596    os_scope: OsScope::All,
2597    decoder: Decoder::Identity,
2598    meaning: "Master keys protecting all DPAPI-encrypted user secrets (credentials, browser passwords, WiFi PSKs)",
2599    mitre_techniques: &["T1555.004"],
2600    fields: DPAPI_FIELDS,
2601    retention: None,
2602    triage_priority: TriagePriority::Critical,
2603    related_artifacts: &["dpapi_cred_user", "dpapi_credhist", "chrome_login_data"],
2604    sources: &[
2605        "https://attack.mitre.org/techniques/T1555/004/",
2606        "https://www.sans.org/blog/dpapi-forensics-credentials-stored-in-windows/",
2607        "https://posts.specterops.io/operational-guidance-for-offensive-user-dpapi-abuse-1fb7fac8b107",
2608        "https://www.sygnia.co/blog/the-downfall-of-dpapis-top-secret-weapon/",
2609    ],
2610};
2611
2612/// DPAPI Credential Blobs (Local) — encrypted credential store entries.
2613///
2614/// GUID-named binary files; each contains a DPAPI-encrypted credential blob
2615/// protecting a username/password pair for a network resource or application.
2616pub static DPAPI_CRED_USER: ArtifactDescriptor = ArtifactDescriptor {
2617    id: "dpapi_cred_user",
2618    name: "DPAPI Credential Blobs (Local)",
2619    artifact_type: ArtifactType::Directory,
2620    hive: None,
2621    key_path: "",
2622    value_name: None,
2623    file_path: Some(r"C:\Users\*\AppData\Local\Microsoft\Credentials"),
2624    scope: DataScope::User,
2625    os_scope: OsScope::All,
2626    decoder: Decoder::Identity,
2627    meaning:
2628        "DPAPI-encrypted credential blobs for network resources; decryptable with DPAPI master key",
2629    mitre_techniques: &["T1555.004"],
2630    fields: DPAPI_FIELDS,
2631    retention: None,
2632    triage_priority: TriagePriority::High,
2633    related_artifacts: &["dpapi_masterkey_user", "windows_vault_user"],
2634    sources: &[
2635        "https://attack.mitre.org/techniques/T1555/004/",
2636        "https://www.sans.org/blog/dpapi-forensics-credentials-stored-in-windows/",
2637        "https://posts.specterops.io/operational-guidance-for-offensive-user-dpapi-abuse-1fb7fac8b107",
2638        "https://www.sygnia.co/blog/the-downfall-of-dpapis-top-secret-weapon/",
2639    ],
2640};
2641
2642/// DPAPI Credential Blobs (Roaming) — roaming profile credential store.
2643pub static DPAPI_CRED_ROAMING: ArtifactDescriptor = ArtifactDescriptor {
2644    id: "dpapi_cred_roaming",
2645    name: "DPAPI Credential Blobs (Roaming)",
2646    artifact_type: ArtifactType::Directory,
2647    hive: None,
2648    key_path: "",
2649    value_name: None,
2650    file_path: Some(r"C:\Users\*\AppData\Roaming\Microsoft\Credentials"),
2651    scope: DataScope::User,
2652    os_scope: OsScope::All,
2653    decoder: Decoder::Identity,
2654    meaning:
2655        "Roaming DPAPI credential blobs; same structure as Local, synced across domain machines",
2656    mitre_techniques: &["T1555.004"],
2657    fields: DPAPI_FIELDS,
2658    retention: None,
2659    triage_priority: TriagePriority::High,
2660    related_artifacts: &[],
2661    sources: &[
2662        "https://attack.mitre.org/techniques/T1555/004/",
2663        "https://www.sans.org/blog/dpapi-forensics-credentials-stored-in-windows/",
2664        "https://posts.specterops.io/operational-guidance-for-offensive-user-dpapi-abuse-1fb7fac8b107",
2665    ],
2666};
2667
2668static VAULT_FIELDS: &[FieldSchema] = &[
2669    FieldSchema {
2670        name: "policy_file",
2671        value_type: ValueType::Text,
2672        description: ".vpol policy file containing encryption key material",
2673        is_uid_component: false,
2674    },
2675    FieldSchema {
2676        name: "vcrd_file",
2677        value_type: ValueType::Text,
2678        description: ".vcrd credential file containing the encrypted credential",
2679        is_uid_component: true,
2680    },
2681];
2682
2683/// Windows Vault (User) — Windows Credential Manager per-user vault.
2684///
2685/// `.vpol` file stores encrypted vault key; `.vcrd` files store individual
2686/// credentials. Credential Manager UI entries live here.
2687pub static WINDOWS_VAULT_USER: ArtifactDescriptor = ArtifactDescriptor {
2688    id: "windows_vault_user",
2689    name: "Windows Vault (User)",
2690    artifact_type: ArtifactType::Directory,
2691    hive: None,
2692    key_path: "",
2693    value_name: None,
2694    file_path: Some(r"C:\Users\*\AppData\Local\Microsoft\Vault"),
2695    scope: DataScope::User,
2696    os_scope: OsScope::Win7Plus,
2697    decoder: Decoder::Identity,
2698    meaning: "Per-user Credential Manager vault (.vpol + .vcrd); contains WEB and WINDOWS saved credentials",
2699    mitre_techniques: &["T1555.004"],
2700    fields: VAULT_FIELDS,
2701    retention: None,
2702    triage_priority: TriagePriority::Medium,
2703    related_artifacts: &[],
2704    sources: &[
2705        "https://attack.mitre.org/techniques/T1555/004/",
2706        "https://learn.microsoft.com/en-us/windows/win32/secauthn/credential-manager",
2707        "https://blog.digital-forensics.it/2016/01/windows-revaulting.html",
2708    ],
2709};
2710
2711/// Windows Vault (System) — system-wide Windows Credential Manager vault.
2712pub static WINDOWS_VAULT_SYSTEM: ArtifactDescriptor = ArtifactDescriptor {
2713    id: "windows_vault_system",
2714    name: "Windows Vault (System)",
2715    artifact_type: ArtifactType::Directory,
2716    hive: None,
2717    key_path: "",
2718    value_name: None,
2719    file_path: Some(r"C:\ProgramData\Microsoft\Vault"),
2720    scope: DataScope::System,
2721    os_scope: OsScope::Win7Plus,
2722    decoder: Decoder::Identity,
2723    meaning: "System-level Windows Credential Manager vault; contains machine-scoped credentials",
2724    mitre_techniques: &["T1555.004"],
2725    fields: VAULT_FIELDS,
2726    retention: None,
2727    triage_priority: TriagePriority::Medium,
2728    related_artifacts: &[],
2729    sources: &[
2730        "https://attack.mitre.org/techniques/T1555/004/",
2731        "https://learn.microsoft.com/en-us/windows/win32/secauthn/credential-manager",
2732        "https://blog.digital-forensics.it/2016/01/windows-revaulting.html",
2733    ],
2734};
2735
2736static RDP_FIELDS: &[FieldSchema] = &[FieldSchema {
2737    name: "username_hint",
2738    value_type: ValueType::Text,
2739    description: "Last username used to connect to this RDP server",
2740    is_uid_component: false,
2741}];
2742
2743/// RDP Saved Server Connections — lateral movement evidence (T1021.001).
2744///
2745/// Each sub-key is a hostname/IP; the `UsernameHint` value shows the username
2746/// used for that connection. Evidence of RDP-based lateral movement.
2747pub static RDP_CLIENT_SERVERS: ArtifactDescriptor = ArtifactDescriptor {
2748    id: "rdp_client_servers",
2749    name: "RDP Client Saved Servers",
2750    artifact_type: ArtifactType::RegistryKey,
2751    hive: Some(HiveTarget::NtUser),
2752    key_path: r"Software\Microsoft\Terminal Server Client\Servers",
2753    value_name: Some("UsernameHint"),
2754    file_path: None,
2755    scope: DataScope::User,
2756    os_scope: OsScope::All,
2757    decoder: Decoder::Identity,
2758    meaning:
2759        "Hostnames and usernames of previously-connected RDP servers; lateral movement evidence",
2760    mitre_techniques: &["T1021.001"],
2761    fields: RDP_FIELDS,
2762    retention: None,
2763    triage_priority: TriagePriority::Medium,
2764    related_artifacts: &[],
2765    sources: &[
2766        "https://attack.mitre.org/techniques/T1021/001/",
2767        "https://www.sans.org/blog/windows-rdp-forensics/",
2768        "https://forensafe.com/blogs/rdc.html",
2769        "https://www.magnetforensics.com/blog/rdp-artifacts-in-incident-response/",
2770    ],
2771};
2772
2773static RDP_MRU_FIELDS: &[FieldSchema] = &[FieldSchema {
2774    name: "server",
2775    value_type: ValueType::Text,
2776    description: "RDP server address from the most-recently-used list",
2777    is_uid_component: true,
2778}];
2779
2780/// RDP Client Default MRU — ordered list of recently connected RDP servers.
2781pub static RDP_CLIENT_DEFAULT: ArtifactDescriptor = ArtifactDescriptor {
2782    id: "rdp_client_default",
2783    name: "RDP Client Default MRU",
2784    artifact_type: ArtifactType::RegistryKey,
2785    hive: Some(HiveTarget::NtUser),
2786    key_path: r"Software\Microsoft\Terminal Server Client\Default",
2787    value_name: None,
2788    file_path: None,
2789    scope: DataScope::User,
2790    os_scope: OsScope::All,
2791    decoder: Decoder::Identity,
2792    meaning:
2793        "MRU0-MRU9 ordered list of RDP server addresses; confirms specific hosts were targeted",
2794    mitre_techniques: &["T1021.001"],
2795    fields: RDP_MRU_FIELDS,
2796    retention: None,
2797    triage_priority: TriagePriority::Medium,
2798    related_artifacts: &[],
2799    sources: &[
2800        "https://attack.mitre.org/techniques/T1021/001/",
2801        "https://www.sans.org/blog/windows-rdp-forensics/",
2802        "https://forensafe.com/blogs/rdc.html",
2803        "https://www.magnetforensics.com/blog/rdp-artifacts-in-incident-response/",
2804    ],
2805};
2806
2807static NTDS_FIELDS: &[FieldSchema] = &[FieldSchema {
2808    name: "path",
2809    value_type: ValueType::Text,
2810    description: "Full path to the NTDS.dit file",
2811    is_uid_component: true,
2812}];
2813
2814/// NTDS.dit — Active Directory database (DC only) (T1003.003).
2815///
2816/// Contains all domain user account hashes. Extracting and cracking these
2817/// grants access to every domain account. Requires VSS or offline access.
2818pub static NTDS_DIT: ArtifactDescriptor = ArtifactDescriptor {
2819    id: "ntds_dit",
2820    name: "Active Directory Database (NTDS.dit)",
2821    artifact_type: ArtifactType::File,
2822    hive: None,
2823    key_path: "",
2824    value_name: None,
2825    file_path: Some(r"C:\Windows\NTDS\NTDS.dit"),
2826    scope: DataScope::System,
2827    os_scope: OsScope::All,
2828    decoder: Decoder::Identity,
2829    meaning: "Domain controller AD database; contains NTLM hashes for all domain accounts",
2830    mitre_techniques: &["T1003.003"],
2831    fields: NTDS_FIELDS,
2832    retention: None,
2833    triage_priority: TriagePriority::Critical,
2834    related_artifacts: &[],
2835    sources: &[
2836        "https://attack.mitre.org/techniques/T1003/003/",
2837        "https://www.sans.org/blog/protecting-ad-from-credential-theft/",
2838    ],
2839};
2840
2841static BROWSER_CRED_FIELDS: &[FieldSchema] = &[
2842    FieldSchema {
2843        name: "origin_url",
2844        value_type: ValueType::Text,
2845        description: "URL the credential is associated with",
2846        is_uid_component: true,
2847    },
2848    FieldSchema {
2849        name: "username_value",
2850        value_type: ValueType::Text,
2851        description: "Saved username",
2852        is_uid_component: false,
2853    },
2854];
2855
2856/// Chrome/Edge Login Data — SQLite database of saved browser passwords (T1555.003).
2857pub static CHROME_LOGIN_DATA: ArtifactDescriptor = ArtifactDescriptor {
2858    id: "chrome_login_data",
2859    name: "Chrome/Edge Login Data (SQLite)",
2860    artifact_type: ArtifactType::File,
2861    hive: None,
2862    key_path: "",
2863    value_name: None,
2864    file_path: Some(r"C:\Users\*\AppData\Local\Google\Chrome\User Data\Default\Login Data"),
2865    scope: DataScope::User,
2866    os_scope: OsScope::All,
2867    decoder: Decoder::Identity,
2868    meaning: "SQLite DB with DPAPI-encrypted passwords for saved Chrome/Edge credentials",
2869    mitre_techniques: &["T1555.003"],
2870    fields: BROWSER_CRED_FIELDS,
2871    retention: None,
2872    triage_priority: TriagePriority::Critical,
2873    related_artifacts: &["chrome_cookies", "dpapi_masterkey_user"],
2874    sources: &[
2875        "https://attack.mitre.org/techniques/T1555/003/",
2876        "https://redcanary.com/threat-detection-report/techniques/t1555/",
2877        "https://atropos4n6.com/windows/chrome-login-data-forensics/",
2878        "https://www.foxtonforensics.com/blog/post/analysing-chrome-login-data",
2879    ],
2880};
2881
2882static FIREFOX_CRED_FIELDS: &[FieldSchema] = &[FieldSchema {
2883    name: "hostname",
2884    value_type: ValueType::Text,
2885    description: "Hostname the Firefox credential is associated with",
2886    is_uid_component: true,
2887}];
2888
2889/// Firefox logins.json — JSON credential store (T1555.003).
2890///
2891/// NSS3-encrypted credentials; decryptable with `key4.db` and user's Firefox password.
2892pub static FIREFOX_LOGINS: ArtifactDescriptor = ArtifactDescriptor {
2893    id: "firefox_logins",
2894    name: "Firefox logins.json",
2895    artifact_type: ArtifactType::File,
2896    hive: None,
2897    key_path: "",
2898    value_name: None,
2899    file_path: Some(r"C:\Users\*\AppData\Roaming\Mozilla\Firefox\Profiles\*\logins.json"),
2900    scope: DataScope::User,
2901    os_scope: OsScope::All,
2902    decoder: Decoder::Identity,
2903    meaning:
2904        "NSS3-encrypted Firefox saved credentials; decryptable with key4.db and master password",
2905    mitre_techniques: &["T1555.003"],
2906    fields: FIREFOX_CRED_FIELDS,
2907    retention: None,
2908    triage_priority: TriagePriority::Critical,
2909    related_artifacts: &[],
2910    sources: &[
2911        "https://attack.mitre.org/techniques/T1555/003/",
2912        "https://redcanary.com/threat-detection-report/techniques/t1555/",
2913        "https://atropos4n6.com/windows/chrome-login-data-forensics/",
2914    ],
2915};
2916
2917static WIFI_FIELDS: &[FieldSchema] = &[
2918    FieldSchema {
2919        name: "ssid",
2920        value_type: ValueType::Text,
2921        description: "WiFi network SSID (network name)",
2922        is_uid_component: true,
2923    },
2924    FieldSchema {
2925        name: "key_material",
2926        value_type: ValueType::Text,
2927        description: "Pre-shared key or 802.1X EAP credentials (may be DPAPI-encrypted)",
2928        is_uid_component: false,
2929    },
2930];
2931
2932/// Wireless Network Profiles — contains PSKs for previously joined networks (T1552.001).
2933///
2934/// XML files; `<keyMaterial>` field may contain the plaintext PSK or a
2935/// DPAPI-encrypted blob depending on profile type.
2936pub static WIFI_PROFILES: ArtifactDescriptor = ArtifactDescriptor {
2937    id: "wifi_profiles",
2938    name: "Wireless Network Profiles (WLAN)",
2939    artifact_type: ArtifactType::Directory,
2940    hive: None,
2941    key_path: "",
2942    value_name: None,
2943    file_path: Some(r"C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces"),
2944    scope: DataScope::System,
2945    os_scope: OsScope::All,
2946    decoder: Decoder::Identity,
2947    meaning: "XML profiles for previously joined WiFi networks; may contain plaintext PSKs",
2948    mitre_techniques: &["T1552.001"],
2949    fields: WIFI_FIELDS,
2950    retention: None,
2951    triage_priority: TriagePriority::Medium,
2952    related_artifacts: &[],
2953    sources: &[
2954        "https://attack.mitre.org/techniques/T1552/001/",
2955        "https://www.sans.org/blog/wireless-forensics/",
2956        "https://forensafe.com/blogs/winwirelessnetworks.html",
2957    ],
2958};
2959
2960// ═══════════════════════════════════════════════════════════════════════════
2961// Batch D — Linux persistence / execution / credential artifacts
2962// ═══════════════════════════════════════════════════════════════════════════
2963
2964// ── Shared Linux field schemas ────────────────────────────────────────────
2965
2966/// Cron / script line — single scheduled command or shell line.
2967static CRON_LINE_FIELDS: &[FieldSchema] = &[FieldSchema {
2968    name: "schedule_line",
2969    value_type: ValueType::Text,
2970    description: "Cron schedule expression and command, or shell script line",
2971    is_uid_component: false,
2972}];
2973
2974/// SSH public key entry.
2975static SSH_KEY_FIELDS: &[FieldSchema] = &[FieldSchema {
2976    name: "public_key",
2977    value_type: ValueType::Text,
2978    description: "SSH public key entry (key-type base64 comment)",
2979    is_uid_component: true,
2980}];
2981
2982/// Linux account entry (colon-delimited fields).
2983static ACCOUNT_FIELDS: &[FieldSchema] = &[
2984    FieldSchema {
2985        name: "username",
2986        value_type: ValueType::Text,
2987        description: "Account username",
2988        is_uid_component: true,
2989    },
2990    FieldSchema {
2991        name: "uid",
2992        value_type: ValueType::UnsignedInt,
2993        description: "Numeric user ID (0 = root)",
2994        is_uid_component: false,
2995    },
2996];
2997
2998/// Log line / journal entry.
2999static LOG_LINE_FIELDS: &[FieldSchema] = &[FieldSchema {
3000    name: "log_line",
3001    value_type: ValueType::Text,
3002    description: "Log line or structured journal entry",
3003    is_uid_component: false,
3004}];
3005
3006// ── Linux persistence: cron ───────────────────────────────────────────────
3007
3008/// System-wide crontab at `/etc/crontab` (T1053.003).
3009///
3010/// Format: `minute hour dom month dow user command`. Field `user` distinguishes
3011/// this from per-user crontabs. Any non-root `user` with unusual commands is suspicious.
3012pub static LINUX_CRONTAB_SYSTEM: ArtifactDescriptor = ArtifactDescriptor {
3013    id: "linux_crontab_system",
3014    name: "System Crontab (/etc/crontab)",
3015    artifact_type: ArtifactType::File,
3016    hive: None,
3017    key_path: "",
3018    value_name: None,
3019    file_path: Some("/etc/crontab"),
3020    scope: DataScope::System,
3021    os_scope: OsScope::Linux,
3022    decoder: Decoder::Identity,
3023    meaning: "System-wide scheduled job definitions; user field allows cross-account execution",
3024    mitre_techniques: &["T1053.003"],
3025    fields: CRON_LINE_FIELDS,
3026    retention: None,
3027    triage_priority: TriagePriority::Medium,
3028    related_artifacts: &[],
3029    sources: &[
3030        "https://attack.mitre.org/techniques/T1053/003/",
3031        "https://www.sans.org/blog/linux-persistence-mechanisms/",
3032        "https://linux.die.net/man/5/crontab",
3033        "https://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/",
3034    ],
3035};
3036
3037/// Drop-in cron jobs directory `/etc/cron.d/` (T1053.003).
3038///
3039/// Files here follow the same format as `/etc/crontab` (with user field).
3040/// Attackers drop files here for system-level persistence without editing crontab.
3041pub static LINUX_CRON_D: ArtifactDescriptor = ArtifactDescriptor {
3042    id: "linux_cron_d",
3043    name: "Cron Drop-in Directory (/etc/cron.d/)",
3044    artifact_type: ArtifactType::Directory,
3045    hive: None,
3046    key_path: "",
3047    value_name: None,
3048    file_path: Some("/etc/cron.d"),
3049    scope: DataScope::System,
3050    os_scope: OsScope::Linux,
3051    decoder: Decoder::Identity,
3052    meaning: "Drop-in cron files with full crontab format; easy to add without touching crontab",
3053    mitre_techniques: &["T1053.003"],
3054    fields: CRON_LINE_FIELDS,
3055    retention: None,
3056    triage_priority: TriagePriority::Medium,
3057    related_artifacts: &[],
3058    sources: &[
3059        "https://attack.mitre.org/techniques/T1053/003/",
3060        "https://www.sans.org/blog/linux-persistence-mechanisms/",
3061        "https://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/",
3062    ],
3063};
3064
3065/// Periodic cron directories (daily/hourly/weekly/monthly) (T1053.003).
3066///
3067/// Scripts placed here are executed by run-parts at the named interval.
3068/// No schedule expression needed — just a plain executable script.
3069pub static LINUX_CRON_PERIODIC: ArtifactDescriptor = ArtifactDescriptor {
3070    id: "linux_cron_periodic",
3071    name: "Cron Periodic Directories (/etc/cron.{daily,hourly,weekly,monthly}/)",
3072    artifact_type: ArtifactType::Directory,
3073    hive: None,
3074    key_path: "",
3075    value_name: None,
3076    file_path: Some("/etc/cron.daily"),
3077    scope: DataScope::System,
3078    os_scope: OsScope::Linux,
3079    decoder: Decoder::Identity,
3080    meaning: "Shell scripts executed periodically by crond/anacron; no schedule syntax required",
3081    mitre_techniques: &["T1053.003"],
3082    fields: DIR_ENTRY_FIELDS,
3083    retention: None,
3084    triage_priority: TriagePriority::Medium,
3085    related_artifacts: &[],
3086    sources: &["https://attack.mitre.org/techniques/T1053/003/"],
3087};
3088
3089/// Per-user crontab spool at `/var/spool/cron/crontabs/{user}` (T1053.003).
3090///
3091/// Each file is owned by and runs commands as the named user.
3092/// `crontab -e` edits this file. Direct edits by root are possible.
3093pub static LINUX_USER_CRONTAB: ArtifactDescriptor = ArtifactDescriptor {
3094    id: "linux_user_crontab",
3095    name: "Per-User Crontab Spool",
3096    artifact_type: ArtifactType::File,
3097    hive: None,
3098    key_path: "",
3099    value_name: None,
3100    file_path: Some("/var/spool/cron/crontabs/*"),
3101    scope: DataScope::User,
3102    os_scope: OsScope::Linux,
3103    decoder: Decoder::Identity,
3104    meaning: "Per-user scheduled jobs; attacker can set up recurring execution without admin",
3105    mitre_techniques: &["T1053.003"],
3106    fields: CRON_LINE_FIELDS,
3107    retention: None,
3108    triage_priority: TriagePriority::Medium,
3109    related_artifacts: &[],
3110    sources: &[
3111        "https://attack.mitre.org/techniques/T1053/003/",
3112        "https://www.sans.org/blog/linux-persistence-mechanisms/",
3113        "https://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/",
3114    ],
3115};
3116
3117/// Anacron configuration at `/etc/anacrontab`.
3118///
3119/// Anacron runs jobs that were missed due to system downtime — useful for
3120/// laptops. Format: `period delay job-id command`.
3121pub static LINUX_ANACRONTAB: ArtifactDescriptor = ArtifactDescriptor {
3122    id: "linux_anacrontab",
3123    name: "Anacrontab (/etc/anacrontab)",
3124    artifact_type: ArtifactType::File,
3125    hive: None,
3126    key_path: "",
3127    value_name: None,
3128    file_path: Some("/etc/anacrontab"),
3129    scope: DataScope::System,
3130    os_scope: OsScope::Linux,
3131    decoder: Decoder::Identity,
3132    meaning: "Deferred cron jobs for irregular uptime; period-based rather than time-based",
3133    mitre_techniques: &["T1053.003"],
3134    fields: CRON_LINE_FIELDS,
3135    retention: None,
3136    triage_priority: TriagePriority::Medium,
3137    related_artifacts: &[],
3138    sources: &[
3139        "https://attack.mitre.org/techniques/T1053/003/",
3140        "https://linux.die.net/man/8/anacron",
3141    ],
3142};
3143
3144// ── Linux persistence: systemd ────────────────────────────────────────────
3145
3146/// System-level systemd service units (T1543.002).
3147///
3148/// `.service` files in `/etc/systemd/system/` (admin-installed, highest priority)
3149/// or `/lib/systemd/system/` (package-installed). Key fields: `ExecStart`,
3150/// `WantedBy`, `After`. Malicious units often `WantedBy=multi-user.target`.
3151pub static LINUX_SYSTEMD_SYSTEM_UNIT: ArtifactDescriptor = ArtifactDescriptor {
3152    id: "linux_systemd_system_unit",
3153    name: "systemd System Service Units",
3154    artifact_type: ArtifactType::Directory,
3155    hive: None,
3156    key_path: "",
3157    value_name: None,
3158    file_path: Some("/etc/systemd/system"),
3159    scope: DataScope::System,
3160    os_scope: OsScope::LinuxSystemd,
3161    decoder: Decoder::Identity,
3162    meaning:
3163        "Service definitions executed as root at boot; WantedBy=multi-user.target = auto-start",
3164    mitre_techniques: &["T1543.002"],
3165    fields: DIR_ENTRY_FIELDS,
3166    retention: None,
3167    triage_priority: TriagePriority::Medium,
3168    related_artifacts: &[],
3169    sources: &[
3170        "https://attack.mitre.org/techniques/T1543/002/",
3171        "https://www.sans.org/blog/linux-persistence-mechanisms/",
3172        "https://www.freedesktop.org/software/systemd/man/systemd.unit.html",
3173        "https://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/",
3174        "https://www.elastic.co/security-labs/primer-on-persistence-mechanisms",
3175    ],
3176};
3177
3178/// Per-user systemd service units (T1543.002).
3179///
3180/// Stored in `~/.config/systemd/user/*.service`; executed as the user's
3181/// session starts. No root required. `systemctl --user enable` activates.
3182pub static LINUX_SYSTEMD_USER_UNIT: ArtifactDescriptor = ArtifactDescriptor {
3183    id: "linux_systemd_user_unit",
3184    name: "systemd User Service Units",
3185    artifact_type: ArtifactType::Directory,
3186    hive: None,
3187    key_path: "",
3188    value_name: None,
3189    file_path: Some("~/.config/systemd/user"),
3190    scope: DataScope::User,
3191    os_scope: OsScope::LinuxSystemd,
3192    decoder: Decoder::Identity,
3193    meaning: "User-scope service definitions; executed without root on user login",
3194    mitre_techniques: &["T1543.002"],
3195    fields: DIR_ENTRY_FIELDS,
3196    retention: None,
3197    triage_priority: TriagePriority::Medium,
3198    related_artifacts: &[],
3199    sources: &[
3200        "https://attack.mitre.org/techniques/T1543/002/",
3201        "https://www.freedesktop.org/software/systemd/man/systemd.unit.html",
3202        "https://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/",
3203    ],
3204};
3205
3206/// systemd timer units — cron-like scheduling (T1053.006).
3207///
3208/// `.timer` files trigger associated `.service` units on a schedule.
3209/// More flexible than cron: supports calendar expressions and monotonic timers.
3210pub static LINUX_SYSTEMD_TIMER: ArtifactDescriptor = ArtifactDescriptor {
3211    id: "linux_systemd_timer",
3212    name: "systemd Timer Units",
3213    artifact_type: ArtifactType::Directory,
3214    hive: None,
3215    key_path: "",
3216    value_name: None,
3217    file_path: Some("/etc/systemd/system"),
3218    scope: DataScope::System,
3219    os_scope: OsScope::LinuxSystemd,
3220    decoder: Decoder::Identity,
3221    meaning: "Timer-based scheduled execution; malicious timers trigger services on a schedule",
3222    mitre_techniques: &["T1053.006"],
3223    fields: DIR_ENTRY_FIELDS,
3224    retention: None,
3225    triage_priority: TriagePriority::Medium,
3226    related_artifacts: &[],
3227    sources: &[
3228        "https://attack.mitre.org/techniques/T1053/006/",
3229        "https://www.freedesktop.org/software/systemd/man/systemd.timer.html",
3230        "https://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/",
3231    ],
3232};
3233
3234// ── Linux persistence: init / rc.local ───────────────────────────────────
3235
3236/// `/etc/rc.local` — legacy startup script (T1037.004).
3237///
3238/// Executed at the end of each multiuser runlevel. Still supported on most
3239/// distros. Must be executable (+x). Any command here runs as root at boot.
3240pub static LINUX_RC_LOCAL: ArtifactDescriptor = ArtifactDescriptor {
3241    id: "linux_rc_local",
3242    name: "rc.local Startup Script",
3243    artifact_type: ArtifactType::File,
3244    hive: None,
3245    key_path: "",
3246    value_name: None,
3247    file_path: Some("/etc/rc.local"),
3248    scope: DataScope::System,
3249    os_scope: OsScope::Linux,
3250    decoder: Decoder::Identity,
3251    meaning: "Legacy boot-time script executed as root; simple and widely supported",
3252    mitre_techniques: &["T1037.004"],
3253    fields: CRON_LINE_FIELDS,
3254    retention: None,
3255    triage_priority: TriagePriority::Medium,
3256    related_artifacts: &[],
3257    sources: &[
3258        "https://attack.mitre.org/techniques/T1037/004/",
3259        "https://www.sans.org/blog/linux-persistence-mechanisms/",
3260        "https://pberba.github.io/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration/",
3261        "https://www.elastic.co/security-labs/sequel-on-persistence-mechanisms",
3262    ],
3263};
3264
3265/// SysV init scripts directory `/etc/init.d/`.
3266///
3267/// Scripts here are executed by the init system at specific runlevels.
3268/// Symlinks in `/etc/rc{N}.d/` control when they run. Legacy but still present.
3269pub static LINUX_INIT_D: ArtifactDescriptor = ArtifactDescriptor {
3270    id: "linux_init_d",
3271    name: "SysV Init Scripts (/etc/init.d/)",
3272    artifact_type: ArtifactType::Directory,
3273    hive: None,
3274    key_path: "",
3275    value_name: None,
3276    file_path: Some("/etc/init.d"),
3277    scope: DataScope::System,
3278    os_scope: OsScope::Linux,
3279    decoder: Decoder::Identity,
3280    meaning: "SysV init scripts; malicious script here runs at boot across reboots",
3281    mitre_techniques: &["T1543.002"],
3282    fields: DIR_ENTRY_FIELDS,
3283    retention: None,
3284    triage_priority: TriagePriority::Medium,
3285    related_artifacts: &[],
3286    sources: &[
3287        "https://attack.mitre.org/techniques/T1543/002/",
3288        "https://attack.mitre.org/techniques/T1037/004/",
3289        "https://pberba.github.io/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration/",
3290    ],
3291};
3292
3293// ── Linux persistence: shell startup files ────────────────────────────────
3294
3295/// `~/.bashrc` — per-user Bash interactive shell startup (T1546.004).
3296///
3297/// Sourced for every non-login interactive bash shell. Attackers add aliases,
3298/// functions, or background processes here. Survives reboots.
3299pub static LINUX_BASHRC_USER: ArtifactDescriptor = ArtifactDescriptor {
3300    id: "linux_bashrc_user",
3301    name: "User ~/.bashrc",
3302    artifact_type: ArtifactType::File,
3303    hive: None,
3304    key_path: "",
3305    value_name: None,
3306    file_path: Some("~/.bashrc"),
3307    scope: DataScope::User,
3308    os_scope: OsScope::Linux,
3309    decoder: Decoder::Identity,
3310    meaning: "Sourced on every interactive bash session; persistent aliases, functions, or background processes",
3311    mitre_techniques: &["T1546.004"],
3312    fields: CRON_LINE_FIELDS,
3313    retention: None,
3314    triage_priority: TriagePriority::Medium,
3315    related_artifacts: &[],
3316    sources: &[
3317        "https://attack.mitre.org/techniques/T1546/004/",
3318        "https://www.sans.org/blog/linux-persistence-mechanisms/",
3319        "https://pberba.github.io/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration/",
3320        "https://www.elastic.co/guide/en/security/current/bash-shell-profile-modification.html",
3321    ],
3322};
3323
3324/// `~/.bash_profile` — Bash login shell startup (T1546.004).
3325pub static LINUX_BASH_PROFILE_USER: ArtifactDescriptor = ArtifactDescriptor {
3326    id: "linux_bash_profile_user",
3327    name: "User ~/.bash_profile",
3328    artifact_type: ArtifactType::File,
3329    hive: None,
3330    key_path: "",
3331    value_name: None,
3332    file_path: Some("~/.bash_profile"),
3333    scope: DataScope::User,
3334    os_scope: OsScope::Linux,
3335    decoder: Decoder::Identity,
3336    meaning: "Sourced on Bash login shells; runs at SSH login and console login",
3337    mitre_techniques: &["T1546.004"],
3338    fields: CRON_LINE_FIELDS,
3339    retention: None,
3340    triage_priority: TriagePriority::Medium,
3341    related_artifacts: &[],
3342    sources: &[
3343        "https://attack.mitre.org/techniques/T1546/004/",
3344        "https://pberba.github.io/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration/",
3345    ],
3346};
3347
3348/// `~/.profile` — POSIX login shell startup.
3349pub static LINUX_PROFILE_USER: ArtifactDescriptor = ArtifactDescriptor {
3350    id: "linux_profile_user",
3351    name: "User ~/.profile",
3352    artifact_type: ArtifactType::File,
3353    hive: None,
3354    key_path: "",
3355    value_name: None,
3356    file_path: Some("~/.profile"),
3357    scope: DataScope::User,
3358    os_scope: OsScope::Linux,
3359    decoder: Decoder::Identity,
3360    meaning: "POSIX login shell startup; sourced by sh, dash, and bash on login",
3361    mitre_techniques: &["T1546.004"],
3362    fields: CRON_LINE_FIELDS,
3363    retention: None,
3364    triage_priority: TriagePriority::Medium,
3365    related_artifacts: &[],
3366    sources: &[
3367        "https://attack.mitre.org/techniques/T1546/004/",
3368        "https://pberba.github.io/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration/",
3369    ],
3370};
3371
3372/// `~/.zshrc` — per-user Zsh interactive startup (T1546.004).
3373pub static LINUX_ZSHRC_USER: ArtifactDescriptor = ArtifactDescriptor {
3374    id: "linux_zshrc_user",
3375    name: "User ~/.zshrc",
3376    artifact_type: ArtifactType::File,
3377    hive: None,
3378    key_path: "",
3379    value_name: None,
3380    file_path: Some("~/.zshrc"),
3381    scope: DataScope::User,
3382    os_scope: OsScope::Linux,
3383    decoder: Decoder::Identity,
3384    meaning: "Sourced on every interactive Zsh session; same persistence vector as .bashrc",
3385    mitre_techniques: &["T1546.004"],
3386    fields: CRON_LINE_FIELDS,
3387    retention: None,
3388    triage_priority: TriagePriority::Medium,
3389    related_artifacts: &[],
3390    sources: &[
3391        "https://attack.mitre.org/techniques/T1546/004/",
3392        "https://pberba.github.io/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration/",
3393    ],
3394};
3395
3396/// `/etc/profile` — system-wide login shell startup.
3397pub static LINUX_PROFILE_SYSTEM: ArtifactDescriptor = ArtifactDescriptor {
3398    id: "linux_profile_system",
3399    name: "System /etc/profile",
3400    artifact_type: ArtifactType::File,
3401    hive: None,
3402    key_path: "",
3403    value_name: None,
3404    file_path: Some("/etc/profile"),
3405    scope: DataScope::System,
3406    os_scope: OsScope::Linux,
3407    decoder: Decoder::Identity,
3408    meaning: "System-wide login shell startup; modifications affect all users",
3409    mitre_techniques: &["T1546.004"],
3410    fields: CRON_LINE_FIELDS,
3411    retention: None,
3412    triage_priority: TriagePriority::Medium,
3413    related_artifacts: &[],
3414    sources: &[
3415        "https://attack.mitre.org/techniques/T1546/004/",
3416        "https://www.sans.org/blog/linux-persistence-mechanisms/",
3417        "https://pberba.github.io/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration/",
3418    ],
3419};
3420
3421/// `/etc/profile.d/` — drop-in system-wide shell startup scripts.
3422pub static LINUX_PROFILE_D: ArtifactDescriptor = ArtifactDescriptor {
3423    id: "linux_profile_d",
3424    name: "System /etc/profile.d/ Drop-ins",
3425    artifact_type: ArtifactType::Directory,
3426    hive: None,
3427    key_path: "",
3428    value_name: None,
3429    file_path: Some("/etc/profile.d"),
3430    scope: DataScope::System,
3431    os_scope: OsScope::Linux,
3432    decoder: Decoder::Identity,
3433    meaning: "Shell scripts sourced by /etc/profile for all users at login; drop-in persistence",
3434    mitre_techniques: &["T1546.004"],
3435    fields: DIR_ENTRY_FIELDS,
3436    retention: None,
3437    triage_priority: TriagePriority::Medium,
3438    related_artifacts: &[],
3439    sources: &[
3440        "https://attack.mitre.org/techniques/T1546/004/",
3441        "https://pberba.github.io/security/2022/02/06/linux-threat-hunting-for-persistence-initialization-scripts-and-shell-configuration/",
3442    ],
3443};
3444
3445// ── Linux persistence: dynamic linker ────────────────────────────────────
3446
3447/// `/etc/ld.so.preload` — system-wide library preload (T1574.006).
3448///
3449/// Libraries listed here are loaded into EVERY process before any other
3450/// library, including setuid binaries. This is a classic rootkit technique.
3451/// An empty or absent file is normal; ANY entry is highly suspicious.
3452pub static LINUX_LD_SO_PRELOAD: ArtifactDescriptor = ArtifactDescriptor {
3453    id: "linux_ld_so_preload",
3454    name: "Dynamic Linker Preload (/etc/ld.so.preload)",
3455    artifact_type: ArtifactType::File,
3456    hive: None,
3457    key_path: "",
3458    value_name: None,
3459    file_path: Some("/etc/ld.so.preload"),
3460    scope: DataScope::System,
3461    os_scope: OsScope::Linux,
3462    decoder: Decoder::Identity,
3463    meaning:
3464        "Libraries preloaded into EVERY process system-wide; standard rootkit hiding mechanism",
3465    mitre_techniques: &["T1574.006"],
3466    fields: CRON_LINE_FIELDS,
3467    retention: None,
3468    triage_priority: TriagePriority::Medium,
3469    related_artifacts: &[],
3470    sources: &[
3471        "https://attack.mitre.org/techniques/T1574/006/",
3472        "https://www.sans.org/blog/linux-persistence-mechanisms/",
3473        "https://www.wiz.io/blog/linux-rootkits-explained-part-1-dynamic-linker-hijacking",
3474        "https://www.sentinelone.com/labs/leveraging-ld_audit-to-beat-the-traditional-linux-library-preloading-technique/",
3475    ],
3476};
3477
3478/// `/etc/ld.so.conf.d/` — linker search path configuration (T1574.006).
3479///
3480/// Adding a directory containing malicious `.so` files here allows library
3481/// hijacking without needing LD_PRELOAD.
3482pub static LINUX_LD_SO_CONF_D: ArtifactDescriptor = ArtifactDescriptor {
3483    id: "linux_ld_so_conf_d",
3484    name: "Linker Config Directory (/etc/ld.so.conf.d/)",
3485    artifact_type: ArtifactType::Directory,
3486    hive: None,
3487    key_path: "",
3488    value_name: None,
3489    file_path: Some("/etc/ld.so.conf.d"),
3490    scope: DataScope::System,
3491    os_scope: OsScope::Linux,
3492    decoder: Decoder::Identity,
3493    meaning:
3494        "Library search path config; malicious entry adds attacker directory to ldconfig paths",
3495    mitre_techniques: &["T1574.006"],
3496    fields: DIR_ENTRY_FIELDS,
3497    retention: None,
3498    triage_priority: TriagePriority::Medium,
3499    related_artifacts: &[],
3500    sources: &["https://attack.mitre.org/techniques/T1574/006/"],
3501};
3502
3503// ── Linux persistence: SSH ────────────────────────────────────────────────
3504
3505/// SSH authorized_keys — persistent backdoor public keys (T1098.004).
3506///
3507/// Any public key listed here allows passwordless SSH login as the owner.
3508/// Attackers add their key for persistent remote access.
3509pub static LINUX_SSH_AUTHORIZED_KEYS: ArtifactDescriptor = ArtifactDescriptor {
3510    id: "linux_ssh_authorized_keys",
3511    name: "SSH authorized_keys",
3512    artifact_type: ArtifactType::File,
3513    hive: None,
3514    key_path: "",
3515    value_name: None,
3516    file_path: Some("~/.ssh/authorized_keys"),
3517    scope: DataScope::User,
3518    os_scope: OsScope::Linux,
3519    decoder: Decoder::Identity,
3520    meaning: "Public keys permitting passwordless SSH login; attacker key = permanent backdoor",
3521    mitre_techniques: &["T1098.004"],
3522    fields: SSH_KEY_FIELDS,
3523    retention: None,
3524    triage_priority: TriagePriority::High,
3525    related_artifacts: &[],
3526    sources: &[
3527        "https://attack.mitre.org/techniques/T1098/004/",
3528        "https://www.sans.org/blog/ssh-backdoors/",
3529        "https://sandflysecurity.com/blog/detecting-unauthorized-ssh-keys-in-linux/",
3530    ],
3531};
3532
3533// ── Linux persistence: PAM / privilege / kernel ───────────────────────────
3534
3535/// `/etc/pam.d/` — PAM module configuration (T1556.003).
3536///
3537/// Each file configures authentication for a service (e.g., `sshd`, `sudo`,
3538/// `su`). Replacing `pam_unix.so` or adding a malicious module intercepts
3539/// ALL authentication for that service.
3540pub static LINUX_PAM_D: ArtifactDescriptor = ArtifactDescriptor {
3541    id: "linux_pam_d",
3542    name: "PAM Configuration (/etc/pam.d/)",
3543    artifact_type: ArtifactType::Directory,
3544    hive: None,
3545    key_path: "",
3546    value_name: None,
3547    file_path: Some("/etc/pam.d"),
3548    scope: DataScope::System,
3549    os_scope: OsScope::Linux,
3550    decoder: Decoder::Identity,
3551    meaning: "PAM module configs per service; malicious module intercepts and logs all passwords",
3552    mitre_techniques: &["T1556.003"],
3553    fields: DIR_ENTRY_FIELDS,
3554    retention: None,
3555    triage_priority: TriagePriority::High,
3556    related_artifacts: &[],
3557    sources: &["https://attack.mitre.org/techniques/T1556/003/"],
3558};
3559
3560/// `/etc/sudoers.d/` — drop-in sudoers rules (T1548.003).
3561///
3562/// `NOPASSWD` entries allow sudo without password. Attackers add entries for
3563/// specific commands or ALL commands without password prompting.
3564pub static LINUX_SUDOERS_D: ArtifactDescriptor = ArtifactDescriptor {
3565    id: "linux_sudoers_d",
3566    name: "Sudoers Drop-ins (/etc/sudoers.d/)",
3567    artifact_type: ArtifactType::Directory,
3568    hive: None,
3569    key_path: "",
3570    value_name: None,
3571    file_path: Some("/etc/sudoers.d"),
3572    scope: DataScope::System,
3573    os_scope: OsScope::Linux,
3574    decoder: Decoder::Identity,
3575    meaning:
3576        "Drop-in sudoers rules; NOPASSWD entries enable privilege escalation without credentials",
3577    mitre_techniques: &["T1548.003"],
3578    fields: DIR_ENTRY_FIELDS,
3579    retention: None,
3580    triage_priority: TriagePriority::High,
3581    related_artifacts: &[],
3582    sources: &["https://attack.mitre.org/techniques/T1548/003/"],
3583};
3584
3585/// `/etc/modules-load.d/` — kernel modules loaded at boot (T1547.006).
3586///
3587/// Each `.conf` file lists module names to load. Attackers register a
3588/// rootkit or malicious kernel module here for persistent kernel-level access.
3589pub static LINUX_MODULES_LOAD_D: ArtifactDescriptor = ArtifactDescriptor {
3590    id: "linux_modules_load_d",
3591    name: "Kernel Module Load Config (/etc/modules-load.d/)",
3592    artifact_type: ArtifactType::Directory,
3593    hive: None,
3594    key_path: "",
3595    value_name: None,
3596    file_path: Some("/etc/modules-load.d"),
3597    scope: DataScope::System,
3598    os_scope: OsScope::LinuxSystemd,
3599    decoder: Decoder::Identity,
3600    meaning: "Kernel modules auto-loaded at boot; rootkit module here = persistent kernel access",
3601    mitre_techniques: &["T1547.006"],
3602    fields: DIR_ENTRY_FIELDS,
3603    retention: None,
3604    triage_priority: TriagePriority::Medium,
3605    related_artifacts: &[],
3606    sources: &["https://attack.mitre.org/techniques/T1547/006/"],
3607};
3608
3609/// `/etc/update-motd.d/` — dynamic MOTD scripts executed on login (Debian/Ubuntu).
3610///
3611/// Every script here runs as root at SSH login to generate the MOTD.
3612/// A persistent backdoor can be hidden here as it looks like a status script.
3613pub static LINUX_MOTD_D: ArtifactDescriptor = ArtifactDescriptor {
3614    id: "linux_motd_d",
3615    name: "Dynamic MOTD Scripts (/etc/update-motd.d/)",
3616    artifact_type: ArtifactType::Directory,
3617    hive: None,
3618    key_path: "",
3619    value_name: None,
3620    file_path: Some("/etc/update-motd.d"),
3621    scope: DataScope::System,
3622    os_scope: OsScope::LinuxDebian,
3623    decoder: Decoder::Identity,
3624    meaning: "Scripts run as root at SSH login for MOTD generation; covert execution vector",
3625    mitre_techniques: &["T1037.004"],
3626    fields: DIR_ENTRY_FIELDS,
3627    retention: None,
3628    triage_priority: TriagePriority::Medium,
3629    related_artifacts: &[],
3630    sources: &["https://attack.mitre.org/techniques/T1037/004/"],
3631};
3632
3633/// `/etc/udev/rules.d/` — udev device event rules (T1546).
3634///
3635/// Rules can execute commands when devices are connected. An attacker can
3636/// create a rule that runs a payload whenever a USB is inserted or a network
3637/// interface comes up.
3638pub static LINUX_UDEV_RULES_D: ArtifactDescriptor = ArtifactDescriptor {
3639    id: "linux_udev_rules_d",
3640    name: "udev Rules (/etc/udev/rules.d/)",
3641    artifact_type: ArtifactType::Directory,
3642    hive: None,
3643    key_path: "",
3644    value_name: None,
3645    file_path: Some("/etc/udev/rules.d"),
3646    scope: DataScope::System,
3647    os_scope: OsScope::LinuxSystemd,
3648    decoder: Decoder::Identity,
3649    meaning: "Device event rules; RUN+= directive executes payload on device attach/detach",
3650    mitre_techniques: &["T1546"],
3651    fields: DIR_ENTRY_FIELDS,
3652    retention: None,
3653    triage_priority: TriagePriority::Medium,
3654    related_artifacts: &[],
3655    sources: &["https://attack.mitre.org/techniques/T1546/"],
3656};
3657
3658// ── Linux execution evidence ──────────────────────────────────────────────
3659
3660/// `~/.bash_history` — Bash interactive command history (T1059.004).
3661///
3662/// Contains commands entered in interactive Bash sessions. Attackers often
3663/// clear this with `history -c` or `unset HISTFILE`. An absent or empty file
3664/// is itself suspicious.
3665pub static LINUX_BASH_HISTORY: ArtifactDescriptor = ArtifactDescriptor {
3666    id: "linux_bash_history",
3667    name: "Bash History (~/.bash_history)",
3668    artifact_type: ArtifactType::File,
3669    hive: None,
3670    key_path: "",
3671    value_name: None,
3672    file_path: Some("~/.bash_history"),
3673    scope: DataScope::User,
3674    os_scope: OsScope::Linux,
3675    decoder: Decoder::Identity,
3676    meaning:
3677        "Interactive Bash command history; reveals lateral movement, exfil, and recon commands",
3678    mitre_techniques: &["T1059.004", "T1552"],
3679    fields: CRON_LINE_FIELDS,
3680    retention: Some("HISTSIZE limit; default 500-2000 commands"),
3681    triage_priority: TriagePriority::High,
3682    related_artifacts: &[],
3683    sources: &[
3684        "https://attack.mitre.org/techniques/T1059/004/",
3685        "https://attack.mitre.org/techniques/T1552/",
3686    ],
3687};
3688
3689/// `~/.zsh_history` — Zsh interactive command history.
3690pub static LINUX_ZSH_HISTORY: ArtifactDescriptor = ArtifactDescriptor {
3691    id: "linux_zsh_history",
3692    name: "Zsh History (~/.zsh_history)",
3693    artifact_type: ArtifactType::File,
3694    hive: None,
3695    key_path: "",
3696    value_name: None,
3697    file_path: Some("~/.zsh_history"),
3698    scope: DataScope::User,
3699    os_scope: OsScope::Linux,
3700    decoder: Decoder::Identity,
3701    meaning: "Interactive Zsh command history; extended format optionally includes timestamps",
3702    mitre_techniques: &["T1059.004", "T1552"],
3703    fields: CRON_LINE_FIELDS,
3704    retention: Some("HISTSIZE limit; default 500-2000 commands"),
3705    triage_priority: TriagePriority::High,
3706    related_artifacts: &[],
3707    sources: &[
3708        "https://attack.mitre.org/techniques/T1059/004/",
3709        "https://attack.mitre.org/techniques/T1552/",
3710    ],
3711};
3712
3713/// `/var/log/wtmp` — binary successful login history (T1078).
3714///
3715/// Utmp-format binary file; `last` command reads it. Records login, logout,
3716/// reboot, and shutdown events. Tampered by log-clearing tools.
3717pub static LINUX_WTMP: ArtifactDescriptor = ArtifactDescriptor {
3718    id: "linux_wtmp",
3719    name: "Login History (/var/log/wtmp)",
3720    artifact_type: ArtifactType::File,
3721    hive: None,
3722    key_path: "",
3723    value_name: None,
3724    file_path: Some("/var/log/wtmp"),
3725    scope: DataScope::System,
3726    os_scope: OsScope::Linux,
3727    decoder: Decoder::Identity,
3728    meaning:
3729        "Binary record of all successful logins/logouts/reboots; evidence of valid-account abuse",
3730    mitre_techniques: &["T1078", "T1021.004"],
3731    fields: LOG_LINE_FIELDS,
3732    retention: Some("until rotated by logrotate"),
3733    triage_priority: TriagePriority::High,
3734    related_artifacts: &[],
3735    sources: &[
3736        "https://attack.mitre.org/techniques/T1078/",
3737        "https://attack.mitre.org/techniques/T1021/004/",
3738        "https://linux.die.net/man/5/wtmp",
3739        "https://www.sans.org/blog/linux-forensics-artifacts/",
3740        "https://bromiley.medium.com/torvalds-tuesday-logon-history-in-the-tmp-files-83530b2acc28",
3741        "https://sandflysecurity.com/blog/using-linux-utmpdump-for-forensics-and-detecting-log-file-tampering",
3742    ],
3743};
3744
3745/// `/var/log/btmp` — binary failed login attempts.
3746///
3747/// Utmp-format binary; `lastb` command reads it. Brute-force evidence.
3748pub static LINUX_BTMP: ArtifactDescriptor = ArtifactDescriptor {
3749    id: "linux_btmp",
3750    name: "Failed Login Attempts (/var/log/btmp)",
3751    artifact_type: ArtifactType::File,
3752    hive: None,
3753    key_path: "",
3754    value_name: None,
3755    file_path: Some("/var/log/btmp"),
3756    scope: DataScope::System,
3757    os_scope: OsScope::Linux,
3758    decoder: Decoder::Identity,
3759    meaning: "Binary record of failed authentication attempts; brute-force and credential-stuffing evidence",
3760    mitre_techniques: &["T1110"],
3761    fields: LOG_LINE_FIELDS,
3762    retention: Some("until rotated by logrotate"),
3763    triage_priority: TriagePriority::High,
3764    related_artifacts: &[],
3765    sources: &[
3766        "https://attack.mitre.org/techniques/T1110/",
3767        "https://linux.die.net/man/5/wtmp",
3768        "https://bromiley.medium.com/torvalds-tuesday-logon-history-in-the-tmp-files-83530b2acc28",
3769        "https://sandflysecurity.com/blog/using-linux-utmpdump-for-forensics-and-detecting-log-file-tampering",
3770    ],
3771};
3772
3773/// `/var/log/lastlog` — binary last-login-per-UID database.
3774///
3775/// Fixed-offset binary file indexed by UID. `lastlog` command reads it.
3776/// Each entry records last login time and source IP.
3777pub static LINUX_LASTLOG: ArtifactDescriptor = ArtifactDescriptor {
3778    id: "linux_lastlog",
3779    name: "Last Login Database (/var/log/lastlog)",
3780    artifact_type: ArtifactType::File,
3781    hive: None,
3782    key_path: "",
3783    value_name: None,
3784    file_path: Some("/var/log/lastlog"),
3785    scope: DataScope::System,
3786    os_scope: OsScope::Linux,
3787    decoder: Decoder::Identity,
3788    meaning: "Per-UID last-login record including source IP; never-logged-in vs recent entries",
3789    mitre_techniques: &["T1078"],
3790    fields: LOG_LINE_FIELDS,
3791    retention: None,
3792    triage_priority: TriagePriority::High,
3793    related_artifacts: &[],
3794    sources: &["https://attack.mitre.org/techniques/T1078/"],
3795};
3796
3797/// `/var/log/auth.log` — authentication and sudo event log (Debian/Ubuntu).
3798///
3799/// Contains PAM authentication events, sudo commands, SSH logins, and su usage.
3800/// Red Hat equivalent: `/var/log/secure`.
3801pub static LINUX_AUTH_LOG: ArtifactDescriptor = ArtifactDescriptor {
3802    id: "linux_auth_log",
3803    name: "Auth Log (/var/log/auth.log)",
3804    artifact_type: ArtifactType::File,
3805    hive: None,
3806    key_path: "",
3807    value_name: None,
3808    file_path: Some("/var/log/auth.log"),
3809    scope: DataScope::System,
3810    os_scope: OsScope::LinuxDebian,
3811    decoder: Decoder::Identity,
3812    meaning: "PAM auth events, SSH logins, sudo commands, su usage; primary lateral-movement log",
3813    mitre_techniques: &["T1078", "T1548.003"],
3814    fields: LOG_LINE_FIELDS,
3815    retention: Some("until rotated by logrotate"),
3816    triage_priority: TriagePriority::High,
3817    related_artifacts: &[],
3818    sources: &[
3819        "https://attack.mitre.org/techniques/T1078/",
3820        "https://attack.mitre.org/techniques/T1548/003/",
3821    ],
3822};
3823
3824/// systemd journal directory `/var/log/journal/`.
3825///
3826/// Binary journal files; `journalctl` reads them. Contains all system and
3827/// service log messages. More tamper-resistant than syslog text files.
3828pub static LINUX_JOURNAL_DIR: ArtifactDescriptor = ArtifactDescriptor {
3829    id: "linux_journal_dir",
3830    name: "systemd Journal (/var/log/journal/)",
3831    artifact_type: ArtifactType::Directory,
3832    hive: None,
3833    key_path: "",
3834    value_name: None,
3835    file_path: Some("/var/log/journal"),
3836    scope: DataScope::System,
3837    os_scope: OsScope::LinuxSystemd,
3838    decoder: Decoder::Identity,
3839    meaning:
3840        "Structured binary system journal; includes boot IDs, service crashes, and audit events",
3841    mitre_techniques: &["T1078", "T1059.004"],
3842    fields: DIR_ENTRY_FIELDS,
3843    retention: Some("50MB or 1 month default; configurable in journald.conf"),
3844    triage_priority: TriagePriority::High,
3845    related_artifacts: &[],
3846    sources: &[
3847        "https://attack.mitre.org/techniques/T1078/",
3848        "https://attack.mitre.org/techniques/T1059/004/",
3849    ],
3850};
3851
3852// ── Linux credential artifacts ────────────────────────────────────────────
3853
3854/// `/etc/passwd` — local user account database (T1087.001).
3855///
3856/// World-readable; fields: `user:x:uid:gid:gecos:home:shell`.
3857/// UID=0 duplicates, unusual shells (`/bin/bash` for service accounts),
3858/// and accounts with homedir `/` are suspicious.
3859pub static LINUX_PASSWD: ArtifactDescriptor = ArtifactDescriptor {
3860    id: "linux_passwd",
3861    name: "User Account Database (/etc/passwd)",
3862    artifact_type: ArtifactType::File,
3863    hive: None,
3864    key_path: "",
3865    value_name: None,
3866    file_path: Some("/etc/passwd"),
3867    scope: DataScope::System,
3868    os_scope: OsScope::Linux,
3869    decoder: Decoder::Identity,
3870    meaning:
3871        "Local user enumeration; UID=0 duplicates or unusual shells indicate backdoor accounts",
3872    mitre_techniques: &["T1087.001", "T1136.001"],
3873    fields: ACCOUNT_FIELDS,
3874    retention: None,
3875    triage_priority: TriagePriority::Critical,
3876    related_artifacts: &[],
3877    sources: &[
3878        "https://attack.mitre.org/techniques/T1087/001/",
3879        "https://attack.mitre.org/techniques/T1136/001/",
3880        "https://linux.die.net/man/5/passwd",
3881        "https://bromiley.medium.com/torvalds-tuesday-user-accounts-597b4ca9dcaf",
3882    ],
3883};
3884
3885/// `/etc/shadow` — password hash database (T1003.008).
3886///
3887/// Root-readable only. Hash formats: `$1$`=MD5, `$5$`=SHA256, `$6$`=SHA512,
3888/// `$y$`=yescrypt (modern). `*` or `!` prefix = locked account.
3889pub static LINUX_SHADOW: ArtifactDescriptor = ArtifactDescriptor {
3890    id: "linux_shadow",
3891    name: "Shadow Password File (/etc/shadow)",
3892    artifact_type: ArtifactType::File,
3893    hive: None,
3894    key_path: "",
3895    value_name: None,
3896    file_path: Some("/etc/shadow"),
3897    scope: DataScope::System,
3898    os_scope: OsScope::Linux,
3899    decoder: Decoder::Identity,
3900    meaning: "Password hashes for all local accounts; crackable offline once read",
3901    mitre_techniques: &["T1003.008"],
3902    fields: ACCOUNT_FIELDS,
3903    retention: None,
3904    triage_priority: TriagePriority::Critical,
3905    related_artifacts: &[],
3906    sources: &[
3907        "https://attack.mitre.org/techniques/T1003/008/",
3908        "https://www.sans.org/blog/linux-password-security/",
3909        "https://bromiley.medium.com/torvalds-tuesday-user-accounts-597b4ca9dcaf",
3910    ],
3911};
3912
3913/// SSH private key files — stolen keys enable impersonation (T1552.004).
3914///
3915/// Unencrypted keys (no `Proc-Type: ENCRYPTED` header) are immediately usable.
3916/// Encrypted keys require the passphrase but are still high-value targets.
3917pub static LINUX_SSH_PRIVATE_KEY: ArtifactDescriptor = ArtifactDescriptor {
3918    id: "linux_ssh_private_key",
3919    name: "SSH Private Keys (~/.ssh/id_*)",
3920    artifact_type: ArtifactType::File,
3921    hive: None,
3922    key_path: "",
3923    value_name: None,
3924    file_path: Some("~/.ssh/id_*"),
3925    scope: DataScope::User,
3926    os_scope: OsScope::Linux,
3927    decoder: Decoder::Identity,
3928    meaning:
3929        "Private key material for SSH authentication; unencrypted keys = immediate lateral movement",
3930    mitre_techniques: &["T1552.004"],
3931    fields: SSH_KEY_FIELDS,
3932    retention: None,
3933    triage_priority: TriagePriority::Critical,
3934    related_artifacts: &[],
3935    sources: &["https://attack.mitre.org/techniques/T1552/004/"],
3936};
3937
3938/// `~/.ssh/known_hosts` — previously connected SSH server fingerprints (T1021.004).
3939///
3940/// Records host key fingerprints of servers the user has connected to.
3941/// Reveals lateral movement destinations and external access patterns.
3942pub static LINUX_SSH_KNOWN_HOSTS: ArtifactDescriptor = ArtifactDescriptor {
3943    id: "linux_ssh_known_hosts",
3944    name: "SSH Known Hosts (~/.ssh/known_hosts)",
3945    artifact_type: ArtifactType::File,
3946    hive: None,
3947    key_path: "",
3948    value_name: None,
3949    file_path: Some("~/.ssh/known_hosts"),
3950    scope: DataScope::User,
3951    os_scope: OsScope::Linux,
3952    decoder: Decoder::Identity,
3953    meaning: "Previously-connected SSH server fingerprints; lateral movement destination history",
3954    mitre_techniques: &["T1021.004", "T1083"],
3955    fields: SSH_KEY_FIELDS,
3956    retention: None,
3957    triage_priority: TriagePriority::Medium,
3958    related_artifacts: &[],
3959    sources: &[
3960        "https://attack.mitre.org/techniques/T1021/004/",
3961        "https://attack.mitre.org/techniques/T1083/",
3962    ],
3963};
3964
3965/// `~/.gnupg/private-keys-v1.d/` — GnuPG private key store (T1552.004).
3966///
3967/// Modern GnuPG (2.1+) stores one `.key` file per secret key.
3968/// Exporting these enables code-signing forgery and decryption of PGP messages.
3969pub static LINUX_GNUPG_PRIVATE: ArtifactDescriptor = ArtifactDescriptor {
3970    id: "linux_gnupg_private",
3971    name: "GnuPG Private Key Store (~/.gnupg/)",
3972    artifact_type: ArtifactType::Directory,
3973    hive: None,
3974    key_path: "",
3975    value_name: None,
3976    file_path: Some("~/.gnupg"),
3977    scope: DataScope::User,
3978    os_scope: OsScope::Linux,
3979    decoder: Decoder::Identity,
3980    meaning: "GnuPG private keys; enables message decryption and code-signing forgery",
3981    mitre_techniques: &["T1552.004"],
3982    fields: DPAPI_FIELDS,
3983    retention: None,
3984    triage_priority: TriagePriority::High,
3985    related_artifacts: &[],
3986    sources: &["https://attack.mitre.org/techniques/T1552/004/"],
3987};
3988
3989/// `~/.aws/credentials` — AWS access key material (T1552.001).
3990///
3991/// INI-format file with `aws_access_key_id` and `aws_secret_access_key`.
3992/// May also contain `aws_session_token` for temporary credentials.
3993pub static LINUX_AWS_CREDENTIALS: ArtifactDescriptor = ArtifactDescriptor {
3994    id: "linux_aws_credentials",
3995    name: "AWS Credentials (~/.aws/credentials)",
3996    artifact_type: ArtifactType::File,
3997    hive: None,
3998    key_path: "",
3999    value_name: None,
4000    file_path: Some("~/.aws/credentials"),
4001    scope: DataScope::User,
4002    os_scope: OsScope::Linux,
4003    decoder: Decoder::Identity,
4004    meaning: "AWS long-term or temporary credentials; enables cloud infrastructure compromise",
4005    mitre_techniques: &["T1552.001"],
4006    fields: FILE_PATH_FIELDS,
4007    retention: None,
4008    triage_priority: TriagePriority::High,
4009    related_artifacts: &[],
4010    sources: &["https://attack.mitre.org/techniques/T1552/001/"],
4011};
4012
4013/// `~/.docker/config.json` — Docker registry auth tokens (T1552.001).
4014///
4015/// Contains base64-encoded `auth` tokens or `credsStore` references for
4016/// container registries. Grants push/pull access to private registries.
4017pub static LINUX_DOCKER_CONFIG: ArtifactDescriptor = ArtifactDescriptor {
4018    id: "linux_docker_config",
4019    name: "Docker Config (~/.docker/config.json)",
4020    artifact_type: ArtifactType::File,
4021    hive: None,
4022    key_path: "",
4023    value_name: None,
4024    file_path: Some("~/.docker/config.json"),
4025    scope: DataScope::User,
4026    os_scope: OsScope::Linux,
4027    decoder: Decoder::Identity,
4028    meaning: "Docker registry credentials; enables container image exfil or malicious image push",
4029    mitre_techniques: &["T1552.001"],
4030    fields: FILE_PATH_FIELDS,
4031    retention: None,
4032    triage_priority: TriagePriority::High,
4033    related_artifacts: &[],
4034    sources: &["https://attack.mitre.org/techniques/T1552/001/"],
4035};
4036
4037// ── Batch E — Windows execution / persistence / credential ───────────────────
4038
4039// ── Execution evidence ────────────────────────────────────────────────────────
4040
4041pub static LNK_FILES: ArtifactDescriptor = ArtifactDescriptor {
4042    id: "lnk_files",
4043    name: "LNK / Shell Link Recent Files",
4044    artifact_type: ArtifactType::Directory,
4045    hive: None,
4046    key_path: "",
4047    value_name: None,
4048    file_path: Some(r"%APPDATA%\Microsoft\Windows\Recent\"),
4049    scope: DataScope::User,
4050    os_scope: OsScope::Win7Plus,
4051    decoder: Decoder::Identity,
4052    meaning: "Shell Link (.lnk) files record target path, MAC timestamps, volume serial, and \
4053              NetBIOS host — evidence of file access even after target deletion. T1547.009.",
4054    mitre_techniques: &["T1547.009", "T1070.004"],
4055    fields: DIR_ENTRY_FIELDS,
4056    retention: None,
4057    triage_priority: TriagePriority::High,
4058    related_artifacts: &["jump_list_auto", "mru_recent_docs"],
4059    sources: &[
4060        "https://attack.mitre.org/techniques/T1547/009/",
4061        "https://attack.mitre.org/techniques/T1070/004/",
4062    ],
4063};
4064
4065pub static JUMP_LIST_AUTO: ArtifactDescriptor = ArtifactDescriptor {
4066    id: "jump_list_auto",
4067    name: "Jump Lists — AutomaticDestinations",
4068    artifact_type: ArtifactType::Directory,
4069    hive: None,
4070    key_path: "",
4071    value_name: None,
4072    file_path: Some(r"%APPDATA%\Microsoft\Windows\Recent\AutomaticDestinations\"),
4073    scope: DataScope::User,
4074    os_scope: OsScope::Win7Plus,
4075    decoder: Decoder::Identity,
4076    meaning: "OLE Compound Document storing per-AppID MRU lists; reveals recently opened files \
4077              for each application including timestamps and target metadata.",
4078    mitre_techniques: &["T1547.009", "T1070.004"],
4079    fields: DIR_ENTRY_FIELDS,
4080    retention: None,
4081    triage_priority: TriagePriority::High,
4082    related_artifacts: &["lnk_files", "mru_recent_docs"],
4083    sources: &[
4084        "https://attack.mitre.org/techniques/T1547/009/",
4085        "https://attack.mitre.org/techniques/T1070/004/",
4086    ],
4087};
4088
4089pub static JUMP_LIST_CUSTOM: ArtifactDescriptor = ArtifactDescriptor {
4090    id: "jump_list_custom",
4091    name: "Jump Lists — CustomDestinations",
4092    artifact_type: ArtifactType::Directory,
4093    hive: None,
4094    key_path: "",
4095    value_name: None,
4096    file_path: Some(r"%APPDATA%\Microsoft\Windows\Recent\CustomDestinations\"),
4097    scope: DataScope::User,
4098    os_scope: OsScope::Win7Plus,
4099    decoder: Decoder::Identity,
4100    meaning: "Application-pinned and custom jump list entries; may persist after file deletion, \
4101              revealing attacker-pinned tools or exfiltrated document access.",
4102    mitre_techniques: &["T1547.009", "T1070.004"],
4103    fields: DIR_ENTRY_FIELDS,
4104    retention: None,
4105    triage_priority: TriagePriority::High,
4106    related_artifacts: &["lnk_files", "jump_list_auto"],
4107    sources: &[
4108        "https://attack.mitre.org/techniques/T1547/009/",
4109        "https://attack.mitre.org/techniques/T1070/004/",
4110    ],
4111};
4112
4113pub static EVTX_DIR: ArtifactDescriptor = ArtifactDescriptor {
4114    id: "evtx_dir",
4115    name: "Windows Event Log Directory (EVTX)",
4116    artifact_type: ArtifactType::Directory,
4117    hive: None,
4118    key_path: "",
4119    value_name: None,
4120    file_path: Some(r"C:\Windows\System32\winevt\Logs\"),
4121    scope: DataScope::System,
4122    os_scope: OsScope::Win7Plus,
4123    decoder: Decoder::Identity,
4124    meaning: "Binary EVTX log files — Security.evtx (4624/4625/4688), System.evtx, \
4125              PowerShell/Operational.evtx. Primary execution, logon, and process-creation record.",
4126    mitre_techniques: &["T1070.001", "T1059.001"],
4127    fields: DIR_ENTRY_FIELDS,
4128    retention: Some("configurable; default ~20MB rolling per channel"),
4129    triage_priority: TriagePriority::Medium,
4130    related_artifacts: &[],
4131    sources: &[
4132        "https://attack.mitre.org/techniques/T1070/001/",
4133        "https://attack.mitre.org/techniques/T1059/001/",
4134    ],
4135};
4136
4137pub static USN_JOURNAL: ArtifactDescriptor = ArtifactDescriptor {
4138    id: "usn_journal",
4139    name: "USN Journal ($UsnJrnl:$J)",
4140    artifact_type: ArtifactType::File,
4141    hive: None,
4142    key_path: "",
4143    value_name: None,
4144    file_path: Some(r"\\.\C:\$Extend\$UsnJrnl:$J"),
4145    scope: DataScope::System,
4146    os_scope: OsScope::Win7Plus,
4147    decoder: Decoder::Identity,
4148    meaning: "NTFS change journal records file create/delete/rename operations with USN sequence \
4149              number; persists even after file deletion, proving prior file existence.",
4150    mitre_techniques: &["T1070.004", "T1059"],
4151    fields: DIR_ENTRY_FIELDS,
4152    retention: None,
4153    triage_priority: TriagePriority::Medium,
4154    related_artifacts: &[],
4155    sources: &[
4156        "https://attack.mitre.org/techniques/T1070/004/",
4157        "https://attack.mitre.org/techniques/T1059/",
4158    ],
4159};
4160
4161// ── Persistence ───────────────────────────────────────────────────────────────
4162
4163pub static WMI_MOF_DIR: ArtifactDescriptor = ArtifactDescriptor {
4164    id: "wmi_mof_dir",
4165    name: "WMI MOF Subscription Repository",
4166    artifact_type: ArtifactType::Directory,
4167    hive: None,
4168    key_path: "",
4169    value_name: None,
4170    file_path: Some(r"C:\Windows\System32\wbem\Repository\"),
4171    scope: DataScope::System,
4172    os_scope: OsScope::Win7Plus,
4173    decoder: Decoder::Identity,
4174    meaning: "WMI CIM repository stores EventFilter, EventConsumer, and FilterToConsumerBinding \
4175              objects; persistence survives reboots and is invisible to registry-only tools.",
4176    mitre_techniques: &["T1546.003"],
4177    fields: DIR_ENTRY_FIELDS,
4178    retention: None,
4179    triage_priority: TriagePriority::High,
4180    related_artifacts: &[],
4181    sources: &["https://attack.mitre.org/techniques/T1546/003/"],
4182};
4183
4184pub static BITS_DB: ArtifactDescriptor = ArtifactDescriptor {
4185    id: "bits_db",
4186    name: "BITS Job Queue Database",
4187    artifact_type: ArtifactType::Directory,
4188    hive: None,
4189    key_path: "",
4190    value_name: None,
4191    file_path: Some(r"C:\ProgramData\Microsoft\Network\Downloader\"),
4192    scope: DataScope::System,
4193    os_scope: OsScope::Win7Plus,
4194    decoder: Decoder::Identity,
4195    meaning: "Background Intelligent Transfer Service queue DB (qmgr0.dat); records download \
4196              jobs including URL, destination, and command-to-notify — abused for stealthy malware staging.",
4197    mitre_techniques: &["T1197"],
4198    fields: DIR_ENTRY_FIELDS,
4199    retention: None,
4200    triage_priority: TriagePriority::High,
4201    related_artifacts: &[],
4202    sources: &[
4203        "https://attack.mitre.org/techniques/T1197/",
4204    ],
4205};
4206
4207static WMI_SUB_FIELDS: &[FieldSchema] = &[
4208    FieldSchema {
4209        name: "filter_name",
4210        description: "WMI EventFilter name",
4211        value_type: ValueType::Text,
4212        is_uid_component: true,
4213    },
4214    FieldSchema {
4215        name: "consumer_type",
4216        description: "Consumer type (Script/CommandLine)",
4217        value_type: ValueType::Text,
4218        is_uid_component: false,
4219    },
4220    FieldSchema {
4221        name: "consumer_value",
4222        description: "Script or command executed on trigger",
4223        value_type: ValueType::Text,
4224        is_uid_component: false,
4225    },
4226    FieldSchema {
4227        name: "query",
4228        description: "WQL query that triggers the subscription",
4229        value_type: ValueType::Text,
4230        is_uid_component: false,
4231    },
4232];
4233
4234pub static WMI_SUBSCRIPTIONS: ArtifactDescriptor = ArtifactDescriptor {
4235    id: "wmi_subscriptions",
4236    name: "WMI Event Subscriptions (Registry)",
4237    artifact_type: ArtifactType::RegistryKey,
4238    hive: Some(HiveTarget::HklmSoftware),
4239    key_path: r"Microsoft\WBEM\ESS\//./root/subscription",
4240    value_name: None,
4241    file_path: None,
4242    scope: DataScope::System,
4243    os_scope: OsScope::Win7Plus,
4244    decoder: Decoder::MultiSz,
4245    meaning: "Registry-side index of WMI subscriptions; cross-reference with MOF repository for \
4246              complete picture of WMI-based persistence.",
4247    mitre_techniques: &["T1546.003"],
4248    fields: WMI_SUB_FIELDS,
4249    retention: None,
4250    triage_priority: TriagePriority::High,
4251    related_artifacts: &[],
4252    sources: &["https://attack.mitre.org/techniques/T1546/003/"],
4253};
4254
4255pub static LOGON_SCRIPTS: ArtifactDescriptor = ArtifactDescriptor {
4256    id: "logon_scripts",
4257    name: "Logon Scripts (UserInitMprLogonScript)",
4258    artifact_type: ArtifactType::RegistryValue,
4259    hive: Some(HiveTarget::NtUser),
4260    key_path: r"Environment",
4261    value_name: Some("UserInitMprLogonScript"),
4262    file_path: None,
4263    scope: DataScope::User,
4264    os_scope: OsScope::Win7Plus,
4265    decoder: Decoder::Identity,
4266    meaning: "Script executed at logon via WinLogon; per-user value allowing unprivileged \
4267              persistence that survives password resets.",
4268    mitre_techniques: &["T1037.001"],
4269    fields: PERSIST_CMD_FIELDS,
4270    retention: None,
4271    triage_priority: TriagePriority::Medium,
4272    related_artifacts: &[],
4273    sources: &["https://attack.mitre.org/techniques/T1037/001/"],
4274};
4275
4276pub static WINSOCK_LSP: ArtifactDescriptor = ArtifactDescriptor {
4277    id: "winsock_lsp",
4278    name: "Winsock Layered Service Provider",
4279    artifact_type: ArtifactType::RegistryKey,
4280    hive: Some(HiveTarget::HklmSystem),
4281    key_path: r"CurrentControlSet\Services\WinSock2\Parameters\Protocol_Catalog9",
4282    value_name: None,
4283    file_path: None,
4284    scope: DataScope::System,
4285    os_scope: OsScope::Win7Plus,
4286    decoder: Decoder::Identity,
4287    meaning: "LSP DLLs intercept all Winsock traffic; malicious LSPs can log credentials from \
4288              plaintext protocols. Rare but high-signal indicator of network interception.",
4289    mitre_techniques: &["T1547.010"],
4290    fields: DLL_FIELDS,
4291    retention: None,
4292    triage_priority: TriagePriority::Medium,
4293    related_artifacts: &[],
4294    sources: &["https://attack.mitre.org/techniques/T1547/010/"],
4295};
4296
4297pub static APPSHIM_DB: ArtifactDescriptor = ArtifactDescriptor {
4298    id: "appshim_db",
4299    name: "Application Shim Database",
4300    artifact_type: ArtifactType::Directory,
4301    hive: None,
4302    key_path: "",
4303    value_name: None,
4304    file_path: Some(r"C:\Windows\apppatch\Custom\"),
4305    scope: DataScope::System,
4306    os_scope: OsScope::Win7Plus,
4307    decoder: Decoder::Identity,
4308    meaning: "Custom SDB shim databases; attackers inject shims to redirect API calls, \
4309              disable security checks, or load malicious DLLs without modifying the target binary.",
4310    mitre_techniques: &["T1546.011"],
4311    fields: DIR_ENTRY_FIELDS,
4312    retention: None,
4313    triage_priority: TriagePriority::Medium,
4314    related_artifacts: &[],
4315    sources: &["https://attack.mitre.org/techniques/T1546/011/"],
4316};
4317
4318pub static PASSWORD_FILTER_DLL: ArtifactDescriptor = ArtifactDescriptor {
4319    id: "password_filter_dll",
4320    name: "Password Filter DLL (Notification Packages)",
4321    artifact_type: ArtifactType::RegistryValue,
4322    hive: Some(HiveTarget::HklmSystem),
4323    key_path: r"CurrentControlSet\Control\Lsa",
4324    value_name: Some("Notification Packages"),
4325    file_path: None,
4326    scope: DataScope::System,
4327    os_scope: OsScope::Win7Plus,
4328    decoder: Decoder::MultiSz,
4329    meaning: "DLLs registered here receive cleartext passwords during every password change; \
4330              malicious filter captures and exfiltrates credentials.",
4331    mitre_techniques: &["T1556.002"],
4332    fields: DLL_FIELDS,
4333    retention: None,
4334    triage_priority: TriagePriority::Medium,
4335    related_artifacts: &[],
4336    sources: &["https://attack.mitre.org/techniques/T1556/002/"],
4337};
4338
4339pub static OFFICE_NORMAL_DOTM: ArtifactDescriptor = ArtifactDescriptor {
4340    id: "office_normal_dotm",
4341    name: "Office Normal Template (Normal.dotm)",
4342    artifact_type: ArtifactType::File,
4343    hive: None,
4344    key_path: "",
4345    value_name: None,
4346    file_path: Some(r"%APPDATA%\Microsoft\Templates\Normal.dotm"),
4347    scope: DataScope::User,
4348    os_scope: OsScope::Win7Plus,
4349    decoder: Decoder::Identity,
4350    meaning: "Global Word template auto-loaded on every document open; malicious macros \
4351              embedded here achieve persistence across all Word sessions.",
4352    mitre_techniques: &["T1137.001"],
4353    fields: FILE_PATH_FIELDS,
4354    retention: None,
4355    triage_priority: TriagePriority::High,
4356    related_artifacts: &[],
4357    sources: &["https://attack.mitre.org/techniques/T1137/001/"],
4358};
4359
4360pub static POWERSHELL_PROFILE_ALL: ArtifactDescriptor = ArtifactDescriptor {
4361    id: "powershell_profile_all",
4362    name: "PowerShell All-Users Profile (profile.ps1)",
4363    artifact_type: ArtifactType::File,
4364    hive: None,
4365    key_path: "",
4366    value_name: None,
4367    file_path: Some(r"C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1"),
4368    scope: DataScope::System,
4369    os_scope: OsScope::Win7Plus,
4370    decoder: Decoder::Identity,
4371    meaning: "System-wide PowerShell profile executed for every user on every PS session start; \
4372              SYSTEM-writable, provides privileged persistence without registry modification.",
4373    mitre_techniques: &["T1546.013"],
4374    fields: PERSIST_CMD_FIELDS,
4375    retention: None,
4376    triage_priority: TriagePriority::High,
4377    related_artifacts: &[],
4378    sources: &["https://attack.mitre.org/techniques/T1546/013/"],
4379};
4380
4381// ── Credentials ───────────────────────────────────────────────────────────────
4382
4383pub static DPAPI_SYSTEM_MASTERKEY: ArtifactDescriptor = ArtifactDescriptor {
4384    id: "dpapi_system_masterkey",
4385    name: "DPAPI System Master Key",
4386    artifact_type: ArtifactType::Directory,
4387    hive: None,
4388    key_path: "",
4389    value_name: None,
4390    file_path: Some(r"C:\Windows\System32\Microsoft\Protect\S-1-5-18\User\"),
4391    scope: DataScope::System,
4392    os_scope: OsScope::Win7Plus,
4393    decoder: Decoder::Identity,
4394    meaning: "DPAPI master keys for the SYSTEM account; used to decrypt SYSTEM-scope secrets \
4395              such as LSA secrets, service credentials, and scheduled task credentials.",
4396    mitre_techniques: &["T1555.004"],
4397    fields: FILE_PATH_FIELDS,
4398    retention: None,
4399    triage_priority: TriagePriority::Critical,
4400    related_artifacts: &["lsa_secrets", "dpapi_masterkey_user"],
4401    sources: &["https://attack.mitre.org/techniques/T1555/004/"],
4402};
4403
4404pub static DPAPI_CREDHIST: ArtifactDescriptor = ArtifactDescriptor {
4405    id: "dpapi_credhist",
4406    name: "DPAPI CREDHIST File",
4407    artifact_type: ArtifactType::File,
4408    hive: None,
4409    key_path: "",
4410    value_name: None,
4411    file_path: Some(r"%APPDATA%\Microsoft\Protect\CREDHIST"),
4412    scope: DataScope::User,
4413    os_scope: OsScope::Win7Plus,
4414    decoder: Decoder::Identity,
4415    meaning: "Chain of previous DPAPI master key derivation entries; enables decryption of \
4416              secrets encrypted with old passwords after a password change.",
4417    mitre_techniques: &["T1555.004"],
4418    fields: FILE_PATH_FIELDS,
4419    retention: None,
4420    triage_priority: TriagePriority::High,
4421    related_artifacts: &["dpapi_masterkey_user"],
4422    sources: &["https://attack.mitre.org/techniques/T1555/004/"],
4423};
4424
4425pub static CHROME_COOKIES: ArtifactDescriptor = ArtifactDescriptor {
4426    id: "chrome_cookies",
4427    name: "Chrome/Edge Cookies (SQLite)",
4428    artifact_type: ArtifactType::File,
4429    hive: None,
4430    key_path: "",
4431    value_name: None,
4432    file_path: Some(r"%LOCALAPPDATA%\Google\Chrome\User Data\Default\Network\Cookies"),
4433    scope: DataScope::User,
4434    os_scope: OsScope::Win7Plus,
4435    decoder: Decoder::Identity,
4436    meaning: "SQLite database of browser session/authentication cookies; adversaries can replay \
4437              these to bypass MFA and impersonate authenticated sessions (pass-the-cookie).",
4438    mitre_techniques: &["T1539", "T1185"],
4439    fields: FILE_PATH_FIELDS,
4440    retention: None,
4441    triage_priority: TriagePriority::High,
4442    related_artifacts: &["chrome_login_data"],
4443    sources: &[
4444        "https://attack.mitre.org/techniques/T1539/",
4445        "https://attack.mitre.org/techniques/T1185/",
4446    ],
4447};
4448
4449pub static EDGE_WEBCACHE: ArtifactDescriptor = ArtifactDescriptor {
4450    id: "edge_webcache",
4451    name: "IE/Edge Legacy WebCacheV01.dat",
4452    artifact_type: ArtifactType::Directory,
4453    hive: None,
4454    key_path: "",
4455    value_name: None,
4456    file_path: Some(r"%LOCALAPPDATA%\Microsoft\Windows\INetCache\"),
4457    scope: DataScope::User,
4458    os_scope: OsScope::Win7Plus,
4459    decoder: Decoder::Identity,
4460    meaning: "ESE database recording all IE/Edge Legacy web history, downloads, and cached \
4461              content; reveals browsing patterns and potential data exfiltration URLs.",
4462    mitre_techniques: &["T1539", "T1217"],
4463    fields: FILE_PATH_FIELDS,
4464    retention: None,
4465    triage_priority: TriagePriority::Low,
4466    related_artifacts: &[],
4467    sources: &[
4468        "https://attack.mitre.org/techniques/T1539/",
4469        "https://attack.mitre.org/techniques/T1217/",
4470    ],
4471};
4472
4473pub static VPN_RAS_PHONEBOOK: ArtifactDescriptor = ArtifactDescriptor {
4474    id: "vpn_ras_phonebook",
4475    name: "VPN Credentials — RAS Phonebook",
4476    artifact_type: ArtifactType::File,
4477    hive: None,
4478    key_path: "",
4479    value_name: None,
4480    file_path: Some(r"%APPDATA%\Microsoft\Network\Connections\Pbk\rasphone.pbk"),
4481    scope: DataScope::User,
4482    os_scope: OsScope::Win7Plus,
4483    decoder: Decoder::Identity,
4484    meaning: "Plain-text INI phonebook storing VPN connection entries including server address \
4485              and saved credential references; reveals network pivoting paths.",
4486    mitre_techniques: &["T1552.001"],
4487    fields: FILE_PATH_FIELDS,
4488    retention: None,
4489    triage_priority: TriagePriority::Low,
4490    related_artifacts: &[],
4491    sources: &["https://attack.mitre.org/techniques/T1552/001/"],
4492};
4493
4494pub static WINDOWS_HELLO_NGC: ArtifactDescriptor = ArtifactDescriptor {
4495    id: "windows_hello_ngc",
4496    name: "Windows Hello / NGC Folder",
4497    artifact_type: ArtifactType::Directory,
4498    hive: None,
4499    key_path: "",
4500    value_name: None,
4501    file_path: Some(r"C:\Windows\ServiceProfiles\LocalService\AppData\Roaming\Microsoft\Ngc\"),
4502    scope: DataScope::System,
4503    os_scope: OsScope::Win10Plus,
4504    decoder: Decoder::Identity,
4505    meaning: "Stores Windows Hello credential provider keys (PIN protectors, biometric keys); \
4506              compromise reveals authentication material bypassing traditional password forensics.",
4507    mitre_techniques: &["T1555"],
4508    fields: FILE_PATH_FIELDS,
4509    retention: None,
4510    triage_priority: TriagePriority::High,
4511    related_artifacts: &[],
4512    sources: &["https://attack.mitre.org/techniques/T1555/"],
4513};
4514
4515pub static USER_CERT_PRIVATE_KEY: ArtifactDescriptor = ArtifactDescriptor {
4516    id: "user_cert_private_key",
4517    name: "User Certificate Private Keys",
4518    artifact_type: ArtifactType::Directory,
4519    hive: None,
4520    key_path: "",
4521    value_name: None,
4522    file_path: Some(r"%APPDATA%\Microsoft\SystemCertificates\My\"),
4523    scope: DataScope::User,
4524    os_scope: OsScope::Win7Plus,
4525    decoder: Decoder::Identity,
4526    meaning: "DPAPI-protected user certificate private keys for code signing, S/MIME, and \
4527              smart-card emulation; exfiltration enables impersonation and signing of malicious artifacts.",
4528    mitre_techniques: &["T1552.004"],
4529    fields: FILE_PATH_FIELDS,
4530    retention: None,
4531    triage_priority: TriagePriority::High,
4532    related_artifacts: &[],
4533    sources: &[
4534        "https://attack.mitre.org/techniques/T1552/004/",
4535    ],
4536};
4537
4538pub static MACHINE_CERT_STORE: ArtifactDescriptor = ArtifactDescriptor {
4539    id: "machine_cert_store",
4540    name: "Machine Certificate Private Keys",
4541    artifact_type: ArtifactType::Directory,
4542    hive: None,
4543    key_path: "",
4544    value_name: None,
4545    file_path: Some(r"C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\"),
4546    scope: DataScope::System,
4547    os_scope: OsScope::Win7Plus,
4548    decoder: Decoder::Identity,
4549    meaning: "Machine-scope RSA private keys protected by DPAPI SYSTEM; used for TLS mutual \
4550              auth, code signing, and IPSec — high-value credential exfiltration target.",
4551    mitre_techniques: &["T1552.004"],
4552    fields: FILE_PATH_FIELDS,
4553    retention: None,
4554    triage_priority: TriagePriority::High,
4555    related_artifacts: &[],
4556    sources: &["https://attack.mitre.org/techniques/T1552/004/"],
4557};
4558
4559// ── Batch F — Linux extended credentials / execution ─────────────────────────
4560
4561pub static LINUX_AT_QUEUE: ArtifactDescriptor = ArtifactDescriptor {
4562    id: "linux_at_queue",
4563    name: "AT Job Queue (/var/spool/at/)",
4564    artifact_type: ArtifactType::Directory,
4565    hive: None,
4566    key_path: "",
4567    value_name: None,
4568    file_path: Some("/var/spool/at/"),
4569    scope: DataScope::System,
4570    os_scope: OsScope::Linux,
4571    decoder: Decoder::Identity,
4572    meaning: "One-shot delayed execution jobs from the `at` command; each file contains a shell \
4573              script to run at a specified time, used for stealthy one-shot persistence.",
4574    mitre_techniques: &["T1053.001"],
4575    fields: CRON_LINE_FIELDS,
4576    retention: None,
4577    triage_priority: TriagePriority::Medium,
4578    related_artifacts: &[],
4579    sources: &["https://attack.mitre.org/techniques/T1053/001/"],
4580};
4581
4582pub static LINUX_SSHD_CONFIG: ArtifactDescriptor = ArtifactDescriptor {
4583    id: "linux_sshd_config",
4584    name: "SSH Daemon Configuration (/etc/ssh/sshd_config)",
4585    artifact_type: ArtifactType::File,
4586    hive: None,
4587    key_path: "",
4588    value_name: None,
4589    file_path: Some("/etc/ssh/sshd_config"),
4590    scope: DataScope::System,
4591    os_scope: OsScope::Linux,
4592    decoder: Decoder::Identity,
4593    meaning: "SSH server config; look for unauthorized AuthorizedKeysFile overrides, \
4594              ForceCommand bypass, PermitRootLogin yes, or AllowUsers modifications.",
4595    mitre_techniques: &["T1098.004", "T1021.004"],
4596    fields: PERSIST_CMD_FIELDS,
4597    retention: None,
4598    triage_priority: TriagePriority::Medium,
4599    related_artifacts: &[],
4600    sources: &[
4601        "https://attack.mitre.org/techniques/T1098/004/",
4602        "https://attack.mitre.org/techniques/T1021/004/",
4603    ],
4604};
4605
4606pub static LINUX_ETC_GROUP: ArtifactDescriptor = ArtifactDescriptor {
4607    id: "linux_etc_group",
4608    name: "Group Accounts (/etc/group)",
4609    artifact_type: ArtifactType::File,
4610    hive: None,
4611    key_path: "",
4612    value_name: None,
4613    file_path: Some("/etc/group"),
4614    scope: DataScope::System,
4615    os_scope: OsScope::Linux,
4616    decoder: Decoder::Identity,
4617    meaning: "Group membership database; cross-reference with /etc/passwd and sudo log to \
4618              detect unauthorized group additions (e.g., added to `sudo` or `docker` group).",
4619    mitre_techniques: &["T1087.001", "T1078.003"],
4620    fields: ACCOUNT_FIELDS,
4621    retention: None,
4622    triage_priority: TriagePriority::Medium,
4623    related_artifacts: &[],
4624    sources: &[
4625        "https://attack.mitre.org/techniques/T1087/001/",
4626        "https://attack.mitre.org/techniques/T1078/003/",
4627    ],
4628};
4629
4630pub static LINUX_GNOME_KEYRING: ArtifactDescriptor = ArtifactDescriptor {
4631    id: "linux_gnome_keyring",
4632    name: "GNOME Keyring (keyrings/)",
4633    artifact_type: ArtifactType::Directory,
4634    hive: None,
4635    key_path: "",
4636    value_name: None,
4637    file_path: Some("~/.local/share/keyrings/"),
4638    scope: DataScope::User,
4639    os_scope: OsScope::Linux,
4640    decoder: Decoder::Identity,
4641    meaning: "GNOME keyring stores WiFi PSK, SSH passphrases, web service passwords, and \
4642              browser master passwords encrypted with user login credential.",
4643    mitre_techniques: &["T1555.003"],
4644    fields: FILE_PATH_FIELDS,
4645    retention: None,
4646    triage_priority: TriagePriority::Critical,
4647    related_artifacts: &[],
4648    sources: &["https://attack.mitre.org/techniques/T1555/003/"],
4649};
4650
4651pub static LINUX_KDE_KWALLET: ArtifactDescriptor = ArtifactDescriptor {
4652    id: "linux_kde_kwallet",
4653    name: "KDE KWallet (kwalletd/)",
4654    artifact_type: ArtifactType::Directory,
4655    hive: None,
4656    key_path: "",
4657    value_name: None,
4658    file_path: Some("~/.local/share/kwalletd/"),
4659    scope: DataScope::User,
4660    os_scope: OsScope::Linux,
4661    decoder: Decoder::Identity,
4662    meaning: "KDE wallet encrypted credential store; stores passwords, SSH keys, and browser \
4663              credentials for KDE applications.",
4664    mitre_techniques: &["T1555.003"],
4665    fields: FILE_PATH_FIELDS,
4666    retention: None,
4667    triage_priority: TriagePriority::Critical,
4668    related_artifacts: &[],
4669    sources: &["https://attack.mitre.org/techniques/T1555/003/"],
4670};
4671
4672pub static LINUX_CHROME_LOGIN_LINUX: ArtifactDescriptor = ArtifactDescriptor {
4673    id: "linux_chrome_login_linux",
4674    name: "Chrome/Chromium Login Data (Linux)",
4675    artifact_type: ArtifactType::File,
4676    hive: None,
4677    key_path: "",
4678    value_name: None,
4679    file_path: Some("~/.config/google-chrome/Default/Login Data"),
4680    scope: DataScope::User,
4681    os_scope: OsScope::Linux,
4682    decoder: Decoder::Identity,
4683    meaning: "SQLite database of saved Chrome passwords on Linux; encryption key stored in \
4684              GNOME Keyring or plaintext depending on configuration.",
4685    mitre_techniques: &["T1555.003"],
4686    fields: FILE_PATH_FIELDS,
4687    retention: None,
4688    triage_priority: TriagePriority::Critical,
4689    related_artifacts: &[],
4690    sources: &["https://attack.mitre.org/techniques/T1555/003/"],
4691};
4692
4693pub static LINUX_FIREFOX_LOGINS_LINUX: ArtifactDescriptor = ArtifactDescriptor {
4694    id: "linux_firefox_logins_linux",
4695    name: "Firefox logins.json (Linux)",
4696    artifact_type: ArtifactType::File,
4697    hive: None,
4698    key_path: "",
4699    value_name: None,
4700    file_path: Some("~/.mozilla/firefox/"),
4701    scope: DataScope::User,
4702    os_scope: OsScope::Linux,
4703    decoder: Decoder::Identity,
4704    meaning:
4705        "JSON-encoded saved Firefox credentials protected by NSS (key4.db); \
4706              can be decrypted with master password or via memory forensics of the Firefox process.",
4707    mitre_techniques: &["T1555.003"],
4708    fields: FILE_PATH_FIELDS,
4709    retention: None,
4710    triage_priority: TriagePriority::Critical,
4711    related_artifacts: &[],
4712    sources: &["https://attack.mitre.org/techniques/T1555/003/"],
4713};
4714
4715pub static LINUX_UTMP: ArtifactDescriptor = ArtifactDescriptor {
4716    id: "linux_utmp",
4717    name: "Current Login Sessions (/run/utmp)",
4718    artifact_type: ArtifactType::File,
4719    hive: None,
4720    key_path: "",
4721    value_name: None,
4722    file_path: Some("/run/utmp"),
4723    scope: DataScope::System,
4724    os_scope: OsScope::Linux,
4725    decoder: Decoder::Identity,
4726    meaning: "Binary utmp records of currently logged-in users; cross-reference with wtmp \
4727              to detect sessions not present in persistent logs (anti-forensics via utmp wiper).",
4728    mitre_techniques: &["T1078"],
4729    fields: LOG_LINE_FIELDS,
4730    retention: None,
4731    triage_priority: TriagePriority::Medium,
4732    related_artifacts: &[],
4733    sources: &[
4734        "https://attack.mitre.org/techniques/T1078/",
4735        "https://linux.die.net/man/5/utmp",
4736        "https://www.sans.org/blog/linux-forensics-artifacts/",
4737        "https://bromiley.medium.com/torvalds-tuesday-logon-history-in-the-tmp-files-83530b2acc28",
4738        "https://sandflysecurity.com/blog/using-linux-utmpdump-for-forensics-and-detecting-log-file-tampering",
4739    ],
4740};
4741
4742pub static LINUX_GCP_CREDENTIALS: ArtifactDescriptor = ArtifactDescriptor {
4743    id: "linux_gcp_credentials",
4744    name: "GCP Application Default Credentials",
4745    artifact_type: ArtifactType::Directory,
4746    hive: None,
4747    key_path: "",
4748    value_name: None,
4749    file_path: Some("~/.config/gcloud/"),
4750    scope: DataScope::User,
4751    os_scope: OsScope::Linux,
4752    decoder: Decoder::Identity,
4753    meaning: "GCP access tokens and service account keys stored by gcloud CLI; \
4754              exfiltration enables cloud resource takeover without password.",
4755    mitre_techniques: &["T1552.001"],
4756    fields: FILE_PATH_FIELDS,
4757    retention: None,
4758    triage_priority: TriagePriority::High,
4759    related_artifacts: &[],
4760    sources: &["https://attack.mitre.org/techniques/T1552/001/"],
4761};
4762
4763pub static LINUX_AZURE_CREDENTIALS: ArtifactDescriptor = ArtifactDescriptor {
4764    id: "linux_azure_credentials",
4765    name: "Azure CLI Credentials (~/.azure/)",
4766    artifact_type: ArtifactType::Directory,
4767    hive: None,
4768    key_path: "",
4769    value_name: None,
4770    file_path: Some("~/.azure/"),
4771    scope: DataScope::User,
4772    os_scope: OsScope::Linux,
4773    decoder: Decoder::Identity,
4774    meaning: "Azure CLI access tokens and service principal credentials; \
4775              msal_token_cache.json contains active OAuth tokens enabling lateral movement in Azure.",
4776    mitre_techniques: &["T1552.001"],
4777    fields: FILE_PATH_FIELDS,
4778    retention: None,
4779    triage_priority: TriagePriority::High,
4780    related_artifacts: &[],
4781    sources: &[
4782        "https://attack.mitre.org/techniques/T1552/001/",
4783    ],
4784};
4785
4786pub static LINUX_KUBE_CONFIG: ArtifactDescriptor = ArtifactDescriptor {
4787    id: "linux_kube_config",
4788    name: "Kubernetes Config (~/.kube/config)",
4789    artifact_type: ArtifactType::File,
4790    hive: None,
4791    key_path: "",
4792    value_name: None,
4793    file_path: Some("~/.kube/config"),
4794    scope: DataScope::User,
4795    os_scope: OsScope::Linux,
4796    decoder: Decoder::Identity,
4797    meaning: "kubectl cluster credentials including bearer tokens, client certificates, \
4798              and cluster API endpoints; enables full cluster takeover if exfiltrated.",
4799    mitre_techniques: &["T1552.001"],
4800    fields: FILE_PATH_FIELDS,
4801    retention: None,
4802    triage_priority: TriagePriority::High,
4803    related_artifacts: &[],
4804    sources: &["https://attack.mitre.org/techniques/T1552/001/"],
4805};
4806
4807pub static LINUX_GIT_CREDENTIALS: ArtifactDescriptor = ArtifactDescriptor {
4808    id: "linux_git_credentials",
4809    name: "Git Credential Store (~/.git-credentials)",
4810    artifact_type: ArtifactType::File,
4811    hive: None,
4812    key_path: "",
4813    value_name: None,
4814    file_path: Some("~/.git-credentials"),
4815    scope: DataScope::User,
4816    os_scope: OsScope::Linux,
4817    decoder: Decoder::Identity,
4818    meaning: "Plaintext git credential store: URL + username + PAT/password per line; \
4819              personal access tokens here can access source repositories and CI/CD pipelines.",
4820    mitre_techniques: &["T1552.001"],
4821    fields: FILE_PATH_FIELDS,
4822    retention: None,
4823    triage_priority: TriagePriority::High,
4824    related_artifacts: &[],
4825    sources: &["https://attack.mitre.org/techniques/T1552/001/"],
4826};
4827
4828pub static LINUX_NETRC: ArtifactDescriptor = ArtifactDescriptor {
4829    id: "linux_netrc",
4830    name: "Netrc Credential File (~/.netrc)",
4831    artifact_type: ArtifactType::File,
4832    hive: None,
4833    key_path: "",
4834    value_name: None,
4835    file_path: Some("~/.netrc"),
4836    scope: DataScope::User,
4837    os_scope: OsScope::Linux,
4838    decoder: Decoder::Identity,
4839    meaning: "Auto-authentication file for ftp, curl, and legacy tools; stores plaintext \
4840              hostname/login/password triplets, often forgotten and highly sensitive.",
4841    mitre_techniques: &["T1552.001"],
4842    fields: FILE_PATH_FIELDS,
4843    retention: None,
4844    triage_priority: TriagePriority::Medium,
4845    related_artifacts: &[],
4846    sources: &["https://attack.mitre.org/techniques/T1552/001/"],
4847};
4848
4849// ── Batch G — LinuxPersist-sourced persistence artifacts ─────────────────────
4850// Source: https://github.com/GuyEldad/LinuxPersist
4851
4852pub static LINUX_ETC_ENVIRONMENT: ArtifactDescriptor = ArtifactDescriptor {
4853    id: "linux_etc_environment",
4854    name: "System Environment Variables (/etc/environment)",
4855    artifact_type: ArtifactType::File,
4856    hive: None,
4857    key_path: "",
4858    value_name: None,
4859    file_path: Some("/etc/environment"),
4860    scope: DataScope::System,
4861    os_scope: OsScope::Linux,
4862    decoder: Decoder::Identity,
4863    meaning:
4864        "System-wide environment variable definitions loaded for every login session and \
4865              PAM-based authentication. Attackers inject PATH hijacks or LD_PRELOAD values here \
4866              to redirect binary execution system-wide without modifying shell configuration files.",
4867    mitre_techniques: &["T1546.004"],
4868    fields: PERSIST_CMD_FIELDS,
4869    retention: None,
4870    triage_priority: TriagePriority::Medium,
4871    related_artifacts: &[],
4872    sources: &[
4873        "https://attack.mitre.org/techniques/T1546/004/",
4874        "https://linux.die.net/man/7/environ",
4875    ],
4876};
4877
4878pub static LINUX_XDG_AUTOSTART_USER: ArtifactDescriptor = ArtifactDescriptor {
4879    id: "linux_xdg_autostart_user",
4880    name: "XDG User Autostart (.desktop files)",
4881    artifact_type: ArtifactType::Directory,
4882    hive: None,
4883    key_path: "",
4884    value_name: None,
4885    file_path: Some("~/.config/autostart/"),
4886    scope: DataScope::User,
4887    os_scope: OsScope::Linux,
4888    decoder: Decoder::Identity,
4889    meaning: "Per-user XDG autostart .desktop files executed when a desktop session starts \
4890              (GNOME/KDE/XFCE). Exec= field runs arbitrary commands at GUI login without \
4891              root privileges — frequently overlooked by server-focused forensic checklists.",
4892    mitre_techniques: &["T1547.014"],
4893    fields: PERSIST_CMD_FIELDS,
4894    retention: None,
4895    triage_priority: TriagePriority::Medium,
4896    related_artifacts: &[],
4897    sources: &[
4898        "https://attack.mitre.org/techniques/T1547/014/",
4899        "https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html",
4900    ],
4901};
4902
4903pub static LINUX_XDG_AUTOSTART_SYSTEM: ArtifactDescriptor = ArtifactDescriptor {
4904    id: "linux_xdg_autostart_system",
4905    name: "XDG System Autostart (.desktop files)",
4906    artifact_type: ArtifactType::Directory,
4907    hive: None,
4908    key_path: "",
4909    value_name: None,
4910    file_path: Some("/etc/xdg/autostart/"),
4911    scope: DataScope::System,
4912    os_scope: OsScope::Linux,
4913    decoder: Decoder::Identity,
4914    meaning:
4915        "System-wide XDG autostart .desktop entries executed for all users at desktop session \
4916              start. Provides privileged persistence targeting all GUI logins on a workstation.",
4917    mitre_techniques: &["T1547.014"],
4918    fields: PERSIST_CMD_FIELDS,
4919    retention: None,
4920    triage_priority: TriagePriority::Medium,
4921    related_artifacts: &[],
4922    sources: &[
4923        "https://attack.mitre.org/techniques/T1547/014/",
4924        "https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html",
4925    ],
4926};
4927
4928pub static LINUX_NETWORKMANAGER_DISPATCHER: ArtifactDescriptor = ArtifactDescriptor {
4929    id: "linux_networkmanager_dispatcher",
4930    name: "NetworkManager Dispatcher Scripts",
4931    artifact_type: ArtifactType::Directory,
4932    hive: None,
4933    key_path: "",
4934    value_name: None,
4935    file_path: Some("/etc/NetworkManager/dispatcher.d/"),
4936    scope: DataScope::System,
4937    os_scope: OsScope::Linux,
4938    decoder: Decoder::Identity,
4939    meaning: "Scripts executed by NetworkManager when network interfaces change state (up/down). \
4940              Provides network-event-triggered persistence — scripts fire on VPN connect, \
4941              WiFi association, or interface cycling, making detection harder than at-boot persistence.",
4942    mitre_techniques: &["T1547.013"],
4943    fields: PERSIST_CMD_FIELDS,
4944    retention: None,
4945    triage_priority: TriagePriority::Medium,
4946    related_artifacts: &[],
4947    sources: &[
4948        "https://attack.mitre.org/techniques/T1547/013/",
4949        "https://networkmanager.dev/docs/api/latest/NetworkManager-dispatcher.html",
4950    ],
4951};
4952
4953pub static LINUX_APT_HOOKS: ArtifactDescriptor = ArtifactDescriptor {
4954    id: "linux_apt_hooks",
4955    name: "APT Package Manager Hook Scripts",
4956    artifact_type: ArtifactType::Directory,
4957    hive: None,
4958    key_path: "",
4959    value_name: None,
4960    file_path: Some("/etc/apt/apt.conf.d/"),
4961    scope: DataScope::System,
4962    os_scope: OsScope::LinuxDebian,
4963    decoder: Decoder::Identity,
4964    meaning: "APT configuration snippets that can define DPkg::Pre-Install-Pkgs, \
4965              DPkg::Post-Invoke, or APT::Update::Post-Invoke hooks; execute as root during \
4966              every package install or update — long-lived trigger-based privilege persistence.",
4967    mitre_techniques: &["T1546.004"],
4968    fields: PERSIST_CMD_FIELDS,
4969    retention: None,
4970    triage_priority: TriagePriority::Medium,
4971    related_artifacts: &[],
4972    sources: &[
4973        "https://attack.mitre.org/techniques/T1546/004/",
4974        "https://attack.mitre.org/techniques/T1546/016/",
4975        "https://wiki.debian.org/DpkgTriggers",
4976    ],
4977};
4978
4979// ── Batch H — Jump List / LNK / Prefetch / SRUM tables / EVTX channels ──────
4980
4981pub static JUMP_LIST_SYSTEM: ArtifactDescriptor = ArtifactDescriptor {
4982    id: "jump_list_system",
4983    name: "Jump Lists — System AutomaticDestinations",
4984    artifact_type: ArtifactType::Directory,
4985    hive: None,
4986    key_path: "",
4987    value_name: None,
4988    file_path: Some(r"C:\ProgramData\Microsoft\Windows\Recent\AutomaticDestinations\"),
4989    scope: DataScope::System,
4990    os_scope: OsScope::Win7Plus,
4991    decoder: Decoder::Identity,
4992    meaning: "System-scope jump lists shared across all users; distinct from per-user \
4993              %APPDATA% copies. Each .automaticDestinations-ms is an OLE CFB containing \
4994              a DestList stream (AppID → target MRU) plus embedded LNK blocks.",
4995    mitre_techniques: &["T1547.009", "T1070.004"],
4996    fields: DIR_ENTRY_FIELDS,
4997    retention: None,
4998    triage_priority: TriagePriority::High,
4999    related_artifacts: &["jump_list_auto", "jump_list_custom"],
5000    sources: &[
5001        "https://attack.mitre.org/techniques/T1547/009/",
5002        "https://attack.mitre.org/techniques/T1070/004/",
5003        "https://www.sans.org/blog/computer-forensics-windows-7-jump-lists/",
5004        "https://windowsir.blogspot.com/2011/05/jump-lists-in-win7.html",
5005        "https://github.com/EricZimmerman/JLECmd",
5006        "https://forensics.wiki/jump_lists/",
5007    ],
5008};
5009
5010pub static LNK_FILES_OFFICE: ArtifactDescriptor = ArtifactDescriptor {
5011    id: "lnk_files_office",
5012    name: "Office Recent LNK Files",
5013    artifact_type: ArtifactType::Directory,
5014    hive: None,
5015    key_path: "",
5016    value_name: None,
5017    file_path: Some(r"%APPDATA%\Microsoft\Office\Recent\"),
5018    scope: DataScope::User,
5019    os_scope: OsScope::Win7Plus,
5020    decoder: Decoder::Identity,
5021    meaning: "Office-specific shell link files created for every document opened via Office. \
5022              Separate from Windows Recent; survives clearing of Windows Recent Items. \
5023              Reveals document access including network shares and USB paths.",
5024    mitre_techniques: &["T1547.009", "T1070.004"],
5025    fields: DIR_ENTRY_FIELDS,
5026    retention: None,
5027    triage_priority: TriagePriority::High,
5028    related_artifacts: &["lnk_files", "mru_recent_docs"],
5029    sources: &[
5030        "https://attack.mitre.org/techniques/T1547/009/",
5031        "https://attack.mitre.org/techniques/T1070/004/",
5032        "https://www.sans.org/blog/lnk-files-analysis-in-windows/",
5033        "https://windowsir.blogspot.com/2009/01/lnk-files-are-your-friends.html",
5034        "https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-shllink/",
5035        "https://www.magnetforensics.com/blog/forensic-analysis-of-lnk-files/",
5036        "https://forensics.wiki/lnk/",
5037    ],
5038};
5039
5040static PREFETCH_FIELDS: &[FieldSchema] = &[
5041    FieldSchema {
5042        name: "executable_name",
5043        description: "Name of the prefetched executable (up to 29 chars)",
5044        value_type: ValueType::Text,
5045        is_uid_component: true,
5046    },
5047    FieldSchema {
5048        name: "run_count",
5049        description: "Number of times the executable has run",
5050        value_type: ValueType::UnsignedInt,
5051        is_uid_component: false,
5052    },
5053    FieldSchema {
5054        name: "last_run_time",
5055        description: "Most recent execution timestamp (FILETIME)",
5056        value_type: ValueType::Timestamp,
5057        is_uid_component: false,
5058    },
5059    FieldSchema {
5060        name: "previous_run_times",
5061        description: "Up to 7 prior execution timestamps (Win 8+)",
5062        value_type: ValueType::Text,
5063        is_uid_component: false,
5064    },
5065    FieldSchema {
5066        name: "volume_path",
5067        description: "Volume device path at time of execution",
5068        value_type: ValueType::Text,
5069        is_uid_component: false,
5070    },
5071    FieldSchema {
5072        name: "referenced_files",
5073        description: "DLLs and files loaded during first 10 seconds",
5074        value_type: ValueType::Text,
5075        is_uid_component: false,
5076    },
5077    FieldSchema {
5078        name: "prefetch_hash",
5079        description: "8-hex path hash appended to filename",
5080        value_type: ValueType::Text,
5081        is_uid_component: true,
5082    },
5083];
5084
5085pub static PREFETCH_FILE: ArtifactDescriptor = ArtifactDescriptor {
5086    id: "prefetch_file",
5087    name: "Prefetch File (.pf)",
5088    artifact_type: ArtifactType::File,
5089    hive: None,
5090    key_path: "",
5091    value_name: None,
5092    file_path: Some(r"C:\Windows\Prefetch\*.pf"),
5093    scope: DataScope::System,
5094    os_scope: OsScope::Win7Plus,
5095    decoder: Decoder::Identity,
5096    meaning: "Binary execution record: executable name, 8-run-timestamp history (Win8+), \
5097              run count, path hash, and referenced DLL list. Win10+ files are MAM-compressed \
5098              (4-byte magic 0x4D 0x41 0x4D 0x04) — decompress with xpress_huff before parsing. \
5099              Versions: v17 (XP), v23 (Vista/7), v26 (Win8), v30/v31 (Win10+).",
5100    mitre_techniques: &["T1059", "T1070.004"],
5101    fields: PREFETCH_FIELDS,
5102    retention: Some("128 entries; oldest evicted"),
5103    triage_priority: TriagePriority::High,
5104    related_artifacts: &[
5105        "shimcache",
5106        "amcache_app_file",
5107        "evtx_security",
5108        "srum_app_resource",
5109    ],
5110    sources: &[
5111        "https://attack.mitre.org/techniques/T1059/",
5112        "https://attack.mitre.org/techniques/T1070/004/",
5113        "https://www.sans.org/blog/computer-forensic-artifacts-windows-7-prefetch-files/",
5114        "https://13cubed.com/downloads/Windows_Forensic_Analysis_Poster.pdf",
5115        "https://isc.sans.edu/diary/Forensic+Value+of+Prefetch/29168",
5116        "https://www.magnetforensics.com/blog/forensic-analysis-of-prefetch-files-in-windows/",
5117    ],
5118};
5119
5120static SRUM_NET_FIELDS: &[FieldSchema] = &[
5121    FieldSchema {
5122        name: "app_id",
5123        description: "Application identifier (path or service name)",
5124        value_type: ValueType::Text,
5125        is_uid_component: true,
5126    },
5127    FieldSchema {
5128        name: "user_id",
5129        description: "SID of the user account",
5130        value_type: ValueType::Text,
5131        is_uid_component: true,
5132    },
5133    FieldSchema {
5134        name: "timestamp",
5135        description: "ESE column TimeStamp (UTC)",
5136        value_type: ValueType::Timestamp,
5137        is_uid_component: false,
5138    },
5139    FieldSchema {
5140        name: "bytes_sent",
5141        description: "Total bytes sent by this app in the interval",
5142        value_type: ValueType::UnsignedInt,
5143        is_uid_component: false,
5144    },
5145    FieldSchema {
5146        name: "bytes_received",
5147        description: "Total bytes received by this app in the interval",
5148        value_type: ValueType::UnsignedInt,
5149        is_uid_component: false,
5150    },
5151    FieldSchema {
5152        name: "interface_luid",
5153        description: "Network interface LUID",
5154        value_type: ValueType::UnsignedInt,
5155        is_uid_component: false,
5156    },
5157];
5158
5159pub static SRUM_NETWORK_USAGE: ArtifactDescriptor = ArtifactDescriptor {
5160    id: "srum_network_usage",
5161    name: "SRUM Network Data Usage Table",
5162    artifact_type: ArtifactType::File,
5163    hive: None,
5164    key_path: "",
5165    value_name: None,
5166    file_path: Some(r"C:\Windows\System32\sru\SRUDB.dat:{973F5D5C-1D90-11D3-AE08-00A0C90F57DA}"),
5167    scope: DataScope::System,
5168    os_scope: OsScope::Win8Plus,
5169    decoder: Decoder::Identity,
5170    meaning:
5171        "ESE table {973F5D5C-1D90-11D3-AE08-00A0C90F57DA} records per-app bytes sent/received \
5172              per network interface per hour. ~30-day retention. Proves data exfiltration volume \
5173              even after log deletion; correlate AppId + UserId + BytesSent for exfil attribution.",
5174    mitre_techniques: &["T1049", "T1048"],
5175    fields: SRUM_NET_FIELDS,
5176    retention: Some("~30 days"),
5177    triage_priority: TriagePriority::Critical,
5178    related_artifacts: &["evtx_security", "srum_app_resource", "prefetch_file"],
5179    sources: &[
5180        "https://attack.mitre.org/techniques/T1049/",
5181        "https://attack.mitre.org/techniques/T1048/",
5182        "https://www.sans.org/white-papers/36660/",
5183        "https://www.sans.org/blog/srum-forensics/",
5184        "https://www.magnetforensics.com/blog/srum-forensic-analysis-of-windows-system-resource-utilization-monitor/",
5185    ],
5186};
5187
5188static SRUM_APP_FIELDS: &[FieldSchema] = &[
5189    FieldSchema {
5190        name: "app_id",
5191        description: "Application path or service name",
5192        value_type: ValueType::Text,
5193        is_uid_component: true,
5194    },
5195    FieldSchema {
5196        name: "user_id",
5197        description: "SID of the user account",
5198        value_type: ValueType::Text,
5199        is_uid_component: true,
5200    },
5201    FieldSchema {
5202        name: "timestamp",
5203        description: "Interval timestamp (UTC)",
5204        value_type: ValueType::Timestamp,
5205        is_uid_component: false,
5206    },
5207    FieldSchema {
5208        name: "foreground_cpu_time",
5209        description: "CPU time used in foreground (100ns units)",
5210        value_type: ValueType::UnsignedInt,
5211        is_uid_component: false,
5212    },
5213    FieldSchema {
5214        name: "background_cpu_time",
5215        description: "CPU time used in background (100ns units)",
5216        value_type: ValueType::UnsignedInt,
5217        is_uid_component: false,
5218    },
5219    FieldSchema {
5220        name: "foreground_cycles",
5221        description: "CPU cycle count in foreground",
5222        value_type: ValueType::UnsignedInt,
5223        is_uid_component: false,
5224    },
5225    FieldSchema {
5226        name: "background_cycles",
5227        description: "CPU cycle count in background",
5228        value_type: ValueType::UnsignedInt,
5229        is_uid_component: false,
5230    },
5231];
5232
5233pub static SRUM_APP_RESOURCE: ArtifactDescriptor = ArtifactDescriptor {
5234    id: "srum_app_resource",
5235    name: "SRUM Application Resource Usage Table",
5236    artifact_type: ArtifactType::File,
5237    hive: None,
5238    key_path: "",
5239    value_name: None,
5240    file_path: Some(r"C:\Windows\System32\sru\SRUDB.dat:{D10CA2FE-6FCF-4F6D-848E-B2E99266FA89}"),
5241    scope: DataScope::System,
5242    os_scope: OsScope::Win8Plus,
5243    decoder: Decoder::Identity,
5244    meaning: "ESE table {D10CA2FE-6FCF-4F6D-848E-B2E99266FA89} records per-app CPU cycles \
5245              (foreground + background) per hour per user. Proves execution even without Prefetch \
5246              or Event Log entries — CPU cycles are non-zero only if the process actually ran.",
5247    mitre_techniques: &["T1059", "T1070.004"],
5248    fields: SRUM_APP_FIELDS,
5249    retention: Some("~30 days"),
5250    triage_priority: TriagePriority::Critical,
5251    related_artifacts: &["srum_network_usage", "prefetch_file", "evtx_security"],
5252    sources: &[
5253        "https://attack.mitre.org/techniques/T1059/",
5254        "https://attack.mitre.org/techniques/T1070/004/",
5255        "https://www.sans.org/white-papers/36660/",
5256        "https://www.sans.org/blog/srum-forensics/",
5257        "https://www.magnetforensics.com/blog/srum-forensic-analysis-of-windows-system-resource-utilization-monitor/",
5258    ],
5259};
5260
5261static SRUM_ENERGY_FIELDS: &[FieldSchema] = &[
5262    FieldSchema {
5263        name: "app_id",
5264        description: "Application path",
5265        value_type: ValueType::Text,
5266        is_uid_component: true,
5267    },
5268    FieldSchema {
5269        name: "user_id",
5270        description: "SID of the user account",
5271        value_type: ValueType::Text,
5272        is_uid_component: true,
5273    },
5274    FieldSchema {
5275        name: "timestamp",
5276        description: "Interval timestamp (UTC)",
5277        value_type: ValueType::Timestamp,
5278        is_uid_component: false,
5279    },
5280    FieldSchema {
5281        name: "charge_level",
5282        description: "Battery charge level at sample time",
5283        value_type: ValueType::UnsignedInt,
5284        is_uid_component: false,
5285    },
5286    FieldSchema {
5287        name: "designed_capacity",
5288        description: "Battery designed capacity (mWh)",
5289        value_type: ValueType::UnsignedInt,
5290        is_uid_component: false,
5291    },
5292    FieldSchema {
5293        name: "full_charge_capacity",
5294        description: "Current full charge capacity (mWh)",
5295        value_type: ValueType::UnsignedInt,
5296        is_uid_component: false,
5297    },
5298];
5299
5300pub static SRUM_ENERGY_USAGE: ArtifactDescriptor = ArtifactDescriptor {
5301    id: "srum_energy_usage",
5302    name: "SRUM Energy Usage Table",
5303    artifact_type: ArtifactType::File,
5304    hive: None,
5305    key_path: "",
5306    value_name: None,
5307    file_path: Some(r"C:\Windows\System32\sru\SRUDB.dat:{FEE4E14F-02A9-4550-B5CE-5FA2DA202E37}"),
5308    scope: DataScope::System,
5309    os_scope: OsScope::Win8Plus,
5310    decoder: Decoder::Identity,
5311    meaning: "ESE table {FEE4E14F-02A9-4550-B5CE-5FA2DA202E37} records battery charge levels \
5312              at each sampling interval — enables timeline reconstruction of device on/off events \
5313              and correlates app activity with physical device presence.",
5314    mitre_techniques: &["T1059"],
5315    fields: SRUM_ENERGY_FIELDS,
5316    retention: Some("~30 days"),
5317    triage_priority: TriagePriority::High,
5318    related_artifacts: &[],
5319    sources: &[
5320        "https://attack.mitre.org/techniques/T1059/",
5321        "https://www.sans.org/white-papers/36660/",
5322    ],
5323};
5324
5325static SRUM_PUSH_FIELDS: &[FieldSchema] = &[
5326    FieldSchema {
5327        name: "app_id",
5328        description: "Application that received notification",
5329        value_type: ValueType::Text,
5330        is_uid_component: true,
5331    },
5332    FieldSchema {
5333        name: "user_id",
5334        description: "SID of the user account",
5335        value_type: ValueType::Text,
5336        is_uid_component: true,
5337    },
5338    FieldSchema {
5339        name: "timestamp",
5340        description: "Notification timestamp (UTC)",
5341        value_type: ValueType::Timestamp,
5342        is_uid_component: false,
5343    },
5344    FieldSchema {
5345        name: "notification_type",
5346        description: "WNS notification type code",
5347        value_type: ValueType::UnsignedInt,
5348        is_uid_component: false,
5349    },
5350    FieldSchema {
5351        name: "payload_size",
5352        description: "Notification payload size in bytes",
5353        value_type: ValueType::UnsignedInt,
5354        is_uid_component: false,
5355    },
5356];
5357
5358pub static SRUM_PUSH_NOTIFICATION: ArtifactDescriptor = ArtifactDescriptor {
5359    id: "srum_push_notification",
5360    name: "SRUM Push Notification Activity Table",
5361    artifact_type: ArtifactType::File,
5362    hive: None,
5363    key_path: "",
5364    value_name: None,
5365    file_path: Some(r"C:\Windows\System32\sru\SRUDB.dat:{D10CA2FE-6FCF-4F6D-848E-B2E99266FA86}"),
5366    scope: DataScope::System,
5367    os_scope: OsScope::Win10Plus,
5368    decoder: Decoder::Identity,
5369    meaning: "ESE table {D10CA2FE-6FCF-4F6D-848E-B2E99266FA86} records Windows Push Notification \
5370              (WNS) activity per app — reveals C2-style notification-triggered execution in \
5371              malicious UWP/PWA apps and confirms app network activity.",
5372    mitre_techniques: &["T1059"],
5373    fields: SRUM_PUSH_FIELDS,
5374    retention: Some("~30 days"),
5375    triage_priority: TriagePriority::High,
5376    related_artifacts: &[],
5377    sources: &[
5378        "https://attack.mitre.org/techniques/T1059/",
5379        "https://www.sans.org/white-papers/36660/",
5380    ],
5381};
5382
5383static EVTX_FIELDS: &[FieldSchema] = &[
5384    FieldSchema {
5385        name: "event_id",
5386        description: "Windows Event ID",
5387        value_type: ValueType::UnsignedInt,
5388        is_uid_component: true,
5389    },
5390    FieldSchema {
5391        name: "timestamp",
5392        description: "Event timestamp (UTC)",
5393        value_type: ValueType::Timestamp,
5394        is_uid_component: false,
5395    },
5396    FieldSchema {
5397        name: "computer",
5398        description: "Source computer name",
5399        value_type: ValueType::Text,
5400        is_uid_component: false,
5401    },
5402    FieldSchema {
5403        name: "subject_user_sid",
5404        description: "SID of the subject user",
5405        value_type: ValueType::Text,
5406        is_uid_component: false,
5407    },
5408    FieldSchema {
5409        name: "subject_user_name",
5410        description: "Username of the subject",
5411        value_type: ValueType::Text,
5412        is_uid_component: false,
5413    },
5414    FieldSchema {
5415        name: "message",
5416        description: "Full event message XML",
5417        value_type: ValueType::Text,
5418        is_uid_component: false,
5419    },
5420];
5421
5422pub static EVTX_SECURITY: ArtifactDescriptor = ArtifactDescriptor {
5423    id: "evtx_security",
5424    name: "Security Event Log (Security.evtx)",
5425    artifact_type: ArtifactType::File,
5426    hive: None,
5427    key_path: "",
5428    value_name: None,
5429    file_path: Some(r"C:\Windows\System32\winevt\Logs\Security.evtx"),
5430    scope: DataScope::System,
5431    os_scope: OsScope::Win7Plus,
5432    decoder: Decoder::Identity,
5433    meaning: "Primary security audit log. Key event IDs: 4624/4625 (logon success/fail), \
5434              4634/4647 (logoff), 4648 (explicit-cred logon), 4688/4689 (process create/exit), \
5435              4698/4702 (scheduled task create/modify), 4720/4732 (account create/group add), \
5436              1102 (audit log cleared — high-priority anti-forensics indicator).",
5437    mitre_techniques: &["T1070.001", "T1059", "T1078"],
5438    fields: EVTX_FIELDS,
5439    retention: Some("configurable; default ~20MB rolling per channel"),
5440    triage_priority: TriagePriority::Critical,
5441    related_artifacts: &[
5442        "srum_network_usage",
5443        "srum_app_resource",
5444        "prefetch_file",
5445        "shimcache",
5446    ],
5447    sources: &[
5448        "https://attack.mitre.org/techniques/T1070/001/",
5449        "https://attack.mitre.org/techniques/T1059/",
5450        "https://attack.mitre.org/techniques/T1078/",
5451        "https://www.sans.org/posters/windows-forensic-analysis/",
5452        "https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/basic-security-audit-policies",
5453        "https://www.13cubed.com/downloads/windows_event_log_cheat_sheet.pdf",
5454        "https://www.magnetforensics.com/blog/the-importance-of-powershell-logs-in-digital-forensics/",
5455    ],
5456};
5457
5458pub static EVTX_SYSTEM: ArtifactDescriptor = ArtifactDescriptor {
5459    id: "evtx_system",
5460    name: "System Event Log (System.evtx)",
5461    artifact_type: ArtifactType::File,
5462    hive: None,
5463    key_path: "",
5464    value_name: None,
5465    file_path: Some(r"C:\Windows\System32\winevt\Logs\System.evtx"),
5466    scope: DataScope::System,
5467    os_scope: OsScope::Win7Plus,
5468    decoder: Decoder::Identity,
5469    meaning:
5470        "System-level events. Key IDs: 7045 (service installed), 7036 (service state change), \
5471              6005/6006 (event log start/stop — boot/shutdown boundary), \
5472              104 (System log cleared). Service installation (7045) is a primary \
5473              lateral-movement and persistence indicator.",
5474    mitre_techniques: &["T1543.003", "T1070.001"],
5475    fields: EVTX_FIELDS,
5476    retention: Some("configurable; default ~20MB rolling per channel"),
5477    triage_priority: TriagePriority::High,
5478    related_artifacts: &["evtx_security", "scheduled_tasks_dir", "services_imagepath"],
5479    sources: &[
5480        "https://attack.mitre.org/techniques/T1543/003/",
5481        "https://attack.mitre.org/techniques/T1070/001/",
5482        "https://www.sans.org/posters/windows-forensic-analysis/",
5483        "https://learn.microsoft.com/en-us/windows/win32/eventlog/event-logging",
5484    ],
5485};
5486
5487pub static EVTX_POWERSHELL: ArtifactDescriptor = ArtifactDescriptor {
5488    id: "evtx_powershell",
5489    name: "PowerShell Operational Log",
5490    artifact_type: ArtifactType::File,
5491    hive: None,
5492    key_path: "",
5493    value_name: None,
5494    file_path: Some(
5495        r"C:\Windows\System32\winevt\Logs\Microsoft-Windows-PowerShell%4Operational.evtx",
5496    ),
5497    scope: DataScope::System,
5498    os_scope: OsScope::Win7Plus,
5499    decoder: Decoder::Identity,
5500    meaning:
5501        "PowerShell script execution telemetry. Event 4103 (module logging — pipeline output), \
5502              4104 (ScriptBlock logging — full script text including deobfuscated content). \
5503              4104 captures AMSI-deobfuscated scripts even when encoded; \
5504              highest-fidelity PS forensic source when enabled.",
5505    mitre_techniques: &["T1059.001", "T1027"],
5506    fields: EVTX_FIELDS,
5507    retention: Some("configurable; default ~20MB rolling per channel"),
5508    triage_priority: TriagePriority::High,
5509    related_artifacts: &[
5510        "evtx_security",
5511        "powershell_history",
5512        "powershell_profile_all",
5513    ],
5514    sources: &[
5515        "https://attack.mitre.org/techniques/T1059/001/",
5516        "https://attack.mitre.org/techniques/T1027/",
5517        "https://www.sans.org/blog/detecting-malicious-powershell/",
5518        "https://redcanary.com/threat-detection-report/techniques/t1059.001/",
5519    ],
5520};
5521
5522pub static EVTX_SYSMON: ArtifactDescriptor = ArtifactDescriptor {
5523    id: "evtx_sysmon",
5524    name: "Sysmon Operational Log",
5525    artifact_type: ArtifactType::File,
5526    hive: None,
5527    key_path: "",
5528    value_name: None,
5529    file_path: Some(r"C:\Windows\System32\winevt\Logs\Microsoft-Windows-Sysmon%4Operational.evtx"),
5530    scope: DataScope::System,
5531    os_scope: OsScope::Win7Plus,
5532    decoder: Decoder::Identity,
5533    meaning:
5534        "Sysmon telemetry (requires deployment). Event 1 (process create + hashes + cmdline), \
5535              3 (network connection), 7 (image load), 8 (CreateRemoteThread), \
5536              10 (ProcessAccess — LSASS reads), 11 (file create), 22 (DNS query). \
5537              Gold standard for EDR-quality forensics without commercial tooling.",
5538    mitre_techniques: &["T1059", "T1055", "T1003.001"],
5539    fields: EVTX_FIELDS,
5540    retention: Some("configurable; default ~20MB rolling per channel"),
5541    triage_priority: TriagePriority::Critical,
5542    related_artifacts: &["evtx_security", "prefetch_file", "srum_app_resource"],
5543    sources: &[
5544        "https://attack.mitre.org/techniques/T1059/",
5545        "https://attack.mitre.org/techniques/T1055/",
5546        "https://attack.mitre.org/techniques/T1003/001/",
5547        "https://learn.microsoft.com/en-us/sysinternals/downloads/sysmon",
5548        "https://www.sans.org/blog/threat-hunting-using-sysmon/",
5549        "https://www.thedfirspot.com/post/sysmon-when-visibility-is-key",
5550    ],
5551};
5552
5553// ── Global catalog ───────────────────────────────────────────────────────────
5554
5555/// The global forensic artifact catalog containing all known artifact descriptors.
5556pub static CATALOG: ForensicCatalog = ForensicCatalog::new(&[
5557    USERASSIST_EXE,
5558    USERASSIST_FOLDER,
5559    RUN_KEY_HKLM_RUN,
5560    RUN_KEY_HKCU_RUN,
5561    RUN_KEY_HKCU_RUNONCE,
5562    RUN_KEY_HKLM_RUNONCE,
5563    TYPED_URLS,
5564    TYPED_URLS_TIME,
5565    PCA_APPLAUNCH_DIC,
5566    IFEO_DEBUGGER,
5567    SHELLBAGS_USER,
5568    AMCACHE_APP_FILE,
5569    SHIMCACHE,
5570    BAM_USER,
5571    DAM_USER,
5572    SAM_USERS,
5573    LSA_SECRETS,
5574    DCC2_CACHE,
5575    MRU_RECENT_DOCS,
5576    USB_ENUM,
5577    MUICACHE,
5578    APPINIT_DLLS,
5579    WINLOGON_USERINIT,
5580    SCREENSAVER_EXE,
5581    // Batch C — Windows persistence
5582    WINLOGON_SHELL,
5583    SERVICES_IMAGEPATH,
5584    ACTIVE_SETUP_HKLM,
5585    ACTIVE_SETUP_HKCU,
5586    COM_HIJACK_CLSID_HKCU,
5587    APPCERT_DLLS,
5588    BOOT_EXECUTE,
5589    LSA_SECURITY_PKGS,
5590    LSA_AUTH_PKGS,
5591    PRINT_MONITORS,
5592    TIME_PROVIDERS,
5593    NETSH_HELPER_DLLS,
5594    BROWSER_HELPER_OBJECTS,
5595    STARTUP_FOLDER_USER,
5596    STARTUP_FOLDER_SYSTEM,
5597    SCHEDULED_TASKS_DIR,
5598    WDIGEST_CACHING,
5599    // Batch C — Windows execution evidence
5600    WORDWHEEL_QUERY,
5601    OPENSAVE_MRU,
5602    LASTVISITED_MRU,
5603    PREFETCH_DIR,
5604    SRUM_DB,
5605    WINDOWS_TIMELINE,
5606    POWERSHELL_HISTORY,
5607    RECYCLE_BIN,
5608    THUMBCACHE,
5609    SEARCH_DB_USER,
5610    // Batch C — Windows credentials
5611    DPAPI_MASTERKEY_USER,
5612    DPAPI_CRED_USER,
5613    DPAPI_CRED_ROAMING,
5614    WINDOWS_VAULT_USER,
5615    WINDOWS_VAULT_SYSTEM,
5616    RDP_CLIENT_SERVERS,
5617    RDP_CLIENT_DEFAULT,
5618    NTDS_DIT,
5619    CHROME_LOGIN_DATA,
5620    FIREFOX_LOGINS,
5621    WIFI_PROFILES,
5622    // Batch D — Linux cron / init persistence
5623    LINUX_CRONTAB_SYSTEM,
5624    LINUX_CRON_D,
5625    LINUX_CRON_PERIODIC,
5626    LINUX_USER_CRONTAB,
5627    LINUX_ANACRONTAB,
5628    // Batch D — Linux systemd persistence
5629    LINUX_SYSTEMD_SYSTEM_UNIT,
5630    LINUX_SYSTEMD_USER_UNIT,
5631    LINUX_SYSTEMD_TIMER,
5632    // Batch D — Linux SysV init
5633    LINUX_RC_LOCAL,
5634    LINUX_INIT_D,
5635    // Batch D — Linux shell startup persistence
5636    LINUX_BASHRC_USER,
5637    LINUX_BASH_PROFILE_USER,
5638    LINUX_PROFILE_USER,
5639    LINUX_ZSHRC_USER,
5640    LINUX_PROFILE_SYSTEM,
5641    LINUX_PROFILE_D,
5642    // Batch D — Linux dynamic linker hijack
5643    LINUX_LD_SO_PRELOAD,
5644    LINUX_LD_SO_CONF_D,
5645    // Batch D — Linux SSH persistence
5646    LINUX_SSH_AUTHORIZED_KEYS,
5647    // Batch D — Linux auth / privilege escalation
5648    LINUX_PAM_D,
5649    LINUX_SUDOERS_D,
5650    LINUX_MODULES_LOAD_D,
5651    LINUX_MOTD_D,
5652    LINUX_UDEV_RULES_D,
5653    // Batch D — Linux execution evidence
5654    LINUX_BASH_HISTORY,
5655    LINUX_ZSH_HISTORY,
5656    LINUX_WTMP,
5657    LINUX_BTMP,
5658    LINUX_LASTLOG,
5659    LINUX_AUTH_LOG,
5660    LINUX_JOURNAL_DIR,
5661    // Batch D — Linux credentials
5662    LINUX_PASSWD,
5663    LINUX_SHADOW,
5664    LINUX_SSH_PRIVATE_KEY,
5665    LINUX_SSH_KNOWN_HOSTS,
5666    LINUX_GNUPG_PRIVATE,
5667    LINUX_AWS_CREDENTIALS,
5668    LINUX_DOCKER_CONFIG,
5669    // Batch E — Windows execution evidence
5670    LNK_FILES,
5671    JUMP_LIST_AUTO,
5672    JUMP_LIST_CUSTOM,
5673    EVTX_DIR,
5674    USN_JOURNAL,
5675    // Batch E — Windows persistence
5676    WMI_MOF_DIR,
5677    BITS_DB,
5678    WMI_SUBSCRIPTIONS,
5679    LOGON_SCRIPTS,
5680    WINSOCK_LSP,
5681    APPSHIM_DB,
5682    PASSWORD_FILTER_DLL,
5683    OFFICE_NORMAL_DOTM,
5684    POWERSHELL_PROFILE_ALL,
5685    // Batch E — Windows credentials
5686    DPAPI_SYSTEM_MASTERKEY,
5687    DPAPI_CREDHIST,
5688    CHROME_COOKIES,
5689    EDGE_WEBCACHE,
5690    VPN_RAS_PHONEBOOK,
5691    WINDOWS_HELLO_NGC,
5692    USER_CERT_PRIVATE_KEY,
5693    MACHINE_CERT_STORE,
5694    // Batch F — Linux extended
5695    LINUX_AT_QUEUE,
5696    LINUX_SSHD_CONFIG,
5697    LINUX_ETC_GROUP,
5698    LINUX_GNOME_KEYRING,
5699    LINUX_KDE_KWALLET,
5700    LINUX_CHROME_LOGIN_LINUX,
5701    LINUX_FIREFOX_LOGINS_LINUX,
5702    LINUX_UTMP,
5703    LINUX_GCP_CREDENTIALS,
5704    LINUX_AZURE_CREDENTIALS,
5705    LINUX_KUBE_CONFIG,
5706    LINUX_GIT_CREDENTIALS,
5707    LINUX_NETRC,
5708    // Batch G — LinuxPersist-sourced
5709    LINUX_ETC_ENVIRONMENT,
5710    LINUX_XDG_AUTOSTART_USER,
5711    LINUX_XDG_AUTOSTART_SYSTEM,
5712    LINUX_NETWORKMANAGER_DISPATCHER,
5713    LINUX_APT_HOOKS,
5714    // Batch H — JL / LNK / Prefetch / SRUM / EVTX
5715    JUMP_LIST_SYSTEM,
5716    LNK_FILES_OFFICE,
5717    PREFETCH_FILE,
5718    SRUM_NETWORK_USAGE,
5719    SRUM_APP_RESOURCE,
5720    SRUM_ENERGY_USAGE,
5721    SRUM_PUSH_NOTIFICATION,
5722    EVTX_SECURITY,
5723    EVTX_SYSTEM,
5724    EVTX_POWERSHELL,
5725    EVTX_SYSMON,
5726]);
5727
5728// ── Tests ────────────────────────────────────────────────────────────────────
5729
5730#[cfg(test)]
5731mod tests {
5732    use super::*;
5733
5734    // ── FILETIME conversion ──────────────────────────────────────────────
5735
5736    #[test]
5737    fn filetime_zero_returns_none() {
5738        assert_eq!(filetime_to_iso8601(0), None);
5739    }
5740
5741    #[test]
5742    fn filetime_before_unix_epoch_returns_none() {
5743        // 1600-01-01 is before the Unix epoch offset.
5744        assert_eq!(filetime_to_iso8601(1), None);
5745    }
5746
5747    #[test]
5748    fn filetime_unix_epoch_is_1970() {
5749        // Exactly the Unix epoch: 1970-01-01T00:00:00Z
5750        let ft: u64 = 116_444_736_000_000_000;
5751        assert_eq!(
5752            filetime_to_iso8601(ft),
5753            Some("1970-01-01T00:00:00Z".to_string())
5754        );
5755    }
5756
5757    #[test]
5758    fn filetime_known_date_2023() {
5759        // 2023-01-15T10:30:00Z
5760        // Unix timestamp: 1673778600
5761        // FILETIME = 1673778600 * 10_000_000 + 116_444_736_000_000_000
5762        let unix_ts: u64 = 1_673_778_600;
5763        let ft = unix_ts * 10_000_000 + 116_444_736_000_000_000;
5764        assert_eq!(
5765            filetime_to_iso8601(ft),
5766            Some("2023-01-15T10:30:00Z".to_string())
5767        );
5768    }
5769
5770    // ── ROT13 ────────────────────────────────────────────────────────────
5771
5772    #[test]
5773    fn rot13_roundtrip() {
5774        let s = "Hello, World!";
5775        assert_eq!(rot13(&rot13(s)), s);
5776    }
5777
5778    #[test]
5779    fn rot13_known_value() {
5780        assert_eq!(rot13("URYYB"), "HELLO");
5781    }
5782
5783    #[test]
5784    fn rot13_numbers_unchanged() {
5785        assert_eq!(rot13("12345"), "12345");
5786    }
5787
5788    // ── Catalog queries ──────────────────────────────────────────────────
5789
5790    #[test]
5791    fn catalog_has_entries() {
5792        assert!(!CATALOG.list().is_empty());
5793        assert_eq!(CATALOG.list().len(), 151);
5794    }
5795
5796    #[test]
5797    fn catalog_by_id_userassist() {
5798        let desc = CATALOG.by_id("userassist_exe").unwrap();
5799        assert_eq!(desc.name, "UserAssist (EXE)");
5800        assert_eq!(desc.hive, Some(HiveTarget::NtUser));
5801        assert_eq!(desc.scope, DataScope::User);
5802    }
5803
5804    #[test]
5805    fn catalog_by_id_missing_returns_none() {
5806        assert!(CATALOG.by_id("nonexistent").is_none());
5807    }
5808
5809    #[test]
5810    fn catalog_filter_by_hive_ntuser() {
5811        let q = ArtifactQuery {
5812            hive: Some(HiveTarget::NtUser),
5813            ..Default::default()
5814        };
5815        let results = CATALOG.filter(&q);
5816        assert!(results.len() >= 2); // userassist + typed_urls
5817        assert!(results.iter().all(|d| d.hive == Some(HiveTarget::NtUser)));
5818    }
5819
5820    #[test]
5821    fn catalog_filter_by_scope_system() {
5822        let q = ArtifactQuery {
5823            scope: Some(DataScope::System),
5824            ..Default::default()
5825        };
5826        let results = CATALOG.filter(&q);
5827        assert!(results.iter().all(|d| d.scope == DataScope::System));
5828    }
5829
5830    #[test]
5831    fn catalog_filter_by_mitre_technique() {
5832        let q = ArtifactQuery {
5833            mitre_technique: Some("T1547.001"),
5834            ..Default::default()
5835        };
5836        let results = CATALOG.filter(&q);
5837        assert!(!results.is_empty());
5838        assert!(results
5839            .iter()
5840            .all(|d| d.mitre_techniques.contains(&"T1547.001")));
5841    }
5842
5843    #[test]
5844    fn catalog_filter_by_artifact_type_file() {
5845        let q = ArtifactQuery {
5846            artifact_type: Some(ArtifactType::File),
5847            ..Default::default()
5848        };
5849        let results = CATALOG.filter(&q);
5850        // Multiple File artifacts now exist (PCA, SRUM, Timeline, PowerShell history,
5851        // NTDS.dit, Chrome Login Data, Firefox logins, Windows Search DB).
5852        assert!(!results.is_empty());
5853        // PCA must still be present.
5854        assert!(results.iter().any(|d| d.id == "pca_applaunch_dic"));
5855    }
5856
5857    #[test]
5858    fn catalog_filter_empty_query_returns_all() {
5859        let q = ArtifactQuery::default();
5860        assert_eq!(CATALOG.filter(&q).len(), CATALOG.list().len());
5861    }
5862
5863    #[test]
5864    fn catalog_filter_by_id() {
5865        let q = ArtifactQuery {
5866            id: Some("typed_urls"),
5867            ..Default::default()
5868        };
5869        let results = CATALOG.filter(&q);
5870        assert_eq!(results.len(), 1);
5871        assert_eq!(results[0].id, "typed_urls");
5872    }
5873
5874    #[test]
5875    fn catalog_filter_combined_scope_and_hive() {
5876        let q = ArtifactQuery {
5877            scope: Some(DataScope::User),
5878            hive: Some(HiveTarget::NtUser),
5879            ..Default::default()
5880        };
5881        let results = CATALOG.filter(&q);
5882        assert!(results.len() >= 2);
5883    }
5884
5885    // ── Decoder: Identity ────────────────────────────────────────────────
5886
5887    #[test]
5888    fn decode_identity_utf8() {
5889        let rec = CATALOG
5890            .decode(&RUN_KEY_HKLM_RUN, "MyApp", b"C:\\Program Files\\app.exe")
5891            .unwrap();
5892        assert_eq!(rec.artifact_id, "run_key_hklm");
5893        assert_eq!(
5894            rec.fields,
5895            vec![(
5896                "value",
5897                ArtifactValue::Text("C:\\Program Files\\app.exe".to_string())
5898            )]
5899        );
5900    }
5901
5902    #[test]
5903    fn decode_identity_empty_raw() {
5904        let rec = CATALOG.decode(&RUN_KEY_HKLM_RUN, "", b"").unwrap();
5905        assert_eq!(
5906            rec.fields,
5907            vec![("value", ArtifactValue::Text(String::new()))]
5908        );
5909    }
5910
5911    #[test]
5912    fn decode_identity_invalid_utf8() {
5913        let err = CATALOG
5914            .decode(&RUN_KEY_HKLM_RUN, "", &[0xFF, 0xFE, 0x80])
5915            .unwrap_err();
5916        assert_eq!(err, DecodeError::InvalidUtf8);
5917    }
5918
5919    // ── Decoder: Rot13NameWithBinaryValue (UserAssist) ───────────────────
5920
5921    #[test]
5922    fn decode_userassist_valid() {
5923        // Build a 72-byte UserAssist binary value:
5924        // bytes 4-7: run_count = 5
5925        // bytes 8-11: focus_count = 3
5926        // bytes 12-15: focus_duration_ms = 10000
5927        // bytes 60-67: FILETIME for 2023-01-15T10:30:00Z
5928        let mut raw = vec![0u8; 72];
5929        raw[4..8].copy_from_slice(&5u32.to_le_bytes());
5930        raw[8..12].copy_from_slice(&3u32.to_le_bytes());
5931        raw[12..16].copy_from_slice(&10000u32.to_le_bytes());
5932        let ft: u64 = 1_673_778_600 * 10_000_000 + 116_444_736_000_000_000;
5933        raw[60..68].copy_from_slice(&ft.to_le_bytes());
5934
5935        let rot13_name = rot13("C:\\Program Files\\notepad.exe");
5936        let rec = CATALOG.decode(&USERASSIST_EXE, &rot13_name, &raw).unwrap();
5937
5938        assert_eq!(rec.artifact_id, "userassist_exe");
5939        assert_eq!(rec.scope, DataScope::User);
5940        assert_eq!(
5941            rec.fields[0],
5942            (
5943                "program",
5944                ArtifactValue::Text("C:\\Program Files\\notepad.exe".to_string())
5945            )
5946        );
5947        assert_eq!(rec.fields[1], ("run_count", ArtifactValue::UnsignedInt(5)));
5948        assert_eq!(
5949            rec.fields[2],
5950            ("focus_count", ArtifactValue::UnsignedInt(3))
5951        );
5952        assert_eq!(
5953            rec.fields[3],
5954            ("focus_duration_ms", ArtifactValue::UnsignedInt(10000))
5955        );
5956        assert_eq!(
5957            rec.fields[4],
5958            (
5959                "last_run",
5960                ArtifactValue::Timestamp("2023-01-15T10:30:00Z".to_string())
5961            )
5962        );
5963        assert_eq!(rec.timestamp, Some("2023-01-15T10:30:00Z".to_string()));
5964    }
5965
5966    #[test]
5967    fn decode_userassist_buffer_too_short() {
5968        let raw = vec![0u8; 16]; // need at least 68 for last_run field
5969        let err = CATALOG.decode(&USERASSIST_EXE, "test", &raw).unwrap_err();
5970        match err {
5971            DecodeError::FieldOutOfBounds { field, .. } => {
5972                assert_eq!(field, "last_run");
5973            }
5974            other => panic!("expected FieldOutOfBounds, got: {other:?}"),
5975        }
5976    }
5977
5978    #[test]
5979    fn decode_userassist_zero_filetime() {
5980        // All zeros: FILETIME at offset 60 is zero -> Null
5981        let raw = vec![0u8; 72];
5982        let rec = CATALOG.decode(&USERASSIST_EXE, "grfg", &raw).unwrap();
5983        assert_eq!(rec.fields[4], ("last_run", ArtifactValue::Null));
5984        assert_eq!(rec.timestamp, None);
5985    }
5986
5987    // ── Decoder: PipeDelimited ───────────────────────────────────────────
5988
5989    #[test]
5990    fn decode_pipe_delimited_from_name() {
5991        let rec = CATALOG
5992            .decode(
5993                &PCA_APPLAUNCH_DIC,
5994                r"C:\Windows\notepad.exe|2023-01-15 10:30:00",
5995                b"",
5996            )
5997            .unwrap();
5998        assert_eq!(rec.artifact_id, "pca_applaunch_dic");
5999        assert_eq!(
6000            rec.fields[0],
6001            (
6002                "exe_path",
6003                ArtifactValue::Text(r"C:\Windows\notepad.exe".to_string())
6004            )
6005        );
6006        assert_eq!(
6007            rec.fields[1],
6008            (
6009                "timestamp",
6010                ArtifactValue::Text("2023-01-15 10:30:00".to_string())
6011            )
6012        );
6013    }
6014
6015    #[test]
6016    fn decode_pipe_delimited_fewer_fields_than_schema() {
6017        // Only one field in the pipe string, but schema expects two.
6018        let rec = CATALOG
6019            .decode(&PCA_APPLAUNCH_DIC, r"C:\app.exe", b"")
6020            .unwrap();
6021        assert_eq!(
6022            rec.fields[0],
6023            ("exe_path", ArtifactValue::Text(r"C:\app.exe".to_string()))
6024        );
6025        // Second field should be Null (missing).
6026        assert_eq!(rec.fields[1], ("timestamp", ArtifactValue::Null));
6027    }
6028
6029    #[test]
6030    fn decode_pipe_delimited_from_raw_when_name_empty() {
6031        let raw = b"C:\\tool.exe|2024-06-01";
6032        let rec = CATALOG.decode(&PCA_APPLAUNCH_DIC, "", raw).unwrap();
6033        assert_eq!(
6034            rec.fields[0],
6035            ("exe_path", ArtifactValue::Text("C:\\tool.exe".to_string()))
6036        );
6037    }
6038
6039    // ── Decoder: DwordLe ─────────────────────────────────────────────────
6040
6041    #[test]
6042    fn decode_dword_le() {
6043        // Build a minimal descriptor with DwordLe decoder.
6044        static DWORD_DESC: ArtifactDescriptor = ArtifactDescriptor {
6045            id: "test_dword",
6046            name: "Test DWORD",
6047            artifact_type: ArtifactType::RegistryValue,
6048            hive: Some(HiveTarget::HklmSoftware),
6049            key_path: "Test",
6050            value_name: None,
6051            file_path: None,
6052            scope: DataScope::System,
6053            os_scope: OsScope::All,
6054            decoder: Decoder::DwordLe,
6055            meaning: "test",
6056            mitre_techniques: &[],
6057            fields: &[],
6058            retention: None,
6059            triage_priority: TriagePriority::Low,
6060            related_artifacts: &[],
6061            sources: &[],
6062        };
6063        let raw = 42u32.to_le_bytes();
6064        let rec = CATALOG.decode(&DWORD_DESC, "val", &raw).unwrap();
6065        assert_eq!(rec.fields, vec![("value", ArtifactValue::UnsignedInt(42))]);
6066    }
6067
6068    #[test]
6069    fn decode_dword_le_too_short() {
6070        static DWORD_DESC: ArtifactDescriptor = ArtifactDescriptor {
6071            id: "test_dword2",
6072            name: "Test DWORD 2",
6073            artifact_type: ArtifactType::RegistryValue,
6074            hive: Some(HiveTarget::HklmSoftware),
6075            key_path: "Test",
6076            value_name: None,
6077            file_path: None,
6078            scope: DataScope::System,
6079            os_scope: OsScope::All,
6080            decoder: Decoder::DwordLe,
6081            meaning: "test",
6082            mitre_techniques: &[],
6083            fields: &[],
6084            retention: None,
6085            triage_priority: TriagePriority::Low,
6086            related_artifacts: &[],
6087            sources: &[],
6088        };
6089        let err = CATALOG.decode(&DWORD_DESC, "v", &[1, 2]).unwrap_err();
6090        assert_eq!(
6091            err,
6092            DecodeError::BufferTooShort {
6093                expected: 4,
6094                actual: 2
6095            }
6096        );
6097    }
6098
6099    // ── Decoder: Utf16Le ─────────────────────────────────────────────────
6100
6101    #[test]
6102    fn decode_utf16le() {
6103        static UTF16_DESC: ArtifactDescriptor = ArtifactDescriptor {
6104            id: "test_utf16",
6105            name: "Test UTF-16",
6106            artifact_type: ArtifactType::RegistryValue,
6107            hive: Some(HiveTarget::NtUser),
6108            key_path: "Test",
6109            value_name: None,
6110            file_path: None,
6111            scope: DataScope::User,
6112            os_scope: OsScope::All,
6113            decoder: Decoder::Utf16Le,
6114            meaning: "test",
6115            mitre_techniques: &[],
6116            fields: &[],
6117            retention: None,
6118            triage_priority: TriagePriority::Low,
6119            related_artifacts: &[],
6120            sources: &[],
6121        };
6122        // "Hi" in UTF-16LE + NUL terminator
6123        let raw: &[u8] = &[0x48, 0x00, 0x69, 0x00, 0x00, 0x00];
6124        let rec = CATALOG.decode(&UTF16_DESC, "", raw).unwrap();
6125        assert_eq!(
6126            rec.fields,
6127            vec![("value", ArtifactValue::Text("Hi".to_string()))]
6128        );
6129    }
6130
6131    #[test]
6132    fn decode_utf16le_odd_length() {
6133        static UTF16_DESC: ArtifactDescriptor = ArtifactDescriptor {
6134            id: "test_utf16_odd",
6135            name: "Test UTF-16 odd",
6136            artifact_type: ArtifactType::RegistryValue,
6137            hive: Some(HiveTarget::NtUser),
6138            key_path: "Test",
6139            value_name: None,
6140            file_path: None,
6141            scope: DataScope::User,
6142            os_scope: OsScope::All,
6143            decoder: Decoder::Utf16Le,
6144            meaning: "test",
6145            mitre_techniques: &[],
6146            fields: &[],
6147            retention: None,
6148            triage_priority: TriagePriority::Low,
6149            related_artifacts: &[],
6150            sources: &[],
6151        };
6152        let err = CATALOG
6153            .decode(&UTF16_DESC, "", &[0x48, 0x00, 0x69])
6154            .unwrap_err();
6155        assert_eq!(err, DecodeError::InvalidUtf16);
6156    }
6157
6158    // ── Decoder: MultiSz ─────────────────────────────────────────────────
6159
6160    #[test]
6161    fn decode_multi_sz() {
6162        static MSZ_DESC: ArtifactDescriptor = ArtifactDescriptor {
6163            id: "test_msz",
6164            name: "Test MultiSz",
6165            artifact_type: ArtifactType::RegistryValue,
6166            hive: Some(HiveTarget::HklmSoftware),
6167            key_path: "Test",
6168            value_name: None,
6169            file_path: None,
6170            scope: DataScope::System,
6171            os_scope: OsScope::All,
6172            decoder: Decoder::MultiSz,
6173            meaning: "test",
6174            mitre_techniques: &[],
6175            fields: &[],
6176            retention: None,
6177            triage_priority: TriagePriority::Low,
6178            related_artifacts: &[],
6179            sources: &[],
6180        };
6181        // "AB\0CD\0\0" in UTF-16LE
6182        let raw: &[u8] = &[
6183            0x41, 0x00, 0x42, 0x00, // "AB"
6184            0x00, 0x00, // NUL separator
6185            0x43, 0x00, 0x44, 0x00, // "CD"
6186            0x00, 0x00, // NUL terminator
6187            0x00, 0x00, // double NUL
6188        ];
6189        let rec = CATALOG.decode(&MSZ_DESC, "", raw).unwrap();
6190        assert_eq!(
6191            rec.fields,
6192            vec![(
6193                "values",
6194                ArtifactValue::List(vec![
6195                    ArtifactValue::Text("AB".to_string()),
6196                    ArtifactValue::Text("CD".to_string()),
6197                ])
6198            )]
6199        );
6200    }
6201
6202    #[test]
6203    fn decode_multi_sz_empty() {
6204        static MSZ_DESC: ArtifactDescriptor = ArtifactDescriptor {
6205            id: "test_msz_empty",
6206            name: "Test MultiSz empty",
6207            artifact_type: ArtifactType::RegistryValue,
6208            hive: Some(HiveTarget::HklmSoftware),
6209            key_path: "Test",
6210            value_name: None,
6211            file_path: None,
6212            scope: DataScope::System,
6213            os_scope: OsScope::All,
6214            decoder: Decoder::MultiSz,
6215            meaning: "test",
6216            mitre_techniques: &[],
6217            fields: &[],
6218            retention: None,
6219            triage_priority: TriagePriority::Low,
6220            related_artifacts: &[],
6221            sources: &[],
6222        };
6223        let rec = CATALOG.decode(&MSZ_DESC, "", &[]).unwrap();
6224        assert_eq!(rec.fields, vec![("values", ArtifactValue::List(vec![]))]);
6225    }
6226
6227    // ── Decoder: MruListEx ───────────────────────────────────────────────
6228
6229    #[test]
6230    fn decode_mrulistex() {
6231        static MRU_DESC: ArtifactDescriptor = ArtifactDescriptor {
6232            id: "test_mru",
6233            name: "Test MRUListEx",
6234            artifact_type: ArtifactType::RegistryValue,
6235            hive: Some(HiveTarget::NtUser),
6236            key_path: "Test",
6237            value_name: None,
6238            file_path: None,
6239            scope: DataScope::User,
6240            os_scope: OsScope::All,
6241            decoder: Decoder::MruListEx,
6242            meaning: "test",
6243            mitre_techniques: &[],
6244            fields: &[],
6245            retention: None,
6246            triage_priority: TriagePriority::Low,
6247            related_artifacts: &[],
6248            sources: &[],
6249        };
6250        // [2, 0, 1, 0xFFFFFFFF]
6251        let mut raw = Vec::new();
6252        raw.extend_from_slice(&2u32.to_le_bytes());
6253        raw.extend_from_slice(&0u32.to_le_bytes());
6254        raw.extend_from_slice(&1u32.to_le_bytes());
6255        raw.extend_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
6256        let rec = CATALOG.decode(&MRU_DESC, "", &raw).unwrap();
6257        assert_eq!(
6258            rec.fields,
6259            vec![(
6260                "indices",
6261                ArtifactValue::List(vec![
6262                    ArtifactValue::UnsignedInt(2),
6263                    ArtifactValue::UnsignedInt(0),
6264                    ArtifactValue::UnsignedInt(1),
6265                ])
6266            )]
6267        );
6268    }
6269
6270    #[test]
6271    fn decode_mrulistex_empty() {
6272        static MRU_DESC: ArtifactDescriptor = ArtifactDescriptor {
6273            id: "test_mru_empty",
6274            name: "Test MRUListEx empty",
6275            artifact_type: ArtifactType::RegistryValue,
6276            hive: Some(HiveTarget::NtUser),
6277            key_path: "Test",
6278            value_name: None,
6279            file_path: None,
6280            scope: DataScope::User,
6281            os_scope: OsScope::All,
6282            decoder: Decoder::MruListEx,
6283            meaning: "test",
6284            mitre_techniques: &[],
6285            fields: &[],
6286            retention: None,
6287            triage_priority: TriagePriority::Low,
6288            related_artifacts: &[],
6289            sources: &[],
6290        };
6291        let rec = CATALOG.decode(&MRU_DESC, "", &[]).unwrap();
6292        assert_eq!(rec.fields, vec![("indices", ArtifactValue::List(vec![]))]);
6293    }
6294
6295    // ── Decoder: FiletimeAt ──────────────────────────────────────────────
6296
6297    #[test]
6298    fn decode_filetime_at() {
6299        static FT_DESC: ArtifactDescriptor = ArtifactDescriptor {
6300            id: "test_ft",
6301            name: "Test FiletimeAt",
6302            artifact_type: ArtifactType::RegistryValue,
6303            hive: Some(HiveTarget::NtUser),
6304            key_path: "Test",
6305            value_name: None,
6306            file_path: None,
6307            scope: DataScope::User,
6308            os_scope: OsScope::All,
6309            decoder: Decoder::FiletimeAt { offset: 0 },
6310            meaning: "test",
6311            mitre_techniques: &[],
6312            fields: &[],
6313            retention: None,
6314            triage_priority: TriagePriority::Low,
6315            related_artifacts: &[],
6316            sources: &[],
6317        };
6318        let ft: u64 = 116_444_736_000_000_000; // Unix epoch
6319        let raw = ft.to_le_bytes();
6320        let rec = CATALOG.decode(&FT_DESC, "", &raw).unwrap();
6321        assert_eq!(
6322            rec.fields,
6323            vec![(
6324                "timestamp",
6325                ArtifactValue::Timestamp("1970-01-01T00:00:00Z".to_string())
6326            )]
6327        );
6328        assert_eq!(rec.timestamp, Some("1970-01-01T00:00:00Z".to_string()));
6329    }
6330
6331    #[test]
6332    fn decode_filetime_at_buffer_too_short() {
6333        static FT_DESC: ArtifactDescriptor = ArtifactDescriptor {
6334            id: "test_ft_short",
6335            name: "Test FiletimeAt short",
6336            artifact_type: ArtifactType::RegistryValue,
6337            hive: Some(HiveTarget::NtUser),
6338            key_path: "Test",
6339            value_name: None,
6340            file_path: None,
6341            scope: DataScope::User,
6342            os_scope: OsScope::All,
6343            decoder: Decoder::FiletimeAt { offset: 4 },
6344            meaning: "test",
6345            mitre_techniques: &[],
6346            fields: &[],
6347            retention: None,
6348            triage_priority: TriagePriority::Low,
6349            related_artifacts: &[],
6350            sources: &[],
6351        };
6352        let err = CATALOG.decode(&FT_DESC, "", &[0; 8]).unwrap_err();
6353        assert_eq!(
6354            err,
6355            DecodeError::BufferTooShort {
6356                expected: 12,
6357                actual: 8
6358            }
6359        );
6360    }
6361
6362    // ── UID generation ───────────────────────────────────────────────────
6363
6364    #[test]
6365    fn uid_registry_with_name() {
6366        let rec = CATALOG
6367            .decode(&RUN_KEY_HKLM_RUN, "MyApp", b"cmd.exe")
6368            .unwrap();
6369        assert!(rec.uid.starts_with("winreg://HKLM\\SOFTWARE/"));
6370        assert!(rec.uid.contains("MyApp"));
6371    }
6372
6373    #[test]
6374    fn uid_file_artifact() {
6375        let rec = CATALOG.decode(&PCA_APPLAUNCH_DIC, "line1", b"").unwrap();
6376        assert!(rec.uid.starts_with("file://"));
6377        assert!(rec.uid.contains("AppLaunch.dic"));
6378    }
6379
6380    // ── DecodeError Display ──────────────────────────────────────────────
6381
6382    #[test]
6383    fn decode_error_display_buffer_too_short() {
6384        let e = DecodeError::BufferTooShort {
6385            expected: 8,
6386            actual: 4,
6387        };
6388        assert_eq!(e.to_string(), "buffer too short: need 8 bytes, got 4");
6389    }
6390
6391    #[test]
6392    fn decode_error_display_field_out_of_bounds() {
6393        let e = DecodeError::FieldOutOfBounds {
6394            field: "last_run",
6395            offset: 60,
6396            size: 8,
6397            buf_len: 16,
6398        };
6399        assert!(e.to_string().contains("last_run"));
6400    }
6401
6402    // ── ArtifactDescriptor field coverage ────────────────────────────────
6403
6404    #[test]
6405    fn userassist_descriptor_has_correct_metadata() {
6406        assert_eq!(USERASSIST_EXE.id, "userassist_exe");
6407        assert_eq!(USERASSIST_EXE.hive, Some(HiveTarget::NtUser));
6408        assert_eq!(USERASSIST_EXE.scope, DataScope::User);
6409        assert_eq!(USERASSIST_EXE.os_scope, OsScope::Win7Plus);
6410        assert!(!USERASSIST_EXE.mitre_techniques.is_empty());
6411        assert!(!USERASSIST_EXE.fields.is_empty());
6412        assert!(USERASSIST_EXE.key_path.contains("UserAssist"));
6413    }
6414
6415    #[test]
6416    fn pca_descriptor_has_correct_metadata() {
6417        assert_eq!(PCA_APPLAUNCH_DIC.id, "pca_applaunch_dic");
6418        assert_eq!(PCA_APPLAUNCH_DIC.artifact_type, ArtifactType::File);
6419        assert_eq!(PCA_APPLAUNCH_DIC.hive, None);
6420        assert_eq!(PCA_APPLAUNCH_DIC.os_scope, OsScope::Win11_22H2);
6421        assert!(PCA_APPLAUNCH_DIC.file_path.is_some());
6422    }
6423
6424    #[test]
6425    fn run_key_descriptor_has_correct_metadata() {
6426        assert_eq!(RUN_KEY_HKLM_RUN.scope, DataScope::System);
6427        assert!(RUN_KEY_HKLM_RUN.mitre_techniques.contains(&"T1547.001"));
6428    }
6429
6430    // ── ArtifactRecord confidence default ────────────────────────────────
6431
6432    #[test]
6433    fn decoded_record_has_default_confidence() {
6434        let rec = CATALOG.decode(&RUN_KEY_HKLM_RUN, "x", b"y").unwrap();
6435        assert!((rec.confidence - 1.0).abs() < f32::EPSILON);
6436    }
6437
6438    // ── BinaryField edge cases ───────────────────────────────────────────
6439
6440    #[test]
6441    fn binary_record_exact_size_boundary() {
6442        // A record with a single U32Le at offset 0 -- exactly 4 bytes.
6443        static FIELDS: &[BinaryField] = &[BinaryField {
6444            name: "val",
6445            offset: 0,
6446            field_type: BinaryFieldType::U32Le,
6447            description: "test",
6448        }];
6449        static DESC: ArtifactDescriptor = ArtifactDescriptor {
6450            id: "test_exact",
6451            name: "Test exact",
6452            artifact_type: ArtifactType::RegistryValue,
6453            hive: Some(HiveTarget::HklmSoftware),
6454            key_path: "Test",
6455            value_name: None,
6456            file_path: None,
6457            scope: DataScope::System,
6458            os_scope: OsScope::All,
6459            decoder: Decoder::BinaryRecord(FIELDS),
6460            meaning: "test",
6461            mitre_techniques: &[],
6462            fields: &[],
6463            retention: None,
6464            triage_priority: TriagePriority::Low,
6465            related_artifacts: &[],
6466            sources: &[],
6467        };
6468        let raw = 99u32.to_le_bytes();
6469        let rec = CATALOG.decode(&DESC, "", &raw).unwrap();
6470        assert_eq!(rec.fields, vec![("val", ArtifactValue::UnsignedInt(99))]);
6471    }
6472
6473    #[test]
6474    fn binary_record_bytes_field() {
6475        static FIELDS: &[BinaryField] = &[BinaryField {
6476            name: "header",
6477            offset: 0,
6478            field_type: BinaryFieldType::Bytes { len: 4 },
6479            description: "test header",
6480        }];
6481        static DESC: ArtifactDescriptor = ArtifactDescriptor {
6482            id: "test_bytes",
6483            name: "Test bytes",
6484            artifact_type: ArtifactType::RegistryValue,
6485            hive: Some(HiveTarget::HklmSoftware),
6486            key_path: "Test",
6487            value_name: None,
6488            file_path: None,
6489            scope: DataScope::System,
6490            os_scope: OsScope::All,
6491            decoder: Decoder::BinaryRecord(FIELDS),
6492            meaning: "test",
6493            mitre_techniques: &[],
6494            fields: &[],
6495            retention: None,
6496            triage_priority: TriagePriority::Low,
6497            related_artifacts: &[],
6498            sources: &[],
6499        };
6500        let raw = [0xDE, 0xAD, 0xBE, 0xEF];
6501        let rec = CATALOG.decode(&DESC, "", &raw).unwrap();
6502        assert_eq!(
6503            rec.fields,
6504            vec![("header", ArtifactValue::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]))]
6505        );
6506    }
6507}
6508
6509// ── Tests for new batch-A/B descriptors ──────────────────────────────────────
6510
6511#[cfg(test)]
6512mod tests_new_descriptors {
6513    use super::*;
6514
6515    // ── Run key variants ─────────────────────────────────────────────────
6516
6517    #[test]
6518    fn run_key_hkcu_run_metadata() {
6519        assert_eq!(RUN_KEY_HKCU_RUN.id, "run_key_hkcu");
6520        assert_eq!(RUN_KEY_HKCU_RUN.hive, Some(HiveTarget::NtUser));
6521        assert_eq!(RUN_KEY_HKCU_RUN.scope, DataScope::User);
6522        assert!(RUN_KEY_HKCU_RUN.mitre_techniques.contains(&"T1547.001"));
6523        assert!(RUN_KEY_HKCU_RUN.key_path.contains("Run"));
6524    }
6525
6526    #[test]
6527    fn run_key_hkcu_runonce_metadata() {
6528        assert_eq!(RUN_KEY_HKCU_RUNONCE.id, "run_key_hkcu_once");
6529        assert_eq!(RUN_KEY_HKCU_RUNONCE.hive, Some(HiveTarget::NtUser));
6530        assert_eq!(RUN_KEY_HKCU_RUNONCE.scope, DataScope::User);
6531        assert!(RUN_KEY_HKCU_RUNONCE.key_path.contains("RunOnce"));
6532    }
6533
6534    #[test]
6535    fn run_key_hklm_runonce_metadata() {
6536        assert_eq!(RUN_KEY_HKLM_RUNONCE.id, "run_key_hklm_once");
6537        assert_eq!(RUN_KEY_HKLM_RUNONCE.hive, Some(HiveTarget::HklmSoftware));
6538        assert_eq!(RUN_KEY_HKLM_RUNONCE.scope, DataScope::System);
6539        assert!(RUN_KEY_HKLM_RUNONCE.key_path.contains("RunOnce"));
6540    }
6541
6542    // ── IFEO ─────────────────────────────────────────────────────────────
6543
6544    #[test]
6545    fn ifeo_debugger_metadata() {
6546        assert_eq!(IFEO_DEBUGGER.id, "ifeo_debugger");
6547        assert_eq!(IFEO_DEBUGGER.hive, Some(HiveTarget::HklmSoftware));
6548        assert_eq!(IFEO_DEBUGGER.scope, DataScope::System);
6549        assert!(IFEO_DEBUGGER.mitre_techniques.contains(&"T1546.012"));
6550        assert!(IFEO_DEBUGGER
6551            .key_path
6552            .contains("Image File Execution Options"));
6553    }
6554
6555    // ── UserAssist folder GUID ────────────────────────────────────────────
6556
6557    #[test]
6558    fn userassist_folder_metadata() {
6559        assert_eq!(USERASSIST_FOLDER.id, "userassist_folder");
6560        assert_eq!(USERASSIST_FOLDER.hive, Some(HiveTarget::NtUser));
6561        assert_eq!(USERASSIST_FOLDER.scope, DataScope::User);
6562        assert!(USERASSIST_FOLDER.key_path.contains("UserAssist"));
6563    }
6564
6565    // ── Shellbags ────────────────────────────────────────────────────────
6566
6567    #[test]
6568    fn shellbags_user_metadata() {
6569        assert_eq!(SHELLBAGS_USER.id, "shellbags_user");
6570        assert_eq!(SHELLBAGS_USER.hive, Some(HiveTarget::UsrClass));
6571        assert_eq!(SHELLBAGS_USER.scope, DataScope::User);
6572        assert!(SHELLBAGS_USER.mitre_techniques.contains(&"T1083"));
6573        assert!(SHELLBAGS_USER.key_path.contains("Shell"));
6574    }
6575
6576    // ── Amcache ──────────────────────────────────────────────────────────
6577
6578    #[test]
6579    fn amcache_app_file_metadata() {
6580        assert_eq!(AMCACHE_APP_FILE.id, "amcache_app_file");
6581        assert_eq!(AMCACHE_APP_FILE.hive, Some(HiveTarget::Amcache));
6582        assert_eq!(AMCACHE_APP_FILE.scope, DataScope::System);
6583        assert!(AMCACHE_APP_FILE.mitre_techniques.contains(&"T1218"));
6584    }
6585
6586    // ── ShimCache ────────────────────────────────────────────────────────
6587
6588    #[test]
6589    fn shimcache_metadata() {
6590        assert_eq!(SHIMCACHE.id, "shimcache");
6591        assert_eq!(SHIMCACHE.hive, Some(HiveTarget::HklmSystem));
6592        assert_eq!(SHIMCACHE.scope, DataScope::System);
6593        assert!(SHIMCACHE.mitre_techniques.contains(&"T1218"));
6594        assert!(SHIMCACHE.key_path.contains("AppCompatCache"));
6595    }
6596
6597    // ── BAM / DAM ────────────────────────────────────────────────────────
6598
6599    #[test]
6600    fn bam_user_metadata() {
6601        assert_eq!(BAM_USER.id, "bam_user");
6602        assert_eq!(BAM_USER.hive, Some(HiveTarget::HklmSystem));
6603        assert_eq!(BAM_USER.scope, DataScope::Mixed);
6604        assert_eq!(BAM_USER.os_scope, OsScope::Win10Plus);
6605        assert!(BAM_USER.key_path.contains("bam"));
6606    }
6607
6608    #[test]
6609    fn dam_user_metadata() {
6610        assert_eq!(DAM_USER.id, "dam_user");
6611        assert_eq!(DAM_USER.hive, Some(HiveTarget::HklmSystem));
6612        assert_eq!(DAM_USER.scope, DataScope::Mixed);
6613        assert_eq!(DAM_USER.os_scope, OsScope::Win10Plus);
6614        assert!(DAM_USER.key_path.contains("dam"));
6615    }
6616
6617    // ── SAM ──────────────────────────────────────────────────────────────
6618
6619    #[test]
6620    fn sam_users_metadata() {
6621        assert_eq!(SAM_USERS.id, "sam_users");
6622        assert_eq!(SAM_USERS.hive, Some(HiveTarget::HklmSam));
6623        assert_eq!(SAM_USERS.scope, DataScope::System);
6624        assert!(SAM_USERS.key_path.contains("Users"));
6625        assert!(SAM_USERS.mitre_techniques.contains(&"T1003.002"));
6626    }
6627
6628    // ── LSA ──────────────────────────────────────────────────────────────
6629
6630    #[test]
6631    fn lsa_secrets_metadata() {
6632        assert_eq!(LSA_SECRETS.id, "lsa_secrets");
6633        assert_eq!(LSA_SECRETS.hive, Some(HiveTarget::HklmSecurity));
6634        assert_eq!(LSA_SECRETS.scope, DataScope::System);
6635        assert!(LSA_SECRETS.key_path.contains("Secrets"));
6636        assert!(LSA_SECRETS.mitre_techniques.contains(&"T1003.004"));
6637    }
6638
6639    #[test]
6640    fn dcc2_cache_metadata() {
6641        assert_eq!(DCC2_CACHE.id, "dcc2_cache");
6642        assert_eq!(DCC2_CACHE.hive, Some(HiveTarget::HklmSecurity));
6643        assert_eq!(DCC2_CACHE.scope, DataScope::System);
6644        assert!(DCC2_CACHE.mitre_techniques.contains(&"T1003.005"));
6645    }
6646
6647    // ── TypedURLsTime ────────────────────────────────────────────────────
6648
6649    #[test]
6650    fn typed_urls_time_metadata() {
6651        assert_eq!(TYPED_URLS_TIME.id, "typed_urls_time");
6652        assert_eq!(TYPED_URLS_TIME.hive, Some(HiveTarget::NtUser));
6653        assert_eq!(TYPED_URLS_TIME.scope, DataScope::User);
6654        assert!(TYPED_URLS_TIME.key_path.contains("TypedURLsTime"));
6655    }
6656
6657    // ── MRU RecentDocs ───────────────────────────────────────────────────
6658
6659    #[test]
6660    fn mru_recent_docs_metadata() {
6661        assert_eq!(MRU_RECENT_DOCS.id, "mru_recent_docs");
6662        assert_eq!(MRU_RECENT_DOCS.hive, Some(HiveTarget::NtUser));
6663        assert_eq!(MRU_RECENT_DOCS.scope, DataScope::User);
6664        assert!(MRU_RECENT_DOCS.key_path.contains("RecentDocs"));
6665    }
6666
6667    // ── USB ──────────────────────────────────────────────────────────────
6668
6669    #[test]
6670    fn usb_enum_metadata() {
6671        assert_eq!(USB_ENUM.id, "usb_enum");
6672        assert_eq!(USB_ENUM.hive, Some(HiveTarget::HklmSystem));
6673        assert_eq!(USB_ENUM.scope, DataScope::System);
6674        assert!(USB_ENUM.mitre_techniques.contains(&"T1200"));
6675        assert!(USB_ENUM.key_path.contains("USBSTOR"));
6676    }
6677
6678    // ── MUICache ─────────────────────────────────────────────────────────
6679
6680    #[test]
6681    fn muicache_metadata() {
6682        assert_eq!(MUICACHE.id, "muicache");
6683        assert_eq!(MUICACHE.hive, Some(HiveTarget::UsrClass));
6684        assert_eq!(MUICACHE.scope, DataScope::User);
6685        assert!(MUICACHE.key_path.contains("MuiCache"));
6686    }
6687
6688    // ── AppInit DLLs ─────────────────────────────────────────────────────
6689
6690    #[test]
6691    fn appinit_dlls_metadata() {
6692        assert_eq!(APPINIT_DLLS.id, "appinit_dlls");
6693        assert_eq!(APPINIT_DLLS.hive, Some(HiveTarget::HklmSoftware));
6694        assert_eq!(APPINIT_DLLS.scope, DataScope::System);
6695        assert!(APPINIT_DLLS.mitre_techniques.contains(&"T1546.010"));
6696        assert!(APPINIT_DLLS.key_path.contains("Windows NT"));
6697    }
6698
6699    // ── Winlogon ─────────────────────────────────────────────────────────
6700
6701    #[test]
6702    fn winlogon_userinit_metadata() {
6703        assert_eq!(WINLOGON_USERINIT.id, "winlogon_userinit");
6704        assert_eq!(WINLOGON_USERINIT.hive, Some(HiveTarget::HklmSoftware));
6705        assert_eq!(WINLOGON_USERINIT.scope, DataScope::System);
6706        assert!(WINLOGON_USERINIT.mitre_techniques.contains(&"T1547.004"));
6707        assert!(WINLOGON_USERINIT.key_path.contains("Winlogon"));
6708    }
6709
6710    // ── Screensaver ──────────────────────────────────────────────────────
6711
6712    #[test]
6713    fn screensaver_exe_metadata() {
6714        assert_eq!(SCREENSAVER_EXE.id, "screensaver_exe");
6715        assert_eq!(SCREENSAVER_EXE.hive, Some(HiveTarget::NtUser));
6716        assert_eq!(SCREENSAVER_EXE.scope, DataScope::User);
6717        assert!(SCREENSAVER_EXE.mitre_techniques.contains(&"T1546.002"));
6718        assert!(SCREENSAVER_EXE.key_path.contains("Desktop"));
6719    }
6720
6721    // ── CATALOG completeness ──────────────────────────────────────────────
6722
6723    #[test]
6724    fn catalog_contains_all_new_descriptors() {
6725        let ids: Vec<&str> = CATALOG.list().iter().map(|d| d.id).collect();
6726        for expected in &[
6727            "run_key_hkcu",
6728            "run_key_hkcu_once",
6729            "run_key_hklm_once",
6730            "ifeo_debugger",
6731            "userassist_folder",
6732            "shellbags_user",
6733            "amcache_app_file",
6734            "shimcache",
6735            "bam_user",
6736            "dam_user",
6737            "sam_users",
6738            "lsa_secrets",
6739            "dcc2_cache",
6740            "typed_urls_time",
6741            "mru_recent_docs",
6742            "usb_enum",
6743            "muicache",
6744            "appinit_dlls",
6745            "winlogon_userinit",
6746            "screensaver_exe",
6747        ] {
6748            assert!(ids.contains(expected), "CATALOG missing: {expected}");
6749        }
6750    }
6751}
6752
6753// ── Tests for Batch C (Windows persistence / execution / credential) ──────────
6754
6755#[cfg(test)]
6756mod tests_batch_c {
6757    use super::*;
6758
6759    // ── Windows persistence ───────────────────────────────────────────────
6760
6761    #[test]
6762    fn winlogon_shell_md() {
6763        assert_eq!(WINLOGON_SHELL.id, "winlogon_shell");
6764        assert_eq!(WINLOGON_SHELL.hive, Some(HiveTarget::HklmSoftware));
6765        assert_eq!(WINLOGON_SHELL.scope, DataScope::System);
6766        assert!(WINLOGON_SHELL.mitre_techniques.contains(&"T1547.004"));
6767        assert!(WINLOGON_SHELL.key_path.contains("Winlogon"));
6768    }
6769    #[test]
6770    fn services_imagepath_md() {
6771        assert_eq!(SERVICES_IMAGEPATH.id, "services_imagepath");
6772        assert_eq!(SERVICES_IMAGEPATH.hive, Some(HiveTarget::HklmSystem));
6773        assert_eq!(SERVICES_IMAGEPATH.scope, DataScope::System);
6774        assert!(SERVICES_IMAGEPATH.mitre_techniques.contains(&"T1543.003"));
6775    }
6776    #[test]
6777    fn active_setup_hklm_md() {
6778        assert_eq!(ACTIVE_SETUP_HKLM.id, "active_setup_hklm");
6779        assert_eq!(ACTIVE_SETUP_HKLM.hive, Some(HiveTarget::HklmSoftware));
6780        assert_eq!(ACTIVE_SETUP_HKLM.scope, DataScope::System);
6781        assert!(ACTIVE_SETUP_HKLM.mitre_techniques.contains(&"T1547.014"));
6782    }
6783    #[test]
6784    fn active_setup_hkcu_md() {
6785        assert_eq!(ACTIVE_SETUP_HKCU.id, "active_setup_hkcu");
6786        assert_eq!(ACTIVE_SETUP_HKCU.hive, Some(HiveTarget::NtUser));
6787        assert_eq!(ACTIVE_SETUP_HKCU.scope, DataScope::User);
6788    }
6789    #[test]
6790    fn com_hijack_clsid_hkcu_md() {
6791        assert_eq!(COM_HIJACK_CLSID_HKCU.id, "com_hijack_clsid_hkcu");
6792        assert_eq!(COM_HIJACK_CLSID_HKCU.hive, Some(HiveTarget::UsrClass));
6793        assert_eq!(COM_HIJACK_CLSID_HKCU.scope, DataScope::User);
6794        assert!(COM_HIJACK_CLSID_HKCU
6795            .mitre_techniques
6796            .contains(&"T1546.015"));
6797    }
6798    #[test]
6799    fn appcert_dlls_md() {
6800        assert_eq!(APPCERT_DLLS.id, "appcert_dlls");
6801        assert_eq!(APPCERT_DLLS.hive, Some(HiveTarget::HklmSystem));
6802        assert_eq!(APPCERT_DLLS.scope, DataScope::System);
6803        assert!(APPCERT_DLLS.mitre_techniques.contains(&"T1546.009"));
6804    }
6805    #[test]
6806    fn boot_execute_md() {
6807        assert_eq!(BOOT_EXECUTE.id, "boot_execute");
6808        assert_eq!(BOOT_EXECUTE.hive, Some(HiveTarget::HklmSystem));
6809        assert_eq!(BOOT_EXECUTE.scope, DataScope::System);
6810        assert!(BOOT_EXECUTE.mitre_techniques.contains(&"T1547.001"));
6811        assert!(BOOT_EXECUTE.key_path.contains("Session Manager"));
6812    }
6813    #[test]
6814    fn lsa_security_pkgs_md() {
6815        assert_eq!(LSA_SECURITY_PKGS.id, "lsa_security_pkgs");
6816        assert_eq!(LSA_SECURITY_PKGS.hive, Some(HiveTarget::HklmSystem));
6817        assert_eq!(LSA_SECURITY_PKGS.scope, DataScope::System);
6818        assert!(LSA_SECURITY_PKGS.mitre_techniques.contains(&"T1547.005"));
6819    }
6820    #[test]
6821    fn lsa_auth_pkgs_md() {
6822        assert_eq!(LSA_AUTH_PKGS.id, "lsa_auth_pkgs");
6823        assert_eq!(LSA_AUTH_PKGS.hive, Some(HiveTarget::HklmSystem));
6824        assert_eq!(LSA_AUTH_PKGS.scope, DataScope::System);
6825        assert!(LSA_AUTH_PKGS.mitre_techniques.contains(&"T1547.002"));
6826    }
6827    #[test]
6828    fn print_monitors_md() {
6829        assert_eq!(PRINT_MONITORS.id, "print_monitors");
6830        assert_eq!(PRINT_MONITORS.hive, Some(HiveTarget::HklmSystem));
6831        assert_eq!(PRINT_MONITORS.scope, DataScope::System);
6832        assert!(PRINT_MONITORS.mitre_techniques.contains(&"T1547.010"));
6833    }
6834    #[test]
6835    fn time_providers_md() {
6836        assert_eq!(TIME_PROVIDERS.id, "time_providers");
6837        assert_eq!(TIME_PROVIDERS.hive, Some(HiveTarget::HklmSystem));
6838        assert_eq!(TIME_PROVIDERS.scope, DataScope::System);
6839        assert!(TIME_PROVIDERS.mitre_techniques.contains(&"T1547.003"));
6840    }
6841    #[test]
6842    fn netsh_helper_dlls_md() {
6843        assert_eq!(NETSH_HELPER_DLLS.id, "netsh_helper_dlls");
6844        assert_eq!(NETSH_HELPER_DLLS.hive, Some(HiveTarget::HklmSoftware));
6845        assert_eq!(NETSH_HELPER_DLLS.scope, DataScope::System);
6846        assert!(NETSH_HELPER_DLLS.mitre_techniques.contains(&"T1546.007"));
6847    }
6848    #[test]
6849    fn browser_helper_objects_md() {
6850        assert_eq!(BROWSER_HELPER_OBJECTS.id, "browser_helper_objects");
6851        assert_eq!(BROWSER_HELPER_OBJECTS.hive, Some(HiveTarget::HklmSoftware));
6852        assert_eq!(BROWSER_HELPER_OBJECTS.scope, DataScope::System);
6853        assert!(BROWSER_HELPER_OBJECTS.mitre_techniques.contains(&"T1176"));
6854    }
6855    #[test]
6856    fn startup_folder_user_md() {
6857        assert_eq!(STARTUP_FOLDER_USER.id, "startup_folder_user");
6858        assert_eq!(STARTUP_FOLDER_USER.artifact_type, ArtifactType::Directory);
6859        assert_eq!(STARTUP_FOLDER_USER.scope, DataScope::User);
6860        assert!(STARTUP_FOLDER_USER.mitre_techniques.contains(&"T1547.001"));
6861    }
6862    #[test]
6863    fn startup_folder_system_md() {
6864        assert_eq!(STARTUP_FOLDER_SYSTEM.id, "startup_folder_system");
6865        assert_eq!(STARTUP_FOLDER_SYSTEM.artifact_type, ArtifactType::Directory);
6866        assert_eq!(STARTUP_FOLDER_SYSTEM.scope, DataScope::System);
6867        assert!(STARTUP_FOLDER_SYSTEM
6868            .mitre_techniques
6869            .contains(&"T1547.001"));
6870    }
6871    #[test]
6872    fn scheduled_tasks_dir_md() {
6873        assert_eq!(SCHEDULED_TASKS_DIR.id, "scheduled_tasks_dir");
6874        assert_eq!(SCHEDULED_TASKS_DIR.artifact_type, ArtifactType::Directory);
6875        assert_eq!(SCHEDULED_TASKS_DIR.scope, DataScope::System);
6876        assert!(SCHEDULED_TASKS_DIR.mitre_techniques.contains(&"T1053.005"));
6877    }
6878    #[test]
6879    fn wdigest_caching_md() {
6880        assert_eq!(WDIGEST_CACHING.id, "wdigest_caching");
6881        assert_eq!(WDIGEST_CACHING.hive, Some(HiveTarget::HklmSystem));
6882        assert_eq!(WDIGEST_CACHING.scope, DataScope::System);
6883        assert!(WDIGEST_CACHING.mitre_techniques.contains(&"T1003.001"));
6884    }
6885
6886    // ── Windows execution evidence ────────────────────────────────────────
6887
6888    #[test]
6889    fn wordwheel_query_md() {
6890        assert_eq!(WORDWHEEL_QUERY.id, "wordwheel_query");
6891        assert_eq!(WORDWHEEL_QUERY.hive, Some(HiveTarget::NtUser));
6892        assert_eq!(WORDWHEEL_QUERY.scope, DataScope::User);
6893        assert!(WORDWHEEL_QUERY.key_path.contains("WordWheelQuery"));
6894    }
6895    #[test]
6896    fn opensave_mru_md() {
6897        assert_eq!(OPENSAVE_MRU.id, "opensave_mru");
6898        assert_eq!(OPENSAVE_MRU.hive, Some(HiveTarget::NtUser));
6899        assert_eq!(OPENSAVE_MRU.scope, DataScope::User);
6900        assert!(OPENSAVE_MRU.key_path.contains("OpenSaveMRU"));
6901    }
6902    #[test]
6903    fn lastvisited_mru_md() {
6904        assert_eq!(LASTVISITED_MRU.id, "lastvisited_mru");
6905        assert_eq!(LASTVISITED_MRU.hive, Some(HiveTarget::NtUser));
6906        assert_eq!(LASTVISITED_MRU.scope, DataScope::User);
6907        assert!(LASTVISITED_MRU.key_path.contains("LastVisitedMRU"));
6908    }
6909    #[test]
6910    fn prefetch_dir_md() {
6911        assert_eq!(PREFETCH_DIR.id, "prefetch_dir");
6912        assert_eq!(PREFETCH_DIR.artifact_type, ArtifactType::Directory);
6913        assert_eq!(PREFETCH_DIR.scope, DataScope::System);
6914        assert!(PREFETCH_DIR.mitre_techniques.contains(&"T1204.002"));
6915    }
6916    #[test]
6917    fn srum_db_md() {
6918        assert_eq!(SRUM_DB.id, "srum_db");
6919        assert_eq!(SRUM_DB.artifact_type, ArtifactType::File);
6920        assert_eq!(SRUM_DB.scope, DataScope::System);
6921        assert!(SRUM_DB.os_scope == OsScope::Win8Plus);
6922    }
6923    #[test]
6924    fn windows_timeline_md() {
6925        assert_eq!(WINDOWS_TIMELINE.id, "windows_timeline");
6926        assert_eq!(WINDOWS_TIMELINE.artifact_type, ArtifactType::File);
6927        assert_eq!(WINDOWS_TIMELINE.scope, DataScope::User);
6928        assert_eq!(WINDOWS_TIMELINE.os_scope, OsScope::Win10Plus);
6929    }
6930    #[test]
6931    fn powershell_history_md() {
6932        assert_eq!(POWERSHELL_HISTORY.id, "powershell_history");
6933        assert_eq!(POWERSHELL_HISTORY.artifact_type, ArtifactType::File);
6934        assert_eq!(POWERSHELL_HISTORY.scope, DataScope::User);
6935        assert!(POWERSHELL_HISTORY.mitre_techniques.contains(&"T1059.001"));
6936    }
6937    #[test]
6938    fn recycle_bin_md() {
6939        assert_eq!(RECYCLE_BIN.id, "recycle_bin");
6940        assert_eq!(RECYCLE_BIN.artifact_type, ArtifactType::Directory);
6941        assert_eq!(RECYCLE_BIN.scope, DataScope::User);
6942        assert!(RECYCLE_BIN.mitre_techniques.contains(&"T1070.004"));
6943    }
6944    #[test]
6945    fn thumbcache_md() {
6946        assert_eq!(THUMBCACHE.id, "thumbcache");
6947        assert_eq!(THUMBCACHE.artifact_type, ArtifactType::Directory);
6948        assert_eq!(THUMBCACHE.scope, DataScope::User);
6949    }
6950    #[test]
6951    fn search_db_user_md() {
6952        assert_eq!(SEARCH_DB_USER.id, "search_db_user");
6953        assert_eq!(SEARCH_DB_USER.artifact_type, ArtifactType::File);
6954        assert_eq!(SEARCH_DB_USER.scope, DataScope::System);
6955    }
6956
6957    // ── Windows credentials ───────────────────────────────────────────────
6958
6959    #[test]
6960    fn dpapi_masterkey_user_md() {
6961        assert_eq!(DPAPI_MASTERKEY_USER.id, "dpapi_masterkey_user");
6962        assert_eq!(DPAPI_MASTERKEY_USER.artifact_type, ArtifactType::Directory);
6963        assert_eq!(DPAPI_MASTERKEY_USER.scope, DataScope::User);
6964        assert!(DPAPI_MASTERKEY_USER.mitre_techniques.contains(&"T1555.004"));
6965    }
6966    #[test]
6967    fn dpapi_cred_user_md() {
6968        assert_eq!(DPAPI_CRED_USER.id, "dpapi_cred_user");
6969        assert_eq!(DPAPI_CRED_USER.artifact_type, ArtifactType::Directory);
6970        assert_eq!(DPAPI_CRED_USER.scope, DataScope::User);
6971    }
6972    #[test]
6973    fn dpapi_cred_roaming_md() {
6974        assert_eq!(DPAPI_CRED_ROAMING.id, "dpapi_cred_roaming");
6975        assert_eq!(DPAPI_CRED_ROAMING.artifact_type, ArtifactType::Directory);
6976        assert_eq!(DPAPI_CRED_ROAMING.scope, DataScope::User);
6977    }
6978    #[test]
6979    fn windows_vault_user_md() {
6980        assert_eq!(WINDOWS_VAULT_USER.id, "windows_vault_user");
6981        assert_eq!(WINDOWS_VAULT_USER.artifact_type, ArtifactType::Directory);
6982        assert_eq!(WINDOWS_VAULT_USER.scope, DataScope::User);
6983        assert!(WINDOWS_VAULT_USER.mitre_techniques.contains(&"T1555.004"));
6984    }
6985    #[test]
6986    fn windows_vault_system_md() {
6987        assert_eq!(WINDOWS_VAULT_SYSTEM.id, "windows_vault_system");
6988        assert_eq!(WINDOWS_VAULT_SYSTEM.artifact_type, ArtifactType::Directory);
6989        assert_eq!(WINDOWS_VAULT_SYSTEM.scope, DataScope::System);
6990    }
6991    #[test]
6992    fn rdp_client_servers_md() {
6993        assert_eq!(RDP_CLIENT_SERVERS.id, "rdp_client_servers");
6994        assert_eq!(RDP_CLIENT_SERVERS.hive, Some(HiveTarget::NtUser));
6995        assert_eq!(RDP_CLIENT_SERVERS.scope, DataScope::User);
6996        assert!(RDP_CLIENT_SERVERS.mitre_techniques.contains(&"T1021.001"));
6997    }
6998    #[test]
6999    fn rdp_client_default_md() {
7000        assert_eq!(RDP_CLIENT_DEFAULT.id, "rdp_client_default");
7001        assert_eq!(RDP_CLIENT_DEFAULT.hive, Some(HiveTarget::NtUser));
7002        assert_eq!(RDP_CLIENT_DEFAULT.scope, DataScope::User);
7003        assert!(RDP_CLIENT_DEFAULT.mitre_techniques.contains(&"T1021.001"));
7004    }
7005    #[test]
7006    fn ntds_dit_md() {
7007        assert_eq!(NTDS_DIT.id, "ntds_dit");
7008        assert_eq!(NTDS_DIT.artifact_type, ArtifactType::File);
7009        assert_eq!(NTDS_DIT.scope, DataScope::System);
7010        assert!(NTDS_DIT.mitre_techniques.contains(&"T1003.003"));
7011    }
7012    #[test]
7013    fn chrome_login_data_md() {
7014        assert_eq!(CHROME_LOGIN_DATA.id, "chrome_login_data");
7015        assert_eq!(CHROME_LOGIN_DATA.artifact_type, ArtifactType::File);
7016        assert_eq!(CHROME_LOGIN_DATA.scope, DataScope::User);
7017        assert!(CHROME_LOGIN_DATA.mitre_techniques.contains(&"T1555.003"));
7018    }
7019    #[test]
7020    fn firefox_logins_md() {
7021        assert_eq!(FIREFOX_LOGINS.id, "firefox_logins");
7022        assert_eq!(FIREFOX_LOGINS.artifact_type, ArtifactType::File);
7023        assert_eq!(FIREFOX_LOGINS.scope, DataScope::User);
7024        assert!(FIREFOX_LOGINS.mitre_techniques.contains(&"T1555.003"));
7025    }
7026    #[test]
7027    fn wifi_profiles_md() {
7028        assert_eq!(WIFI_PROFILES.id, "wifi_profiles");
7029        assert_eq!(WIFI_PROFILES.artifact_type, ArtifactType::Directory);
7030        assert_eq!(WIFI_PROFILES.scope, DataScope::System);
7031        assert!(WIFI_PROFILES.mitre_techniques.contains(&"T1552.001"));
7032    }
7033
7034    // ── CATALOG completeness (batch C) ────────────────────────────────────
7035
7036    #[test]
7037    fn catalog_contains_batch_c() {
7038        let ids: Vec<&str> = CATALOG.list().iter().map(|d| d.id).collect();
7039        for expected in &[
7040            "winlogon_shell",
7041            "services_imagepath",
7042            "active_setup_hklm",
7043            "active_setup_hkcu",
7044            "com_hijack_clsid_hkcu",
7045            "appcert_dlls",
7046            "boot_execute",
7047            "lsa_security_pkgs",
7048            "lsa_auth_pkgs",
7049            "print_monitors",
7050            "time_providers",
7051            "netsh_helper_dlls",
7052            "browser_helper_objects",
7053            "startup_folder_user",
7054            "startup_folder_system",
7055            "scheduled_tasks_dir",
7056            "wdigest_caching",
7057            "wordwheel_query",
7058            "opensave_mru",
7059            "lastvisited_mru",
7060            "prefetch_dir",
7061            "srum_db",
7062            "windows_timeline",
7063            "powershell_history",
7064            "recycle_bin",
7065            "thumbcache",
7066            "search_db_user",
7067            "dpapi_masterkey_user",
7068            "dpapi_cred_user",
7069            "dpapi_cred_roaming",
7070            "windows_vault_user",
7071            "windows_vault_system",
7072            "rdp_client_servers",
7073            "rdp_client_default",
7074            "ntds_dit",
7075            "chrome_login_data",
7076            "firefox_logins",
7077            "wifi_profiles",
7078        ] {
7079            assert!(ids.contains(expected), "CATALOG missing: {expected}");
7080        }
7081    }
7082}
7083
7084// ── Tests for Batch D (Linux persistence / execution / credential) ────────────
7085
7086#[cfg(test)]
7087mod tests_batch_d {
7088    use super::*;
7089
7090    // ── Linux persistence: cron ───────────────────────────────────────────
7091
7092    #[test]
7093    fn linux_crontab_system_md() {
7094        assert_eq!(LINUX_CRONTAB_SYSTEM.id, "linux_crontab_system");
7095        assert_eq!(LINUX_CRONTAB_SYSTEM.artifact_type, ArtifactType::File);
7096        assert_eq!(LINUX_CRONTAB_SYSTEM.scope, DataScope::System);
7097        assert_eq!(LINUX_CRONTAB_SYSTEM.os_scope, OsScope::Linux);
7098        assert!(LINUX_CRONTAB_SYSTEM.mitre_techniques.contains(&"T1053.003"));
7099    }
7100    #[test]
7101    fn linux_cron_d_md() {
7102        assert_eq!(LINUX_CRON_D.id, "linux_cron_d");
7103        assert_eq!(LINUX_CRON_D.artifact_type, ArtifactType::Directory);
7104        assert_eq!(LINUX_CRON_D.scope, DataScope::System);
7105        assert_eq!(LINUX_CRON_D.os_scope, OsScope::Linux);
7106    }
7107    #[test]
7108    fn linux_cron_periodic_md() {
7109        assert_eq!(LINUX_CRON_PERIODIC.id, "linux_cron_periodic");
7110        assert_eq!(LINUX_CRON_PERIODIC.artifact_type, ArtifactType::Directory);
7111        assert_eq!(LINUX_CRON_PERIODIC.scope, DataScope::System);
7112    }
7113    #[test]
7114    fn linux_user_crontab_md() {
7115        assert_eq!(LINUX_USER_CRONTAB.id, "linux_user_crontab");
7116        assert_eq!(LINUX_USER_CRONTAB.artifact_type, ArtifactType::File);
7117        assert_eq!(LINUX_USER_CRONTAB.scope, DataScope::User);
7118        assert!(LINUX_USER_CRONTAB.mitre_techniques.contains(&"T1053.003"));
7119    }
7120    #[test]
7121    fn linux_anacrontab_md() {
7122        assert_eq!(LINUX_ANACRONTAB.id, "linux_anacrontab");
7123        assert_eq!(LINUX_ANACRONTAB.artifact_type, ArtifactType::File);
7124        assert_eq!(LINUX_ANACRONTAB.scope, DataScope::System);
7125    }
7126
7127    // ── Linux persistence: systemd ────────────────────────────────────────
7128
7129    #[test]
7130    fn linux_systemd_system_unit_md() {
7131        assert_eq!(LINUX_SYSTEMD_SYSTEM_UNIT.id, "linux_systemd_system_unit");
7132        assert_eq!(
7133            LINUX_SYSTEMD_SYSTEM_UNIT.artifact_type,
7134            ArtifactType::Directory
7135        );
7136        assert_eq!(LINUX_SYSTEMD_SYSTEM_UNIT.scope, DataScope::System);
7137        assert_eq!(LINUX_SYSTEMD_SYSTEM_UNIT.os_scope, OsScope::LinuxSystemd);
7138        assert!(LINUX_SYSTEMD_SYSTEM_UNIT
7139            .mitre_techniques
7140            .contains(&"T1543.002"));
7141    }
7142    #[test]
7143    fn linux_systemd_user_unit_md() {
7144        assert_eq!(LINUX_SYSTEMD_USER_UNIT.id, "linux_systemd_user_unit");
7145        assert_eq!(
7146            LINUX_SYSTEMD_USER_UNIT.artifact_type,
7147            ArtifactType::Directory
7148        );
7149        assert_eq!(LINUX_SYSTEMD_USER_UNIT.scope, DataScope::User);
7150        assert_eq!(LINUX_SYSTEMD_USER_UNIT.os_scope, OsScope::LinuxSystemd);
7151    }
7152    #[test]
7153    fn linux_systemd_timer_md() {
7154        assert_eq!(LINUX_SYSTEMD_TIMER.id, "linux_systemd_timer");
7155        assert_eq!(LINUX_SYSTEMD_TIMER.artifact_type, ArtifactType::Directory);
7156        assert_eq!(LINUX_SYSTEMD_TIMER.os_scope, OsScope::LinuxSystemd);
7157        assert!(LINUX_SYSTEMD_TIMER.mitre_techniques.contains(&"T1053.006"));
7158    }
7159
7160    // ── Linux persistence: init / rc.local ───────────────────────────────
7161
7162    #[test]
7163    fn linux_rc_local_md() {
7164        assert_eq!(LINUX_RC_LOCAL.id, "linux_rc_local");
7165        assert_eq!(LINUX_RC_LOCAL.artifact_type, ArtifactType::File);
7166        assert_eq!(LINUX_RC_LOCAL.scope, DataScope::System);
7167        assert!(LINUX_RC_LOCAL.mitre_techniques.contains(&"T1037.004"));
7168    }
7169    #[test]
7170    fn linux_init_d_md() {
7171        assert_eq!(LINUX_INIT_D.id, "linux_init_d");
7172        assert_eq!(LINUX_INIT_D.artifact_type, ArtifactType::Directory);
7173        assert_eq!(LINUX_INIT_D.scope, DataScope::System);
7174    }
7175
7176    // ── Linux persistence: shell startup ─────────────────────────────────
7177
7178    #[test]
7179    fn linux_bashrc_user_md() {
7180        assert_eq!(LINUX_BASHRC_USER.id, "linux_bashrc_user");
7181        assert_eq!(LINUX_BASHRC_USER.artifact_type, ArtifactType::File);
7182        assert_eq!(LINUX_BASHRC_USER.scope, DataScope::User);
7183        assert!(LINUX_BASHRC_USER.mitre_techniques.contains(&"T1546.004"));
7184    }
7185    #[test]
7186    fn linux_bash_profile_user_md() {
7187        assert_eq!(LINUX_BASH_PROFILE_USER.id, "linux_bash_profile_user");
7188        assert_eq!(LINUX_BASH_PROFILE_USER.scope, DataScope::User);
7189        assert!(LINUX_BASH_PROFILE_USER
7190            .mitre_techniques
7191            .contains(&"T1546.004"));
7192    }
7193    #[test]
7194    fn linux_profile_user_md() {
7195        assert_eq!(LINUX_PROFILE_USER.id, "linux_profile_user");
7196        assert_eq!(LINUX_PROFILE_USER.scope, DataScope::User);
7197    }
7198    #[test]
7199    fn linux_zshrc_user_md() {
7200        assert_eq!(LINUX_ZSHRC_USER.id, "linux_zshrc_user");
7201        assert_eq!(LINUX_ZSHRC_USER.scope, DataScope::User);
7202        assert!(LINUX_ZSHRC_USER.mitre_techniques.contains(&"T1546.004"));
7203    }
7204    #[test]
7205    fn linux_profile_system_md() {
7206        assert_eq!(LINUX_PROFILE_SYSTEM.id, "linux_profile_system");
7207        assert_eq!(LINUX_PROFILE_SYSTEM.scope, DataScope::System);
7208    }
7209    #[test]
7210    fn linux_profile_d_md() {
7211        assert_eq!(LINUX_PROFILE_D.id, "linux_profile_d");
7212        assert_eq!(LINUX_PROFILE_D.artifact_type, ArtifactType::Directory);
7213        assert_eq!(LINUX_PROFILE_D.scope, DataScope::System);
7214    }
7215
7216    // ── Linux persistence: LD_PRELOAD / linker ────────────────────────────
7217
7218    #[test]
7219    fn linux_ld_so_preload_md() {
7220        assert_eq!(LINUX_LD_SO_PRELOAD.id, "linux_ld_so_preload");
7221        assert_eq!(LINUX_LD_SO_PRELOAD.artifact_type, ArtifactType::File);
7222        assert_eq!(LINUX_LD_SO_PRELOAD.scope, DataScope::System);
7223        assert!(LINUX_LD_SO_PRELOAD.mitre_techniques.contains(&"T1574.006"));
7224    }
7225    #[test]
7226    fn linux_ld_so_conf_d_md() {
7227        assert_eq!(LINUX_LD_SO_CONF_D.id, "linux_ld_so_conf_d");
7228        assert_eq!(LINUX_LD_SO_CONF_D.artifact_type, ArtifactType::Directory);
7229        assert_eq!(LINUX_LD_SO_CONF_D.scope, DataScope::System);
7230    }
7231
7232    // ── Linux persistence: SSH ────────────────────────────────────────────
7233
7234    #[test]
7235    fn linux_ssh_authorized_keys_md() {
7236        assert_eq!(LINUX_SSH_AUTHORIZED_KEYS.id, "linux_ssh_authorized_keys");
7237        assert_eq!(LINUX_SSH_AUTHORIZED_KEYS.artifact_type, ArtifactType::File);
7238        assert_eq!(LINUX_SSH_AUTHORIZED_KEYS.scope, DataScope::User);
7239        assert!(LINUX_SSH_AUTHORIZED_KEYS
7240            .mitre_techniques
7241            .contains(&"T1098.004"));
7242    }
7243
7244    // ── Linux persistence: PAM / sudo / kernel ────────────────────────────
7245
7246    #[test]
7247    fn linux_pam_d_md() {
7248        assert_eq!(LINUX_PAM_D.id, "linux_pam_d");
7249        assert_eq!(LINUX_PAM_D.artifact_type, ArtifactType::Directory);
7250        assert_eq!(LINUX_PAM_D.scope, DataScope::System);
7251        assert!(LINUX_PAM_D.mitre_techniques.contains(&"T1556.003"));
7252    }
7253    #[test]
7254    fn linux_sudoers_d_md() {
7255        assert_eq!(LINUX_SUDOERS_D.id, "linux_sudoers_d");
7256        assert_eq!(LINUX_SUDOERS_D.artifact_type, ArtifactType::Directory);
7257        assert_eq!(LINUX_SUDOERS_D.scope, DataScope::System);
7258        assert!(LINUX_SUDOERS_D.mitre_techniques.contains(&"T1548.003"));
7259    }
7260    #[test]
7261    fn linux_modules_load_d_md() {
7262        assert_eq!(LINUX_MODULES_LOAD_D.id, "linux_modules_load_d");
7263        assert_eq!(LINUX_MODULES_LOAD_D.artifact_type, ArtifactType::Directory);
7264        assert_eq!(LINUX_MODULES_LOAD_D.scope, DataScope::System);
7265        assert!(LINUX_MODULES_LOAD_D.mitre_techniques.contains(&"T1547.006"));
7266    }
7267    #[test]
7268    fn linux_motd_d_md() {
7269        assert_eq!(LINUX_MOTD_D.id, "linux_motd_d");
7270        assert_eq!(LINUX_MOTD_D.artifact_type, ArtifactType::Directory);
7271        assert_eq!(LINUX_MOTD_D.scope, DataScope::System);
7272    }
7273    #[test]
7274    fn linux_udev_rules_d_md() {
7275        assert_eq!(LINUX_UDEV_RULES_D.id, "linux_udev_rules_d");
7276        assert_eq!(LINUX_UDEV_RULES_D.artifact_type, ArtifactType::Directory);
7277        assert_eq!(LINUX_UDEV_RULES_D.scope, DataScope::System);
7278        assert!(LINUX_UDEV_RULES_D.mitre_techniques.contains(&"T1546"));
7279    }
7280
7281    // ── Linux execution evidence ──────────────────────────────────────────
7282
7283    #[test]
7284    fn linux_bash_history_md() {
7285        assert_eq!(LINUX_BASH_HISTORY.id, "linux_bash_history");
7286        assert_eq!(LINUX_BASH_HISTORY.artifact_type, ArtifactType::File);
7287        assert_eq!(LINUX_BASH_HISTORY.scope, DataScope::User);
7288        assert!(LINUX_BASH_HISTORY.mitre_techniques.contains(&"T1059.004"));
7289    }
7290    #[test]
7291    fn linux_zsh_history_md() {
7292        assert_eq!(LINUX_ZSH_HISTORY.id, "linux_zsh_history");
7293        assert_eq!(LINUX_ZSH_HISTORY.scope, DataScope::User);
7294    }
7295    #[test]
7296    fn linux_wtmp_md() {
7297        assert_eq!(LINUX_WTMP.id, "linux_wtmp");
7298        assert_eq!(LINUX_WTMP.artifact_type, ArtifactType::File);
7299        assert_eq!(LINUX_WTMP.scope, DataScope::System);
7300        assert!(LINUX_WTMP.mitre_techniques.contains(&"T1078"));
7301    }
7302    #[test]
7303    fn linux_btmp_md() {
7304        assert_eq!(LINUX_BTMP.id, "linux_btmp");
7305        assert_eq!(LINUX_BTMP.artifact_type, ArtifactType::File);
7306        assert_eq!(LINUX_BTMP.scope, DataScope::System);
7307    }
7308    #[test]
7309    fn linux_lastlog_md() {
7310        assert_eq!(LINUX_LASTLOG.id, "linux_lastlog");
7311        assert_eq!(LINUX_LASTLOG.artifact_type, ArtifactType::File);
7312        assert_eq!(LINUX_LASTLOG.scope, DataScope::System);
7313    }
7314    #[test]
7315    fn linux_auth_log_md() {
7316        assert_eq!(LINUX_AUTH_LOG.id, "linux_auth_log");
7317        assert_eq!(LINUX_AUTH_LOG.artifact_type, ArtifactType::File);
7318        assert_eq!(LINUX_AUTH_LOG.scope, DataScope::System);
7319        assert!(LINUX_AUTH_LOG.mitre_techniques.contains(&"T1078"));
7320    }
7321    #[test]
7322    fn linux_journal_dir_md() {
7323        assert_eq!(LINUX_JOURNAL_DIR.id, "linux_journal_dir");
7324        assert_eq!(LINUX_JOURNAL_DIR.artifact_type, ArtifactType::Directory);
7325        assert_eq!(LINUX_JOURNAL_DIR.os_scope, OsScope::LinuxSystemd);
7326    }
7327
7328    // ── Linux credentials ─────────────────────────────────────────────────
7329
7330    #[test]
7331    fn linux_passwd_md() {
7332        assert_eq!(LINUX_PASSWD.id, "linux_passwd");
7333        assert_eq!(LINUX_PASSWD.artifact_type, ArtifactType::File);
7334        assert_eq!(LINUX_PASSWD.scope, DataScope::System);
7335        assert!(LINUX_PASSWD.mitre_techniques.contains(&"T1087.001"));
7336    }
7337    #[test]
7338    fn linux_shadow_md() {
7339        assert_eq!(LINUX_SHADOW.id, "linux_shadow");
7340        assert_eq!(LINUX_SHADOW.artifact_type, ArtifactType::File);
7341        assert_eq!(LINUX_SHADOW.scope, DataScope::System);
7342        assert!(LINUX_SHADOW.mitre_techniques.contains(&"T1003.008"));
7343    }
7344    #[test]
7345    fn linux_ssh_private_key_md() {
7346        assert_eq!(LINUX_SSH_PRIVATE_KEY.id, "linux_ssh_private_key");
7347        assert_eq!(LINUX_SSH_PRIVATE_KEY.artifact_type, ArtifactType::File);
7348        assert_eq!(LINUX_SSH_PRIVATE_KEY.scope, DataScope::User);
7349        assert!(LINUX_SSH_PRIVATE_KEY
7350            .mitre_techniques
7351            .contains(&"T1552.004"));
7352    }
7353    #[test]
7354    fn linux_ssh_known_hosts_md() {
7355        assert_eq!(LINUX_SSH_KNOWN_HOSTS.id, "linux_ssh_known_hosts");
7356        assert_eq!(LINUX_SSH_KNOWN_HOSTS.scope, DataScope::User);
7357        assert!(LINUX_SSH_KNOWN_HOSTS
7358            .mitre_techniques
7359            .contains(&"T1021.004"));
7360    }
7361    #[test]
7362    fn linux_gnupg_private_md() {
7363        assert_eq!(LINUX_GNUPG_PRIVATE.id, "linux_gnupg_private");
7364        assert_eq!(LINUX_GNUPG_PRIVATE.artifact_type, ArtifactType::Directory);
7365        assert_eq!(LINUX_GNUPG_PRIVATE.scope, DataScope::User);
7366        assert!(LINUX_GNUPG_PRIVATE.mitre_techniques.contains(&"T1552.004"));
7367    }
7368    #[test]
7369    fn linux_aws_credentials_md() {
7370        assert_eq!(LINUX_AWS_CREDENTIALS.id, "linux_aws_credentials");
7371        assert_eq!(LINUX_AWS_CREDENTIALS.artifact_type, ArtifactType::File);
7372        assert_eq!(LINUX_AWS_CREDENTIALS.scope, DataScope::User);
7373        assert!(LINUX_AWS_CREDENTIALS
7374            .mitre_techniques
7375            .contains(&"T1552.001"));
7376    }
7377    #[test]
7378    fn linux_docker_config_md() {
7379        assert_eq!(LINUX_DOCKER_CONFIG.id, "linux_docker_config");
7380        assert_eq!(LINUX_DOCKER_CONFIG.artifact_type, ArtifactType::File);
7381        assert_eq!(LINUX_DOCKER_CONFIG.scope, DataScope::User);
7382        assert!(LINUX_DOCKER_CONFIG.mitre_techniques.contains(&"T1552.001"));
7383    }
7384
7385    // ── CATALOG completeness (batch D) ────────────────────────────────────
7386
7387    #[test]
7388    fn catalog_contains_batch_d() {
7389        let ids: Vec<&str> = CATALOG.list().iter().map(|d| d.id).collect();
7390        for expected in &[
7391            "linux_crontab_system",
7392            "linux_cron_d",
7393            "linux_cron_periodic",
7394            "linux_user_crontab",
7395            "linux_anacrontab",
7396            "linux_systemd_system_unit",
7397            "linux_systemd_user_unit",
7398            "linux_systemd_timer",
7399            "linux_rc_local",
7400            "linux_init_d",
7401            "linux_bashrc_user",
7402            "linux_bash_profile_user",
7403            "linux_profile_user",
7404            "linux_zshrc_user",
7405            "linux_profile_system",
7406            "linux_profile_d",
7407            "linux_ld_so_preload",
7408            "linux_ld_so_conf_d",
7409            "linux_ssh_authorized_keys",
7410            "linux_pam_d",
7411            "linux_sudoers_d",
7412            "linux_modules_load_d",
7413            "linux_motd_d",
7414            "linux_udev_rules_d",
7415            "linux_bash_history",
7416            "linux_zsh_history",
7417            "linux_wtmp",
7418            "linux_btmp",
7419            "linux_lastlog",
7420            "linux_auth_log",
7421            "linux_journal_dir",
7422            "linux_passwd",
7423            "linux_shadow",
7424            "linux_ssh_private_key",
7425            "linux_ssh_known_hosts",
7426            "linux_gnupg_private",
7427            "linux_aws_credentials",
7428            "linux_docker_config",
7429        ] {
7430            assert!(ids.contains(expected), "CATALOG missing: {expected}");
7431        }
7432    }
7433
7434    // ═══════════════════════════════════════════════════════════════════════
7435    // Batch E — Windows execution / persistence / credential (RED)
7436    // ═══════════════════════════════════════════════════════════════════════
7437
7438    // ── Windows execution evidence ────────────────────────────────────────
7439
7440    #[test]
7441    fn lnk_files_md() {
7442        assert_eq!(LNK_FILES.id, "lnk_files");
7443        assert_eq!(LNK_FILES.artifact_type, ArtifactType::Directory);
7444        assert_eq!(LNK_FILES.scope, DataScope::User);
7445        assert!(LNK_FILES.mitre_techniques.contains(&"T1547.009"));
7446    }
7447    #[test]
7448    fn jump_list_auto_md() {
7449        assert_eq!(JUMP_LIST_AUTO.id, "jump_list_auto");
7450        assert_eq!(JUMP_LIST_AUTO.artifact_type, ArtifactType::Directory);
7451        assert_eq!(JUMP_LIST_AUTO.scope, DataScope::User);
7452        assert!(JUMP_LIST_AUTO.mitre_techniques.contains(&"T1547.009"));
7453    }
7454    #[test]
7455    fn jump_list_custom_md() {
7456        assert_eq!(JUMP_LIST_CUSTOM.id, "jump_list_custom");
7457        assert_eq!(JUMP_LIST_CUSTOM.artifact_type, ArtifactType::Directory);
7458        assert_eq!(JUMP_LIST_CUSTOM.scope, DataScope::User);
7459        assert!(JUMP_LIST_CUSTOM.mitre_techniques.contains(&"T1547.009"));
7460    }
7461    #[test]
7462    fn evtx_dir_md() {
7463        assert_eq!(EVTX_DIR.id, "evtx_dir");
7464        assert_eq!(EVTX_DIR.artifact_type, ArtifactType::Directory);
7465        assert_eq!(EVTX_DIR.scope, DataScope::System);
7466        assert!(EVTX_DIR.mitre_techniques.contains(&"T1070.001"));
7467    }
7468    #[test]
7469    fn usn_journal_md() {
7470        assert_eq!(USN_JOURNAL.id, "usn_journal");
7471        assert_eq!(USN_JOURNAL.artifact_type, ArtifactType::File);
7472        assert_eq!(USN_JOURNAL.scope, DataScope::System);
7473        assert_eq!(USN_JOURNAL.os_scope, OsScope::Win7Plus);
7474    }
7475
7476    // ── Windows persistence ───────────────────────────────────────────────
7477
7478    #[test]
7479    fn wmi_mof_dir_md() {
7480        assert_eq!(WMI_MOF_DIR.id, "wmi_mof_dir");
7481        assert_eq!(WMI_MOF_DIR.artifact_type, ArtifactType::Directory);
7482        assert_eq!(WMI_MOF_DIR.scope, DataScope::System);
7483        assert!(WMI_MOF_DIR.mitre_techniques.contains(&"T1546.003"));
7484    }
7485    #[test]
7486    fn bits_db_md() {
7487        assert_eq!(BITS_DB.id, "bits_db");
7488        assert_eq!(BITS_DB.artifact_type, ArtifactType::Directory);
7489        assert_eq!(BITS_DB.scope, DataScope::System);
7490        assert!(BITS_DB.mitre_techniques.contains(&"T1197"));
7491    }
7492    #[test]
7493    fn wmi_subscriptions_md() {
7494        assert_eq!(WMI_SUBSCRIPTIONS.id, "wmi_subscriptions");
7495        assert_eq!(WMI_SUBSCRIPTIONS.artifact_type, ArtifactType::RegistryKey);
7496        assert_eq!(WMI_SUBSCRIPTIONS.scope, DataScope::System);
7497        assert!(WMI_SUBSCRIPTIONS.mitre_techniques.contains(&"T1546.003"));
7498    }
7499    #[test]
7500    fn logon_scripts_md() {
7501        assert_eq!(LOGON_SCRIPTS.id, "logon_scripts");
7502        assert_eq!(LOGON_SCRIPTS.artifact_type, ArtifactType::RegistryValue);
7503        assert_eq!(LOGON_SCRIPTS.scope, DataScope::User);
7504        assert!(LOGON_SCRIPTS.mitre_techniques.contains(&"T1037.001"));
7505    }
7506    #[test]
7507    fn winsock_lsp_md() {
7508        assert_eq!(WINSOCK_LSP.id, "winsock_lsp");
7509        assert_eq!(WINSOCK_LSP.artifact_type, ArtifactType::RegistryKey);
7510        assert_eq!(WINSOCK_LSP.scope, DataScope::System);
7511        assert!(WINSOCK_LSP.mitre_techniques.contains(&"T1547.010"));
7512    }
7513    #[test]
7514    fn appshim_db_md() {
7515        assert_eq!(APPSHIM_DB.id, "appshim_db");
7516        assert_eq!(APPSHIM_DB.artifact_type, ArtifactType::Directory);
7517        assert_eq!(APPSHIM_DB.scope, DataScope::System);
7518        assert!(APPSHIM_DB.mitre_techniques.contains(&"T1546.011"));
7519    }
7520    #[test]
7521    fn password_filter_dll_md() {
7522        assert_eq!(PASSWORD_FILTER_DLL.id, "password_filter_dll");
7523        assert_eq!(
7524            PASSWORD_FILTER_DLL.artifact_type,
7525            ArtifactType::RegistryValue
7526        );
7527        assert_eq!(PASSWORD_FILTER_DLL.scope, DataScope::System);
7528        assert!(PASSWORD_FILTER_DLL.mitre_techniques.contains(&"T1556.002"));
7529    }
7530    #[test]
7531    fn office_normal_dotm_md() {
7532        assert_eq!(OFFICE_NORMAL_DOTM.id, "office_normal_dotm");
7533        assert_eq!(OFFICE_NORMAL_DOTM.artifact_type, ArtifactType::File);
7534        assert_eq!(OFFICE_NORMAL_DOTM.scope, DataScope::User);
7535        assert!(OFFICE_NORMAL_DOTM.mitre_techniques.contains(&"T1137.001"));
7536    }
7537    #[test]
7538    fn powershell_profile_all_md() {
7539        assert_eq!(POWERSHELL_PROFILE_ALL.id, "powershell_profile_all");
7540        assert_eq!(POWERSHELL_PROFILE_ALL.artifact_type, ArtifactType::File);
7541        assert_eq!(POWERSHELL_PROFILE_ALL.scope, DataScope::System);
7542        assert!(POWERSHELL_PROFILE_ALL
7543            .mitre_techniques
7544            .contains(&"T1546.013"));
7545    }
7546
7547    // ── Windows credentials ───────────────────────────────────────────────
7548
7549    #[test]
7550    fn dpapi_system_masterkey_md() {
7551        assert_eq!(DPAPI_SYSTEM_MASTERKEY.id, "dpapi_system_masterkey");
7552        assert_eq!(
7553            DPAPI_SYSTEM_MASTERKEY.artifact_type,
7554            ArtifactType::Directory
7555        );
7556        assert_eq!(DPAPI_SYSTEM_MASTERKEY.scope, DataScope::System);
7557        assert!(DPAPI_SYSTEM_MASTERKEY
7558            .mitre_techniques
7559            .contains(&"T1555.004"));
7560    }
7561    #[test]
7562    fn dpapi_credhist_md() {
7563        assert_eq!(DPAPI_CREDHIST.id, "dpapi_credhist");
7564        assert_eq!(DPAPI_CREDHIST.artifact_type, ArtifactType::File);
7565        assert_eq!(DPAPI_CREDHIST.scope, DataScope::User);
7566        assert!(DPAPI_CREDHIST.mitre_techniques.contains(&"T1555.004"));
7567    }
7568    #[test]
7569    fn chrome_cookies_md() {
7570        assert_eq!(CHROME_COOKIES.id, "chrome_cookies");
7571        assert_eq!(CHROME_COOKIES.artifact_type, ArtifactType::File);
7572        assert_eq!(CHROME_COOKIES.scope, DataScope::User);
7573        assert!(CHROME_COOKIES.mitre_techniques.contains(&"T1539"));
7574    }
7575    #[test]
7576    fn edge_webcache_md() {
7577        assert_eq!(EDGE_WEBCACHE.id, "edge_webcache");
7578        assert_eq!(EDGE_WEBCACHE.artifact_type, ArtifactType::Directory);
7579        assert_eq!(EDGE_WEBCACHE.scope, DataScope::User);
7580        assert!(EDGE_WEBCACHE.mitre_techniques.contains(&"T1539"));
7581    }
7582    #[test]
7583    fn vpn_ras_phonebook_md() {
7584        assert_eq!(VPN_RAS_PHONEBOOK.id, "vpn_ras_phonebook");
7585        assert_eq!(VPN_RAS_PHONEBOOK.artifact_type, ArtifactType::File);
7586        assert_eq!(VPN_RAS_PHONEBOOK.scope, DataScope::User);
7587        assert!(VPN_RAS_PHONEBOOK.mitre_techniques.contains(&"T1552.001"));
7588    }
7589    #[test]
7590    fn windows_hello_ngc_md() {
7591        assert_eq!(WINDOWS_HELLO_NGC.id, "windows_hello_ngc");
7592        assert_eq!(WINDOWS_HELLO_NGC.artifact_type, ArtifactType::Directory);
7593        assert_eq!(WINDOWS_HELLO_NGC.scope, DataScope::System);
7594        assert!(WINDOWS_HELLO_NGC.mitre_techniques.contains(&"T1555"));
7595    }
7596    #[test]
7597    fn user_cert_private_key_md() {
7598        assert_eq!(USER_CERT_PRIVATE_KEY.id, "user_cert_private_key");
7599        assert_eq!(USER_CERT_PRIVATE_KEY.artifact_type, ArtifactType::Directory);
7600        assert_eq!(USER_CERT_PRIVATE_KEY.scope, DataScope::User);
7601        assert!(USER_CERT_PRIVATE_KEY
7602            .mitre_techniques
7603            .contains(&"T1552.004"));
7604    }
7605    #[test]
7606    fn machine_cert_store_md() {
7607        assert_eq!(MACHINE_CERT_STORE.id, "machine_cert_store");
7608        assert_eq!(MACHINE_CERT_STORE.artifact_type, ArtifactType::Directory);
7609        assert_eq!(MACHINE_CERT_STORE.scope, DataScope::System);
7610        assert!(MACHINE_CERT_STORE.mitre_techniques.contains(&"T1552.004"));
7611    }
7612
7613    // ── CATALOG completeness (batch E) ────────────────────────────────────
7614
7615    #[test]
7616    fn catalog_contains_batch_e() {
7617        let ids: Vec<&str> = CATALOG.list().iter().map(|d| d.id).collect();
7618        for expected in &[
7619            "lnk_files",
7620            "jump_list_auto",
7621            "jump_list_custom",
7622            "evtx_dir",
7623            "usn_journal",
7624            "wmi_mof_dir",
7625            "bits_db",
7626            "wmi_subscriptions",
7627            "logon_scripts",
7628            "winsock_lsp",
7629            "appshim_db",
7630            "password_filter_dll",
7631            "office_normal_dotm",
7632            "powershell_profile_all",
7633            "dpapi_system_masterkey",
7634            "dpapi_credhist",
7635            "chrome_cookies",
7636            "edge_webcache",
7637            "vpn_ras_phonebook",
7638            "windows_hello_ngc",
7639            "user_cert_private_key",
7640            "machine_cert_store",
7641        ] {
7642            assert!(ids.contains(expected), "CATALOG missing: {expected}");
7643        }
7644    }
7645
7646    // ═══════════════════════════════════════════════════════════════════════
7647    // Batch F — Linux extended credential / execution artifacts (RED)
7648    // ═══════════════════════════════════════════════════════════════════════
7649
7650    #[test]
7651    fn linux_at_queue_md() {
7652        assert_eq!(LINUX_AT_QUEUE.id, "linux_at_queue");
7653        assert_eq!(LINUX_AT_QUEUE.artifact_type, ArtifactType::Directory);
7654        assert_eq!(LINUX_AT_QUEUE.scope, DataScope::System);
7655        assert!(LINUX_AT_QUEUE.mitre_techniques.contains(&"T1053.001"));
7656    }
7657    #[test]
7658    fn linux_sshd_config_md() {
7659        assert_eq!(LINUX_SSHD_CONFIG.id, "linux_sshd_config");
7660        assert_eq!(LINUX_SSHD_CONFIG.artifact_type, ArtifactType::File);
7661        assert_eq!(LINUX_SSHD_CONFIG.scope, DataScope::System);
7662        assert!(LINUX_SSHD_CONFIG.mitre_techniques.contains(&"T1098.004"));
7663    }
7664    #[test]
7665    fn linux_etc_group_md() {
7666        assert_eq!(LINUX_ETC_GROUP.id, "linux_etc_group");
7667        assert_eq!(LINUX_ETC_GROUP.artifact_type, ArtifactType::File);
7668        assert_eq!(LINUX_ETC_GROUP.scope, DataScope::System);
7669        assert!(LINUX_ETC_GROUP.mitre_techniques.contains(&"T1087.001"));
7670    }
7671    #[test]
7672    fn linux_gnome_keyring_md() {
7673        assert_eq!(LINUX_GNOME_KEYRING.id, "linux_gnome_keyring");
7674        assert_eq!(LINUX_GNOME_KEYRING.artifact_type, ArtifactType::Directory);
7675        assert_eq!(LINUX_GNOME_KEYRING.scope, DataScope::User);
7676        assert!(LINUX_GNOME_KEYRING.mitre_techniques.contains(&"T1555.003"));
7677    }
7678    #[test]
7679    fn linux_kde_kwallet_md() {
7680        assert_eq!(LINUX_KDE_KWALLET.id, "linux_kde_kwallet");
7681        assert_eq!(LINUX_KDE_KWALLET.artifact_type, ArtifactType::Directory);
7682        assert_eq!(LINUX_KDE_KWALLET.scope, DataScope::User);
7683        assert!(LINUX_KDE_KWALLET.mitre_techniques.contains(&"T1555.003"));
7684    }
7685    #[test]
7686    fn linux_chrome_login_linux_md() {
7687        assert_eq!(LINUX_CHROME_LOGIN_LINUX.id, "linux_chrome_login_linux");
7688        assert_eq!(LINUX_CHROME_LOGIN_LINUX.artifact_type, ArtifactType::File);
7689        assert_eq!(LINUX_CHROME_LOGIN_LINUX.scope, DataScope::User);
7690        assert!(LINUX_CHROME_LOGIN_LINUX
7691            .mitre_techniques
7692            .contains(&"T1555.003"));
7693    }
7694    #[test]
7695    fn linux_firefox_logins_linux_md() {
7696        assert_eq!(LINUX_FIREFOX_LOGINS_LINUX.id, "linux_firefox_logins_linux");
7697        assert_eq!(LINUX_FIREFOX_LOGINS_LINUX.artifact_type, ArtifactType::File);
7698        assert_eq!(LINUX_FIREFOX_LOGINS_LINUX.scope, DataScope::User);
7699        assert!(LINUX_FIREFOX_LOGINS_LINUX
7700            .mitre_techniques
7701            .contains(&"T1555.003"));
7702    }
7703    #[test]
7704    fn linux_utmp_md() {
7705        assert_eq!(LINUX_UTMP.id, "linux_utmp");
7706        assert_eq!(LINUX_UTMP.artifact_type, ArtifactType::File);
7707        assert_eq!(LINUX_UTMP.scope, DataScope::System);
7708        assert!(LINUX_UTMP.mitre_techniques.contains(&"T1078"));
7709    }
7710    #[test]
7711    fn linux_gcp_credentials_md() {
7712        assert_eq!(LINUX_GCP_CREDENTIALS.id, "linux_gcp_credentials");
7713        assert_eq!(LINUX_GCP_CREDENTIALS.artifact_type, ArtifactType::Directory);
7714        assert_eq!(LINUX_GCP_CREDENTIALS.scope, DataScope::User);
7715        assert!(LINUX_GCP_CREDENTIALS
7716            .mitre_techniques
7717            .contains(&"T1552.001"));
7718    }
7719    #[test]
7720    fn linux_azure_credentials_md() {
7721        assert_eq!(LINUX_AZURE_CREDENTIALS.id, "linux_azure_credentials");
7722        assert_eq!(
7723            LINUX_AZURE_CREDENTIALS.artifact_type,
7724            ArtifactType::Directory
7725        );
7726        assert_eq!(LINUX_AZURE_CREDENTIALS.scope, DataScope::User);
7727        assert!(LINUX_AZURE_CREDENTIALS
7728            .mitre_techniques
7729            .contains(&"T1552.001"));
7730    }
7731    #[test]
7732    fn linux_kube_config_md() {
7733        assert_eq!(LINUX_KUBE_CONFIG.id, "linux_kube_config");
7734        assert_eq!(LINUX_KUBE_CONFIG.artifact_type, ArtifactType::File);
7735        assert_eq!(LINUX_KUBE_CONFIG.scope, DataScope::User);
7736        assert!(LINUX_KUBE_CONFIG.mitre_techniques.contains(&"T1552.001"));
7737    }
7738    #[test]
7739    fn linux_git_credentials_md() {
7740        assert_eq!(LINUX_GIT_CREDENTIALS.id, "linux_git_credentials");
7741        assert_eq!(LINUX_GIT_CREDENTIALS.artifact_type, ArtifactType::File);
7742        assert_eq!(LINUX_GIT_CREDENTIALS.scope, DataScope::User);
7743        assert!(LINUX_GIT_CREDENTIALS
7744            .mitre_techniques
7745            .contains(&"T1552.001"));
7746    }
7747    #[test]
7748    fn linux_netrc_md() {
7749        assert_eq!(LINUX_NETRC.id, "linux_netrc");
7750        assert_eq!(LINUX_NETRC.artifact_type, ArtifactType::File);
7751        assert_eq!(LINUX_NETRC.scope, DataScope::User);
7752        assert!(LINUX_NETRC.mitre_techniques.contains(&"T1552.001"));
7753    }
7754
7755    // ── CATALOG completeness (batch F) ────────────────────────────────────
7756
7757    #[test]
7758    fn catalog_contains_batch_f() {
7759        let ids: Vec<&str> = CATALOG.list().iter().map(|d| d.id).collect();
7760        for expected in &[
7761            "linux_at_queue",
7762            "linux_sshd_config",
7763            "linux_etc_group",
7764            "linux_gnome_keyring",
7765            "linux_kde_kwallet",
7766            "linux_chrome_login_linux",
7767            "linux_firefox_logins_linux",
7768            "linux_utmp",
7769            "linux_gcp_credentials",
7770            "linux_azure_credentials",
7771            "linux_kube_config",
7772            "linux_git_credentials",
7773            "linux_netrc",
7774        ] {
7775            assert!(ids.contains(expected), "CATALOG missing: {expected}");
7776        }
7777    }
7778
7779    // ═══════════════════════════════════════════════════════════════════════
7780    // Batch G — LinuxPersist-sourced artifacts (RED)
7781    // Source: https://github.com/GuyEldad/LinuxPersist
7782    // ═══════════════════════════════════════════════════════════════════════
7783
7784    #[test]
7785    fn linux_etc_environment_md() {
7786        assert_eq!(LINUX_ETC_ENVIRONMENT.id, "linux_etc_environment");
7787        assert_eq!(LINUX_ETC_ENVIRONMENT.artifact_type, ArtifactType::File);
7788        assert_eq!(LINUX_ETC_ENVIRONMENT.scope, DataScope::System);
7789        assert!(LINUX_ETC_ENVIRONMENT
7790            .mitre_techniques
7791            .contains(&"T1546.004"));
7792    }
7793    #[test]
7794    fn linux_xdg_autostart_user_md() {
7795        assert_eq!(LINUX_XDG_AUTOSTART_USER.id, "linux_xdg_autostart_user");
7796        assert_eq!(
7797            LINUX_XDG_AUTOSTART_USER.artifact_type,
7798            ArtifactType::Directory
7799        );
7800        assert_eq!(LINUX_XDG_AUTOSTART_USER.scope, DataScope::User);
7801        assert!(LINUX_XDG_AUTOSTART_USER
7802            .mitre_techniques
7803            .contains(&"T1547.014"));
7804    }
7805    #[test]
7806    fn linux_xdg_autostart_system_md() {
7807        assert_eq!(LINUX_XDG_AUTOSTART_SYSTEM.id, "linux_xdg_autostart_system");
7808        assert_eq!(
7809            LINUX_XDG_AUTOSTART_SYSTEM.artifact_type,
7810            ArtifactType::Directory
7811        );
7812        assert_eq!(LINUX_XDG_AUTOSTART_SYSTEM.scope, DataScope::System);
7813        assert!(LINUX_XDG_AUTOSTART_SYSTEM
7814            .mitre_techniques
7815            .contains(&"T1547.014"));
7816    }
7817    #[test]
7818    fn linux_networkmanager_dispatcher_md() {
7819        assert_eq!(
7820            LINUX_NETWORKMANAGER_DISPATCHER.id,
7821            "linux_networkmanager_dispatcher"
7822        );
7823        assert_eq!(
7824            LINUX_NETWORKMANAGER_DISPATCHER.artifact_type,
7825            ArtifactType::Directory
7826        );
7827        assert_eq!(LINUX_NETWORKMANAGER_DISPATCHER.scope, DataScope::System);
7828        assert!(LINUX_NETWORKMANAGER_DISPATCHER
7829            .mitre_techniques
7830            .contains(&"T1547.013"));
7831    }
7832    #[test]
7833    fn linux_apt_hooks_md() {
7834        assert_eq!(LINUX_APT_HOOKS.id, "linux_apt_hooks");
7835        assert_eq!(LINUX_APT_HOOKS.artifact_type, ArtifactType::Directory);
7836        assert_eq!(LINUX_APT_HOOKS.scope, DataScope::System);
7837        assert_eq!(LINUX_APT_HOOKS.os_scope, OsScope::LinuxDebian);
7838        assert!(LINUX_APT_HOOKS.mitre_techniques.contains(&"T1546.004"));
7839    }
7840
7841    // ── CATALOG completeness (batch G) ────────────────────────────────────
7842
7843    #[test]
7844    fn catalog_contains_batch_g() {
7845        let ids: Vec<&str> = CATALOG.list().iter().map(|d| d.id).collect();
7846        for expected in &[
7847            "linux_etc_environment",
7848            "linux_xdg_autostart_user",
7849            "linux_xdg_autostart_system",
7850            "linux_networkmanager_dispatcher",
7851            "linux_apt_hooks",
7852        ] {
7853            assert!(ids.contains(expected), "CATALOG missing: {expected}");
7854        }
7855    }
7856
7857    // ═══════════════════════════════════════════════════════════════════════
7858    // Batch H — Jump List / LNK / Prefetch / SRUM tables / EVTX channels
7859    // ═══════════════════════════════════════════════════════════════════════
7860
7861    // ── Jump Lists ────────────────────────────────────────────────────────
7862
7863    #[test]
7864    fn jump_list_system_md() {
7865        assert_eq!(JUMP_LIST_SYSTEM.id, "jump_list_system");
7866        assert_eq!(JUMP_LIST_SYSTEM.artifact_type, ArtifactType::Directory);
7867        assert_eq!(JUMP_LIST_SYSTEM.scope, DataScope::System);
7868        assert!(JUMP_LIST_SYSTEM.mitre_techniques.contains(&"T1547.009"));
7869    }
7870
7871    // ── LNK Files ─────────────────────────────────────────────────────────
7872
7873    #[test]
7874    fn lnk_files_office_md() {
7875        assert_eq!(LNK_FILES_OFFICE.id, "lnk_files_office");
7876        assert_eq!(LNK_FILES_OFFICE.artifact_type, ArtifactType::Directory);
7877        assert_eq!(LNK_FILES_OFFICE.scope, DataScope::User);
7878        assert!(LNK_FILES_OFFICE.mitre_techniques.contains(&"T1547.009"));
7879    }
7880
7881    // ── Prefetch ──────────────────────────────────────────────────────────
7882
7883    #[test]
7884    fn prefetch_file_md() {
7885        assert_eq!(PREFETCH_FILE.id, "prefetch_file");
7886        assert_eq!(PREFETCH_FILE.artifact_type, ArtifactType::File);
7887        assert_eq!(PREFETCH_FILE.scope, DataScope::System);
7888        assert_eq!(PREFETCH_FILE.os_scope, OsScope::Win7Plus);
7889        assert!(PREFETCH_FILE.mitre_techniques.contains(&"T1059"));
7890    }
7891
7892    // ── SRUM tables ───────────────────────────────────────────────────────
7893
7894    #[test]
7895    fn srum_network_usage_md() {
7896        assert_eq!(SRUM_NETWORK_USAGE.id, "srum_network_usage");
7897        assert_eq!(SRUM_NETWORK_USAGE.artifact_type, ArtifactType::File);
7898        assert_eq!(SRUM_NETWORK_USAGE.scope, DataScope::System);
7899        assert_eq!(SRUM_NETWORK_USAGE.os_scope, OsScope::Win8Plus);
7900        assert!(SRUM_NETWORK_USAGE.mitre_techniques.contains(&"T1049"));
7901    }
7902    #[test]
7903    fn srum_app_resource_md() {
7904        assert_eq!(SRUM_APP_RESOURCE.id, "srum_app_resource");
7905        assert_eq!(SRUM_APP_RESOURCE.artifact_type, ArtifactType::File);
7906        assert_eq!(SRUM_APP_RESOURCE.scope, DataScope::System);
7907        assert_eq!(SRUM_APP_RESOURCE.os_scope, OsScope::Win8Plus);
7908        assert!(SRUM_APP_RESOURCE.mitre_techniques.contains(&"T1059"));
7909    }
7910    #[test]
7911    fn srum_energy_usage_md() {
7912        assert_eq!(SRUM_ENERGY_USAGE.id, "srum_energy_usage");
7913        assert_eq!(SRUM_ENERGY_USAGE.artifact_type, ArtifactType::File);
7914        assert_eq!(SRUM_ENERGY_USAGE.scope, DataScope::System);
7915        assert_eq!(SRUM_ENERGY_USAGE.os_scope, OsScope::Win8Plus);
7916        assert!(SRUM_ENERGY_USAGE.mitre_techniques.contains(&"T1059"));
7917    }
7918    #[test]
7919    fn srum_push_notification_md() {
7920        assert_eq!(SRUM_PUSH_NOTIFICATION.id, "srum_push_notification");
7921        assert_eq!(SRUM_PUSH_NOTIFICATION.artifact_type, ArtifactType::File);
7922        assert_eq!(SRUM_PUSH_NOTIFICATION.scope, DataScope::System);
7923        assert_eq!(SRUM_PUSH_NOTIFICATION.os_scope, OsScope::Win10Plus);
7924        assert!(SRUM_PUSH_NOTIFICATION.mitre_techniques.contains(&"T1059"));
7925    }
7926
7927    // ── EVTX channels ─────────────────────────────────────────────────────
7928
7929    #[test]
7930    fn evtx_security_md() {
7931        assert_eq!(EVTX_SECURITY.id, "evtx_security");
7932        assert_eq!(EVTX_SECURITY.artifact_type, ArtifactType::File);
7933        assert_eq!(EVTX_SECURITY.scope, DataScope::System);
7934        assert!(EVTX_SECURITY.mitre_techniques.contains(&"T1070.001"));
7935    }
7936    #[test]
7937    fn evtx_system_md() {
7938        assert_eq!(EVTX_SYSTEM.id, "evtx_system");
7939        assert_eq!(EVTX_SYSTEM.artifact_type, ArtifactType::File);
7940        assert_eq!(EVTX_SYSTEM.scope, DataScope::System);
7941        assert!(EVTX_SYSTEM.mitre_techniques.contains(&"T1543.003"));
7942    }
7943    #[test]
7944    fn evtx_powershell_md() {
7945        assert_eq!(EVTX_POWERSHELL.id, "evtx_powershell");
7946        assert_eq!(EVTX_POWERSHELL.artifact_type, ArtifactType::File);
7947        assert_eq!(EVTX_POWERSHELL.scope, DataScope::System);
7948        assert!(EVTX_POWERSHELL.mitre_techniques.contains(&"T1059.001"));
7949    }
7950    #[test]
7951    fn evtx_sysmon_md() {
7952        assert_eq!(EVTX_SYSMON.id, "evtx_sysmon");
7953        assert_eq!(EVTX_SYSMON.artifact_type, ArtifactType::File);
7954        assert_eq!(EVTX_SYSMON.scope, DataScope::System);
7955        assert!(EVTX_SYSMON.mitre_techniques.contains(&"T1059"));
7956    }
7957
7958    // ═══════════════════════════════════════════════════════════════════════
7959    // Enhancement I — retention / triage_priority / related_artifacts (RED)
7960    // ═══════════════════════════════════════════════════════════════════════
7961
7962    // ── TriagePriority enum exists and is ordered ─────────────────────────
7963
7964    #[test]
7965    fn triage_priority_ordering() {
7966        assert!(TriagePriority::Critical > TriagePriority::High);
7967        assert!(TriagePriority::High > TriagePriority::Medium);
7968        assert!(TriagePriority::Medium > TriagePriority::Low);
7969    }
7970
7971    // ── ArtifactDescriptor has new fields ─────────────────────────────────
7972
7973    #[test]
7974    fn descriptor_has_retention_field() {
7975        // retention is Option<&str>; registry persistence keys are indefinite
7976        assert_eq!(RUN_KEY_HKLM_RUN.retention, None);
7977    }
7978
7979    #[test]
7980    fn descriptor_has_triage_priority_field() {
7981        assert_eq!(RUN_KEY_HKLM_RUN.triage_priority, TriagePriority::High);
7982    }
7983
7984    #[test]
7985    fn descriptor_has_related_artifacts_field() {
7986        let _ = RUN_KEY_HKLM_RUN.related_artifacts;
7987    }
7988
7989    // ── Specific retention values ─────────────────────────────────────────
7990
7991    #[test]
7992    fn srum_retention_is_30_days() {
7993        assert_eq!(SRUM_DB.retention, Some("~30 days"));
7994    }
7995
7996    #[test]
7997    fn shimcache_retention_mentions_shutdown() {
7998        assert!(SHIMCACHE.retention.unwrap_or("").contains("shutdown"));
7999    }
8000
8001    #[test]
8002    fn powershell_history_retention_mentions_limit() {
8003        assert!(POWERSHELL_HISTORY.retention.unwrap_or("").contains("4096"));
8004    }
8005
8006    #[test]
8007    fn bam_user_retention_is_7_days() {
8008        assert!(BAM_USER.retention.unwrap_or("").contains("7 day"));
8009    }
8010
8011    #[test]
8012    fn evtx_security_retention_mentions_rolling() {
8013        assert!(EVTX_SECURITY.retention.unwrap_or("").contains("rolling"));
8014    }
8015
8016    // ── Specific triage_priority values ──────────────────────────────────
8017
8018    #[test]
8019    fn evtx_security_triage_is_critical() {
8020        assert_eq!(EVTX_SECURITY.triage_priority, TriagePriority::Critical);
8021    }
8022
8023    #[test]
8024    fn sam_users_triage_is_critical() {
8025        assert_eq!(SAM_USERS.triage_priority, TriagePriority::Critical);
8026    }
8027
8028    #[test]
8029    fn lsa_secrets_triage_is_critical() {
8030        assert_eq!(LSA_SECRETS.triage_priority, TriagePriority::Critical);
8031    }
8032
8033    #[test]
8034    fn linux_shadow_triage_is_critical() {
8035        assert_eq!(LINUX_SHADOW.triage_priority, TriagePriority::Critical);
8036    }
8037
8038    #[test]
8039    fn shimcache_triage_is_critical() {
8040        assert_eq!(SHIMCACHE.triage_priority, TriagePriority::Critical);
8041    }
8042
8043    #[test]
8044    fn userassist_exe_triage_is_high() {
8045        assert_eq!(USERASSIST_EXE.triage_priority, TriagePriority::High);
8046    }
8047
8048    #[test]
8049    fn thumbcache_triage_is_medium() {
8050        assert_eq!(THUMBCACHE.triage_priority, TriagePriority::Medium);
8051    }
8052
8053    #[test]
8054    fn vpn_ras_phonebook_triage_is_low() {
8055        assert_eq!(VPN_RAS_PHONEBOOK.triage_priority, TriagePriority::Low);
8056    }
8057
8058    // ── related_artifacts ────────────────────────────────────────────────
8059
8060    #[test]
8061    fn srum_network_related_includes_evtx_security() {
8062        assert!(SRUM_NETWORK_USAGE
8063            .related_artifacts
8064            .contains(&"evtx_security"));
8065    }
8066
8067    #[test]
8068    fn evtx_security_related_includes_srum() {
8069        assert!(EVTX_SECURITY
8070            .related_artifacts
8071            .contains(&"srum_network_usage"));
8072    }
8073
8074    #[test]
8075    fn prefetch_file_related_includes_shimcache() {
8076        assert!(PREFETCH_FILE.related_artifacts.contains(&"shimcache"));
8077    }
8078
8079    #[test]
8080    fn dpapi_masterkey_related_includes_dpapi_cred() {
8081        assert!(DPAPI_MASTERKEY_USER
8082            .related_artifacts
8083            .contains(&"dpapi_cred_user"));
8084    }
8085
8086    // ── New catalog API ──────────────────────────────────────────────────
8087
8088    #[test]
8089    fn catalog_by_mitre_finds_srum_network_usage() {
8090        let hits = CATALOG.by_mitre("T1049");
8091        assert!(hits.iter().any(|d| d.id == "srum_network_usage"));
8092    }
8093
8094    #[test]
8095    fn catalog_by_mitre_finds_no_results_for_unknown() {
8096        assert!(CATALOG.by_mitre("T9999.999").is_empty());
8097    }
8098
8099    #[test]
8100    fn catalog_for_triage_nonempty_and_critical_first() {
8101        let hits = CATALOG.for_triage();
8102        assert!(!hits.is_empty());
8103        assert_eq!(hits[0].triage_priority, TriagePriority::Critical);
8104        // Last entry must not be higher priority than Medium
8105        assert!(hits.last().unwrap().triage_priority <= TriagePriority::Medium);
8106    }
8107
8108    #[test]
8109    fn catalog_for_triage_stable_within_priority() {
8110        // All Criticals before any High; all Highs before any Medium
8111        let hits = CATALOG.for_triage();
8112        let mut max_seen = TriagePriority::Critical;
8113        for d in &hits {
8114            assert!(d.triage_priority <= max_seen, "priority not monotone");
8115            max_seen = d.triage_priority;
8116        }
8117    }
8118
8119    #[test]
8120    fn catalog_filter_by_keyword_finds_dpapi() {
8121        let hits = CATALOG.filter_by_keyword("DPAPI");
8122        assert!(!hits.is_empty());
8123        assert!(hits.iter().any(|d| d.id.contains("dpapi")));
8124    }
8125
8126    #[test]
8127    fn catalog_filter_by_keyword_case_insensitive() {
8128        let lower = CATALOG.filter_by_keyword("dpapi");
8129        let upper = CATALOG.filter_by_keyword("DPAPI");
8130        assert_eq!(lower.len(), upper.len());
8131    }
8132
8133    // ── CATALOG completeness (batch H) ────────────────────────────────────
8134
8135    // ── sources field — every high-value descriptor must cite at least one
8136    //    authoritative external reference (SANS, Harlan Carvey, Brian Carrier,
8137    //    Red Canary, Microsoft docs, MITRE ATT&CK, etc.)  ────────────────────
8138
8139    #[test]
8140    fn userassist_has_authoritative_sources() {
8141        assert!(
8142            !USERASSIST_EXE.sources.is_empty(),
8143            "USERASSIST_EXE must cite at least one authoritative source"
8144        );
8145    }
8146
8147    #[test]
8148    fn run_key_hklm_has_authoritative_sources() {
8149        assert!(
8150            !RUN_KEY_HKLM_RUN.sources.is_empty(),
8151            "RUN_KEY_HKLM_RUN must cite at least one authoritative source"
8152        );
8153    }
8154
8155    #[test]
8156    fn shimcache_has_authoritative_sources() {
8157        assert!(
8158            !SHIMCACHE.sources.is_empty(),
8159            "SHIMCACHE must cite at least one authoritative source"
8160        );
8161    }
8162
8163    #[test]
8164    fn prefetch_dir_has_authoritative_sources() {
8165        assert!(
8166            !PREFETCH_DIR.sources.is_empty(),
8167            "PREFETCH_DIR must cite at least one authoritative source"
8168        );
8169    }
8170
8171    #[test]
8172    fn amcache_has_authoritative_sources() {
8173        assert!(
8174            !AMCACHE_APP_FILE.sources.is_empty(),
8175            "AMCACHE_APP_FILE must cite at least one authoritative source"
8176        );
8177    }
8178
8179    #[test]
8180    fn evtx_security_has_authoritative_sources() {
8181        assert!(
8182            !EVTX_SECURITY.sources.is_empty(),
8183            "EVTX_SECURITY must cite at least one authoritative source"
8184        );
8185    }
8186
8187    #[test]
8188    fn srum_app_resource_has_authoritative_sources() {
8189        assert!(
8190            !SRUM_APP_RESOURCE.sources.is_empty(),
8191            "SRUM_APP_RESOURCE must cite at least one authoritative source"
8192        );
8193    }
8194
8195    #[test]
8196    fn sam_users_has_authoritative_sources() {
8197        assert!(
8198            !SAM_USERS.sources.is_empty(),
8199            "SAM_USERS must cite at least one authoritative source"
8200        );
8201    }
8202
8203    #[test]
8204    fn shellbags_has_authoritative_sources() {
8205        assert!(
8206            !SHELLBAGS_USER.sources.is_empty(),
8207            "SHELLBAGS_USER must cite at least one authoritative source"
8208        );
8209    }
8210
8211    #[test]
8212    fn no_descriptor_in_catalog_has_empty_sources() {
8213        let empty: Vec<&str> = CATALOG
8214            .list()
8215            .iter()
8216            .filter(|d| d.sources.is_empty())
8217            .map(|d| d.id)
8218            .collect();
8219        assert!(
8220            empty.is_empty(),
8221            "These catalog entries have no authoritative sources: {empty:?}"
8222        );
8223    }
8224
8225    #[test]
8226    fn catalog_contains_batch_h() {
8227        let ids: Vec<&str> = CATALOG.list().iter().map(|d| d.id).collect();
8228        for expected in &[
8229            "jump_list_system",
8230            "lnk_files_office",
8231            "prefetch_file",
8232            "srum_network_usage",
8233            "srum_app_resource",
8234            "srum_energy_usage",
8235            "srum_push_notification",
8236            "evtx_security",
8237            "evtx_system",
8238            "evtx_powershell",
8239            "evtx_sysmon",
8240        ] {
8241            assert!(ids.contains(expected), "CATALOG missing: {expected}");
8242        }
8243    }
8244}