1use crate::encoding::Encoding;
2use crate::event::Event;
3use std::future::Future;
4
5use crate::match_validation::{
6 config::InternalMatchValidationType, config::MatchValidationType, match_status::MatchStatus,
7 match_validator::MatchValidator,
8};
9
10#[cfg(feature = "third-party-active-checkers")]
11use error::MatchValidatorCreationError;
12
13use self::metrics::ScannerMetrics;
14#[cfg(feature = "third-party-active-checkers")]
15use crate::match_validation::match_validator::RAYON_THREAD_POOL;
16use crate::observability::labels::Labels;
17use crate::rule_match::{InternalRuleMatch, RuleMatch};
18use crate::scanner::config::RuleConfig;
19use crate::scanner::internal_rule_match_set::InternalRuleMatchSet;
20use crate::scanner::regex_rule::compiled::RegexCompiledRule;
21use crate::scanner::regex_rule::{RegexCaches, access_regex_caches};
22use crate::scanner::scope::Scope;
23pub use crate::scanner::shared_data::SharedData;
24use crate::scanner::suppression::{CompiledSuppressions, SuppressionValidationError, Suppressions};
25use crate::scoped_ruleset::{ContentVisitor, ExclusionCheck, ScopedRuleSet};
26pub use crate::secondary_validation::Validator;
27use crate::stats::GLOBAL_STATS;
28use crate::tokio::TOKIO_RUNTIME;
29use crate::{CreateScannerError, EncodeIndices, MatchAction, Path, ScannerError};
30use ::metrics::counter;
31use ahash::AHashMap;
32use futures::executor::block_on;
33use serde::{Deserialize, Serialize};
34use serde_with::serde_as;
35use std::ops::Deref;
36use std::pin::Pin;
37use std::sync::Arc;
38use std::time::{Duration, Instant};
39use tokio::task::JoinHandle;
40use tokio::time::timeout;
41
42pub mod config;
43pub mod debug_scan;
44pub mod error;
45pub mod metrics;
46pub mod regex_rule;
47pub mod scope;
48pub mod shared_data;
49pub mod shared_pool;
50pub mod suppression;
51
52mod internal_rule_match_set;
53#[cfg(test)]
54mod test;
55
56#[derive(Clone)]
57pub struct StringMatch {
58 pub start: usize,
59 pub end: usize,
60 pub keyword: Option<String>,
62}
63
64pub trait MatchEmitter<T = ()> {
65 fn emit(&mut self, string_match: StringMatch) -> T;
66}
67
68impl<F, T> MatchEmitter<T> for F
71where
72 F: FnMut(StringMatch) -> T,
73{
74 fn emit(&mut self, string_match: StringMatch) -> T {
75 (self)(string_match)
77 }
78}
79
80#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Default)]
89pub enum Precedence {
90 Catchall,
91 Generic,
92 #[default]
93 Specific,
94}
95
96#[serde_as]
97#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
98pub struct RootRuleConfig<T> {
99 #[serde(default)]
100 pub match_action: MatchAction,
101 #[serde(default)]
102 pub scope: Scope,
103 #[deprecated(note = "Use `third_party_active_checker` instead")]
104 match_validation_type: Option<MatchValidationType>,
105 third_party_active_checker: Option<MatchValidationType>,
106 suppressions: Option<Suppressions>,
107 #[serde(default)]
108 precedence: Precedence,
109 #[serde(default)]
110 pub is_supporting_rule: bool,
111 #[serde(default)]
115 pub tags: Vec<String>,
116 #[serde(flatten)]
117 pub inner: T,
118}
119
120impl<T> RootRuleConfig<T>
121where
122 T: RuleConfig + 'static,
123{
124 pub fn new_dyn(inner: T) -> RootRuleConfig<Arc<dyn RuleConfig>> {
125 RootRuleConfig::new(Arc::new(inner) as Arc<dyn RuleConfig>)
126 }
127
128 pub fn into_dyn(self) -> RootRuleConfig<Arc<dyn RuleConfig>> {
129 self.map_inner(|x| Arc::new(x) as Arc<dyn RuleConfig>)
130 }
131}
132
133impl<T> RootRuleConfig<T> {
134 pub fn new(inner: T) -> Self {
135 #[allow(deprecated)]
136 Self {
137 match_action: MatchAction::None,
138 scope: Scope::all(),
139 match_validation_type: None,
140 third_party_active_checker: None,
141 suppressions: None,
142 precedence: Precedence::default(),
143 is_supporting_rule: false,
144 tags: Vec::new(),
145 inner,
146 }
147 }
148
149 pub fn map_inner<U>(self, func: impl FnOnce(T) -> U) -> RootRuleConfig<U> {
150 #[allow(deprecated)]
151 RootRuleConfig {
152 match_action: self.match_action,
153 scope: self.scope,
154 match_validation_type: self.match_validation_type,
155 third_party_active_checker: self.third_party_active_checker,
156 suppressions: self.suppressions,
157 precedence: self.precedence,
158 is_supporting_rule: self.is_supporting_rule,
159 tags: self.tags,
160 inner: func(self.inner),
161 }
162 }
163
164 pub fn match_action(mut self, action: MatchAction) -> Self {
165 self.match_action = action;
166 self
167 }
168
169 pub fn precedence(mut self, precedence: Precedence) -> Self {
170 self.precedence = precedence;
171 self
172 }
173
174 pub fn scope(mut self, scope: Scope) -> Self {
175 self.scope = scope;
176 self
177 }
178
179 pub fn third_party_active_checker(
180 mut self,
181 match_validation_type: MatchValidationType,
182 ) -> Self {
183 self.third_party_active_checker = Some(match_validation_type);
184 self
185 }
186
187 pub fn suppressions(mut self, suppressions: Suppressions) -> Self {
188 self.suppressions = Some(suppressions);
189 self
190 }
191
192 pub fn is_supporting_rule(mut self, value: bool) -> Self {
193 self.is_supporting_rule = value;
194 self
195 }
196
197 pub fn tags(mut self, tags: Vec<String>) -> Self {
198 self.tags = tags;
199 self
200 }
201
202 pub fn get_suppressions(&self) -> Option<&Suppressions> {
203 self.suppressions.as_ref()
204 }
205
206 fn get_third_party_active_checker(&self) -> Option<&MatchValidationType> {
207 #[allow(deprecated)]
208 self.third_party_active_checker
209 .as_ref()
210 .or(self.match_validation_type.as_ref())
211 }
212}
213
214impl<T> Deref for RootRuleConfig<T> {
215 type Target = T;
216
217 fn deref(&self) -> &Self::Target {
218 &self.inner
219 }
220}
221pub struct RootCompiledRule {
222 pub inner: Box<dyn CompiledRule>,
223 pub scope: Scope,
224 pub match_action: MatchAction,
225 pub match_validation_type: Option<MatchValidationType>,
226 pub suppressions: Option<CompiledSuppressions>,
227 pub precedence: Precedence,
228 pub is_supporting_rule: bool,
229 pub sds_rule_name: Option<String>,
233}
234
235impl RootCompiledRule {
236 pub fn internal_match_validation_type(&self) -> Option<InternalMatchValidationType> {
237 self.match_validation_type
238 .as_ref()
239 .map(|x| x.get_internal_match_validation_type())
240 }
241}
242
243impl Deref for RootCompiledRule {
244 type Target = dyn CompiledRule;
245
246 fn deref(&self) -> &Self::Target {
247 self.inner.as_ref()
248 }
249}
250
251pub struct StringMatchesCtx<'a> {
252 rule_index: usize,
253 pub regex_caches: &'a mut RegexCaches,
254 pub exclusion_check: &'a ExclusionCheck<'a>,
255 pub excluded_matches: &'a mut AHashMap<String, String>,
256 pub match_emitter: &'a mut dyn MatchEmitter,
257 pub wildcard_indices: Option<&'a Vec<(usize, usize)>>,
258 pub enable_debug_observability: bool,
259
260 pub per_string_data: &'a mut SharedData,
262 pub per_scanner_data: &'a SharedData,
263 pub per_event_data: &'a mut SharedData,
264 pub event_id: Option<&'a str>,
265
266 pub scan_metadata: &'a AHashMap<String, String>,
268}
269
270impl StringMatchesCtx<'_> {
271 pub fn process_async(
281 &self,
282 func: impl for<'a> FnOnce(
283 &'a mut AsyncStringMatchesCtx,
284 )
285 -> Pin<Box<dyn Future<Output = Result<(), ScannerError>> + Send + 'a>>
286 + Send
287 + 'static,
288 ) -> RuleResult {
289 let rule_index = self.rule_index;
290
291 let fut = TOKIO_RUNTIME.spawn(async move {
294 let start = Instant::now();
295 let mut ctx = AsyncStringMatchesCtx {
296 rule_matches: vec![],
297 };
298 (func)(&mut ctx).await?;
299 let io_duration = start.elapsed();
300
301 Ok(AsyncRuleInfo {
302 rule_index,
303 rule_matches: ctx.rule_matches,
304 io_duration,
305 })
306 });
307
308 Ok(RuleStatus::Pending(fut))
309 }
310}
311
312pub struct AsyncStringMatchesCtx {
313 rule_matches: Vec<StringMatch>,
314}
315
316impl AsyncStringMatchesCtx {
317 pub fn emit_match(&mut self, string_match: StringMatch) {
318 self.rule_matches.push(string_match);
319 }
320}
321
322#[must_use]
323pub enum RuleStatus {
324 Done,
325 Pending(PendingRuleResult),
326}
327
328pub type PendingRuleResult = JoinHandle<Result<AsyncRuleInfo, ScannerError>>;
330
331pub struct PendingRuleJob {
332 fut: PendingRuleResult,
333 path: Path<'static>,
334}
335
336pub struct AsyncRuleInfo {
337 rule_index: usize,
338 rule_matches: Vec<StringMatch>,
339 io_duration: Duration,
340}
341
342pub type RuleResult = Result<RuleStatus, ScannerError>;
344
345pub trait CompiledRule: Send + Sync {
347 fn init_per_scanner_data(&self, _per_scanner_data: &mut SharedData) {
348 }
350
351 fn init_per_string_data(&self, _labels: &Labels, _per_string_data: &mut SharedData) {
352 }
354
355 fn init_per_event_data(&self, _per_event_data: &mut SharedData) {
356 }
358
359 fn get_string_matches(
360 &self,
361 content: &str,
362 path: &Path,
363 ctx: &mut StringMatchesCtx<'_>,
364 ) -> RuleResult;
365
366 fn should_exclude_multipass_v0(&self) -> bool {
369 false
371 }
372
373 fn on_excluded_match_multipass_v0(
374 &self,
375 _path: &Path,
376 _excluded_path: &str,
377 _enable_debug_observability: bool,
378 ) {
379 }
381
382 fn as_regex_rule(&self) -> Option<&RegexCompiledRule> {
383 None
384 }
385
386 fn as_regex_rule_mut(&mut self) -> Option<&mut RegexCompiledRule> {
387 None
388 }
389
390 fn allow_scanner_to_exclude_namespace(&self) -> bool {
391 true
392 }
393}
394
395impl<T> RuleConfig for Box<T>
396where
397 T: RuleConfig + ?Sized,
398{
399 fn convert_to_compiled_rule(
400 &self,
401 rule_index: usize,
402 labels: Labels,
403 ) -> Result<Box<dyn CompiledRule>, CreateScannerError> {
404 self.as_ref().convert_to_compiled_rule(rule_index, labels)
405 }
406}
407
408#[derive(Debug, PartialEq, Clone)]
409struct ScannerFeatures {
410 pub add_implicit_index_wildcards: bool,
411 pub multipass_v0_enabled: bool,
412 pub return_matches: bool,
413 pub enable_debug_observability: bool,
414}
415
416impl Default for ScannerFeatures {
417 fn default() -> Self {
418 Self {
419 add_implicit_index_wildcards: false,
420 multipass_v0_enabled: true,
421 return_matches: false,
422 enable_debug_observability: false,
423 }
424 }
425}
426
427pub struct ScanOptions {
428 pub blocked_rules_idx: Vec<usize>,
431 pub wildcarded_indices: AHashMap<Path<'static>, Vec<(usize, usize)>>,
433 pub validate_matches: bool,
436 pub scan_metadata: AHashMap<String, String>,
440}
441
442impl Default for ScanOptions {
443 fn default() -> Self {
444 Self {
445 blocked_rules_idx: vec![],
446 wildcarded_indices: AHashMap::new(),
447 validate_matches: false,
448 scan_metadata: AHashMap::new(),
449 }
450 }
451}
452
453pub struct ScanOptionBuilder {
454 blocked_rules_idx: Vec<usize>,
455 wildcarded_indices: AHashMap<Path<'static>, Vec<(usize, usize)>>,
456 validate_matches: bool,
457 scan_metadata: AHashMap<String, String>,
458}
459
460impl ScanOptionBuilder {
461 pub fn new() -> Self {
462 Self {
463 blocked_rules_idx: vec![],
464 wildcarded_indices: AHashMap::new(),
465 validate_matches: false,
466 scan_metadata: AHashMap::new(),
467 }
468 }
469
470 pub fn with_blocked_rules_idx(mut self, blocked_rules_idx: Vec<usize>) -> Self {
471 self.blocked_rules_idx = blocked_rules_idx;
472 self
473 }
474
475 pub fn with_wildcarded_indices(
476 mut self,
477 wildcarded_indices: AHashMap<Path<'static>, Vec<(usize, usize)>>,
478 ) -> Self {
479 self.wildcarded_indices = wildcarded_indices;
480 self
481 }
482
483 pub fn with_validate_matching(mut self, validate_matches: bool) -> Self {
484 self.validate_matches = validate_matches;
485 self
486 }
487
488 pub fn with_scan_metadata(mut self, scan_metadata: AHashMap<String, String>) -> Self {
489 self.scan_metadata = scan_metadata;
490 self
491 }
492
493 pub fn build(self) -> ScanOptions {
494 ScanOptions {
495 blocked_rules_idx: self.blocked_rules_idx,
496 wildcarded_indices: self.wildcarded_indices,
497 validate_matches: self.validate_matches,
498 scan_metadata: self.scan_metadata,
499 }
500 }
501}
502
503pub struct Scanner {
504 rules: Vec<RootCompiledRule>,
505 scoped_ruleset: ScopedRuleSet,
506 scanner_features: ScannerFeatures,
507 metrics: ScannerMetrics,
508 labels: Labels,
509 #[cfg_attr(not(feature = "third-party-active-checkers"), allow(dead_code))]
512 match_validators_per_type: AHashMap<InternalMatchValidationType, Box<dyn MatchValidator>>,
513 per_scanner_data: SharedData,
514 async_scan_timeout: Duration,
515}
516
517impl Scanner {
518 pub fn builder(rules: &[RootRuleConfig<Arc<dyn RuleConfig>>]) -> ScannerBuilder<'_> {
519 ScannerBuilder::new(rules)
520 }
521
522 pub fn scan<E: Event>(&self, event: &mut E) -> Result<Vec<RuleMatch>, ScannerError> {
527 self.scan_with_options(event, ScanOptions::default())
528 }
529
530 pub fn scan_with_options<E: Event>(
535 &self,
536 event: &mut E,
537 options: ScanOptions,
538 ) -> Result<Vec<RuleMatch>, ScannerError> {
539 let start = Instant::now();
540 let validate = options.validate_matches;
541 let result = block_on(self.internal_scan_collect(event, options));
544 match result {
545 Ok((mut rule_matches, io_duration)) => {
546 self.finalize_matches(&mut rule_matches, validate);
547 self.record_metrics(&rule_matches, start, Some(io_duration));
548 Ok(rule_matches)
549 }
550 Err(e) => {
551 self.record_metrics(&[], start, None);
552 Err(e)
553 }
554 }
555 }
556
557 pub async fn scan_async<E: Event>(
561 &self,
562 event: &mut E,
563 ) -> Result<Vec<RuleMatch>, ScannerError> {
564 self.scan_async_with_options(event, ScanOptions::default())
565 .await
566 }
567
568 pub async fn scan_async_with_options<E: Event>(
569 &self,
570 event: &mut E,
571 options: ScanOptions,
572 ) -> Result<Vec<RuleMatch>, ScannerError> {
573 let start = Instant::now();
574 let validate = options.validate_matches;
575 let fut = self.internal_scan_collect(event, options);
576
577 let timeout_result = {
580 let _tokio_guard = TOKIO_RUNTIME.enter();
581 timeout(self.async_scan_timeout, fut)
582 };
583
584 let result = timeout_result.await.unwrap_or(Err(ScannerError::Transient(
585 "Async scan timeout".to_string(),
586 )));
587
588 match result {
589 Ok((mut rule_matches, io_duration)) => {
590 self.finalize_matches(&mut rule_matches, validate);
591 self.record_metrics(&rule_matches, start, Some(io_duration));
592 Ok(rule_matches)
593 }
594 Err(e) => {
595 self.record_metrics(&[], start, None);
596 Err(e)
597 }
598 }
599 }
600
601 fn record_metrics(
602 &self,
603 output_rule_matches: &[RuleMatch],
604 start: Instant,
605 io_duration: Option<Duration>,
606 ) {
607 self.metrics.num_scanned_events.increment(1);
609 let mut match_counts_by_rule: AHashMap<usize, u64> = AHashMap::new();
612 for rule_match in output_rule_matches {
613 *match_counts_by_rule
614 .entry(rule_match.rule_index)
615 .or_default() += 1;
616 }
617 for (rule_index, count) in match_counts_by_rule {
618 match self.rules[rule_index].sds_rule_name.as_deref() {
619 Some(sds_rule_name) => {
620 let labels = self.labels.clone_with_labels(Labels::new(&[(
621 "sds_rule_name",
622 sds_rule_name.to_string(),
623 )]));
624 counter!("scanning.match_count", labels).increment(count);
625 }
626 None => self.metrics.match_count.increment(count),
627 }
628 }
629
630 if let Some(io_duration) = io_duration {
631 let total_duration = start.elapsed();
632 let cpu_duration = total_duration.saturating_sub(io_duration);
633 self.metrics
634 .cpu_duration
635 .increment(cpu_duration.as_nanos() as u64);
636 }
637 }
638
639 fn process_rule_matches<E: Event>(
640 &self,
641 event: &mut E,
642 rule_matches: InternalRuleMatchSet<E::Encoding>,
643 excluded_matches: AHashMap<String, String>,
644 output_rule_matches: &mut Vec<RuleMatch>,
645 need_match_content: bool,
646 ) {
647 if rule_matches.is_empty() {
648 return;
649 }
650 access_regex_caches(|regex_caches| {
651 for (path, mut rule_matches) in rule_matches.into_iter() {
652 event.visit_string_mut(&path, |content| {
654 rule_matches.sort_unstable_by_key(|rule_match| rule_match.utf8_start);
656
657 <<E as Event>::Encoding>::calculate_indices(
658 content,
659 rule_matches.iter_mut().map(
660 |rule_match: &mut InternalRuleMatch<E::Encoding>| EncodeIndices {
661 utf8_start: rule_match.utf8_start,
662 utf8_end: rule_match.utf8_end,
663 custom_start: &mut rule_match.custom_start,
664 custom_end: &mut rule_match.custom_end,
665 },
666 ),
667 );
668
669 if self.scanner_features.multipass_v0_enabled {
670 rule_matches.retain(|rule_match| {
673 if self.rules[rule_match.rule_index]
674 .inner
675 .should_exclude_multipass_v0()
676 {
677 let match_content =
678 &content[rule_match.utf8_start..rule_match.utf8_end];
679 let excluded_path = excluded_matches.get(match_content);
680 if let Some(excluded_path) = excluded_path {
681 self.rules[rule_match.rule_index]
682 .on_excluded_match_multipass_v0(
683 &path,
684 excluded_path,
685 self.scanner_features.enable_debug_observability,
686 );
687 }
688 excluded_path.is_none()
689 } else {
690 true
691 }
692 });
693 }
694
695 self.suppress_matches::<E::Encoding>(&mut rule_matches, content, regex_caches);
696
697 self.sort_and_remove_overlapping_rules::<E::Encoding>(&mut rule_matches);
698
699 let will_mutate = rule_matches.iter().any(|rule_match| {
700 self.rules[rule_match.rule_index].match_action.is_mutating()
701 });
702
703 self.apply_match_actions(
704 content,
705 &path,
706 rule_matches,
707 output_rule_matches,
708 need_match_content,
709 );
710
711 will_mutate
712 });
713 }
714 });
715 }
716
717 async fn internal_scan_collect<E: Event>(
718 &self,
719 event: &mut E,
720 options: ScanOptions,
721 ) -> Result<(Vec<RuleMatch>, Duration), ScannerError> {
722 let need_match_content = self.scanner_features.return_matches || options.validate_matches;
725 let mut rule_matches = InternalRuleMatchSet::new();
727 let mut excluded_matches = AHashMap::new();
728 let mut async_jobs = vec![];
729
730 access_regex_caches(|regex_caches| {
731 self.scoped_ruleset.visit_string_rule_combinations(
732 event,
733 ScannerContentVisitor {
734 scanner: self,
735 regex_caches,
736 rule_matches: &mut rule_matches,
737 blocked_rules: &options.blocked_rules_idx,
738 excluded_matches: &mut excluded_matches,
739 per_event_data: SharedData::new(),
740 wildcarded_indexes: &options.wildcarded_indices,
741 async_jobs: &mut async_jobs,
742 event_id: event.get_id().map(|s| s.to_string()),
743 scan_metadata: &options.scan_metadata,
744 },
745 )
746 })?;
747
748 let mut total_io_duration = Duration::ZERO;
751 for job in async_jobs {
752 let rule_info = job.fut.await.unwrap()?;
753 total_io_duration += rule_info.io_duration;
754 rule_matches.push_async_matches(
755 &job.path,
756 rule_info
757 .rule_matches
758 .into_iter()
759 .map(|x| InternalRuleMatch::new(rule_info.rule_index, x)),
760 );
761 }
762
763 let mut output_rule_matches = vec![];
764
765 self.process_rule_matches(
766 event,
767 rule_matches,
768 excluded_matches,
769 &mut output_rule_matches,
770 need_match_content,
771 );
772
773 Ok((output_rule_matches, total_io_duration))
774 }
775
776 pub fn suppress_matches<E: Encoding>(
777 &self,
778 rule_matches: &mut Vec<InternalRuleMatch<E>>,
779 content: &str,
780 regex_caches: &mut RegexCaches,
781 ) {
782 rule_matches.retain(|rule_match| {
783 if let Some(suppressions) = &self.rules[rule_match.rule_index].suppressions {
784 let match_should_be_suppressed = suppressions.should_match_be_suppressed(
785 &content[rule_match.utf8_start..rule_match.utf8_end],
786 regex_caches,
787 );
788
789 if match_should_be_suppressed {
790 self.metrics.suppressed_match_count.increment(1);
791 }
792 !match_should_be_suppressed
793 } else {
794 true
795 }
796 });
797 }
798
799 #[cfg(feature = "third-party-active-checkers")]
800 pub fn validate_matches(&self, rule_matches: &mut Vec<RuleMatch>) {
801 let mut match_validator_rule_match_per_type = AHashMap::new();
803
804 let mut validated_rule_matches = vec![];
805
806 for mut rule_match in rule_matches.drain(..) {
807 let rule = &self.rules[rule_match.rule_index];
808 if let Some(match_validation_type) = rule.internal_match_validation_type() {
809 match_validator_rule_match_per_type
810 .entry(match_validation_type)
811 .or_insert_with(Vec::new)
812 .push(rule_match)
813 } else {
814 rule_match.match_status.merge(MatchStatus::NotAvailable);
816 validated_rule_matches.push(rule_match);
817 }
818 }
819
820 if !match_validator_rule_match_per_type.is_empty() {
822 let run_validation = || {
823 use rayon::prelude::*;
824
825 match_validator_rule_match_per_type.par_iter_mut().for_each(
826 |(match_validation_type, matches_per_type)| {
827 let match_validator =
828 self.match_validators_per_type.get(match_validation_type);
829 if let Some(match_validator) = match_validator {
830 match_validator
831 .as_ref()
832 .validate(matches_per_type, &self.rules)
833 }
834 },
835 );
836 };
837
838 if rayon::current_thread_index().is_some() {
849 std::thread::scope(|s| {
850 s.spawn(|| RAYON_THREAD_POOL.install(run_validation));
851 });
852 } else {
853 RAYON_THREAD_POOL.install(run_validation);
854 }
855 }
856
857 for (_, mut matches) in match_validator_rule_match_per_type {
859 validated_rule_matches.append(&mut matches);
860 }
861
862 validated_rule_matches.sort_by_key(|rule_match| rule_match.start_index);
864 *rule_matches = validated_rule_matches;
865 }
866
867 fn finalize_matches(&self, rule_matches: &mut Vec<RuleMatch>, validate: bool) {
872 #[cfg(feature = "third-party-active-checkers")]
873 if validate {
874 self.validate_matches(rule_matches);
875 }
876 #[cfg(not(feature = "third-party-active-checkers"))]
877 let _ = validate;
878 rule_matches.retain(|rule_match| !self.rules[rule_match.rule_index].is_supporting_rule);
882 }
883
884 fn apply_match_actions<E: Encoding>(
887 &self,
888 content: &mut String,
889 path: &Path<'static>,
890 rule_matches: Vec<InternalRuleMatch<E>>,
891 output_rule_matches: &mut Vec<RuleMatch>,
892 need_match_content: bool,
893 ) {
894 let mut utf8_byte_delta: isize = 0;
895 let mut custom_index_delta: <E>::IndexShift = <E>::zero_shift();
896
897 for rule_match in rule_matches {
898 output_rule_matches.push(self.apply_match_actions_for_string::<E>(
899 content,
900 path.clone(),
901 rule_match,
902 &mut utf8_byte_delta,
903 &mut custom_index_delta,
904 need_match_content,
905 ));
906 }
907 }
908
909 fn apply_match_actions_for_string<E: Encoding>(
911 &self,
912 content: &mut String,
913 path: Path<'static>,
914 rule_match: InternalRuleMatch<E>,
915 utf8_byte_delta: &mut isize,
917
918 custom_index_delta: &mut <E>::IndexShift,
920 need_match_content: bool,
921 ) -> RuleMatch {
922 let rule = &self.rules[rule_match.rule_index];
923
924 let custom_start =
925 (<E>::get_index(&rule_match.custom_start, rule_match.utf8_start) as isize
926 + <E>::get_shift(custom_index_delta, *utf8_byte_delta)) as usize;
927
928 let mut matched_content_copy = None;
929
930 if need_match_content {
931 let mutated_utf8_match_start =
933 (rule_match.utf8_start as isize + *utf8_byte_delta) as usize;
934 let mutated_utf8_match_end = (rule_match.utf8_end as isize + *utf8_byte_delta) as usize;
935
936 debug_assert!(content.is_char_boundary(mutated_utf8_match_start));
938 debug_assert!(content.is_char_boundary(mutated_utf8_match_end));
939
940 let matched_content = &content[mutated_utf8_match_start..mutated_utf8_match_end];
941 matched_content_copy = Some(matched_content.to_string());
942 }
943
944 if rule.match_action.is_mutating() {
945 let mutated_utf8_match_start =
946 (rule_match.utf8_start as isize + *utf8_byte_delta) as usize;
947 let mutated_utf8_match_end = (rule_match.utf8_end as isize + *utf8_byte_delta) as usize;
948
949 debug_assert!(content.is_char_boundary(mutated_utf8_match_start));
951 debug_assert!(content.is_char_boundary(mutated_utf8_match_end));
952
953 let matched_content = &content[mutated_utf8_match_start..mutated_utf8_match_end];
954 if let Some(replacement) = rule.match_action.get_replacement(matched_content) {
955 let before_replacement = &matched_content[replacement.start..replacement.end];
956
957 <E>::adjust_shift(
959 custom_index_delta,
960 before_replacement,
961 &replacement.replacement,
962 );
963 *utf8_byte_delta +=
964 replacement.replacement.len() as isize - before_replacement.len() as isize;
965
966 let replacement_start = mutated_utf8_match_start + replacement.start;
967 let replacement_end = mutated_utf8_match_start + replacement.end;
968 content.replace_range(replacement_start..replacement_end, &replacement.replacement);
969 }
970 }
971
972 let shift_offset = <E>::get_shift(custom_index_delta, *utf8_byte_delta);
973 let custom_end = (<E>::get_index(&rule_match.custom_end, rule_match.utf8_end) as isize
974 + shift_offset) as usize;
975
976 let rule = &self.rules[rule_match.rule_index];
977
978 let match_status: MatchStatus = if rule.match_validation_type.is_some() {
979 MatchStatus::NotChecked
980 } else {
981 MatchStatus::NotAvailable
982 };
983
984 RuleMatch {
985 rule_index: rule_match.rule_index,
986 path,
987 replacement_type: rule.match_action.replacement_type(),
988 start_index: custom_start,
989 end_index_exclusive: custom_end,
990 shift_offset,
991 match_value: matched_content_copy,
992 match_status,
993 keyword: rule_match.keyword,
994 }
995 }
996
997 fn sort_and_remove_overlapping_rules<E: Encoding>(
998 &self,
999 rule_matches: &mut Vec<InternalRuleMatch<E>>,
1000 ) {
1001 rule_matches.sort_unstable_by(|a, b| {
1005 let ord = self.rules[a.rule_index]
1007 .match_action
1008 .is_mutating()
1009 .cmp(&self.rules[b.rule_index].match_action.is_mutating())
1010 .reverse();
1011
1012 let ord = ord.then(a.utf8_start.cmp(&b.utf8_start));
1014
1015 let ord = ord.then(a.len().cmp(&b.len()).reverse());
1017
1018 let ord = ord.then(
1020 self.rules[a.rule_index]
1021 .precedence
1022 .cmp(&self.rules[b.rule_index].precedence)
1023 .reverse(),
1024 );
1025
1026 let ord = ord.then(a.rule_index.cmp(&b.rule_index));
1028
1029 ord.reverse()
1031 });
1032
1033 let mut retained_rules: Vec<InternalRuleMatch<E>> = vec![];
1034
1035 'rule_matches: while let Some(rule_match) = rule_matches.pop() {
1036 if self.rules[rule_match.rule_index].match_action.is_mutating() {
1037 if let Some(last) = retained_rules.last()
1039 && last.utf8_end > rule_match.utf8_start
1040 {
1041 continue;
1042 }
1043 } else {
1044 for retained_rule in &retained_rules {
1047 if retained_rule.utf8_start < rule_match.utf8_end
1048 && retained_rule.utf8_end > rule_match.utf8_start
1049 {
1050 continue 'rule_matches;
1051 }
1052 }
1053 };
1054 retained_rules.push(rule_match);
1055 }
1056
1057 retained_rules.sort_unstable_by_key(|rule_match| rule_match.utf8_start);
1059
1060 *rule_matches = retained_rules;
1061 }
1062}
1063
1064impl Drop for Scanner {
1065 fn drop(&mut self) {
1066 let stats = &*GLOBAL_STATS;
1067 stats.scanner_deletions.increment(1);
1068 stats.decrement_total_scanners();
1069 }
1070}
1071
1072#[derive(Default)]
1073pub struct ScannerBuilder<'a> {
1074 rules: &'a [RootRuleConfig<Arc<dyn RuleConfig>>],
1075 labels: Labels,
1076 scanner_features: ScannerFeatures,
1077 async_scan_timeout: Duration,
1078}
1079
1080impl ScannerBuilder<'_> {
1081 pub fn new(rules: &[RootRuleConfig<Arc<dyn RuleConfig>>]) -> ScannerBuilder<'_> {
1082 ScannerBuilder {
1083 rules,
1084 labels: Labels::empty(),
1085 scanner_features: ScannerFeatures::default(),
1086 async_scan_timeout: Duration::from_secs(60 * 5),
1087 }
1088 }
1089
1090 pub fn labels(mut self, labels: Labels) -> Self {
1091 self.labels = labels;
1092 self
1093 }
1094
1095 pub fn with_async_scan_timeout(mut self, duration: Duration) -> Self {
1096 self.async_scan_timeout = duration;
1097 self
1098 }
1099
1100 pub fn with_implicit_wildcard_indexes_for_scopes(mut self, value: bool) -> Self {
1101 self.scanner_features.add_implicit_index_wildcards = value;
1102 self
1103 }
1104
1105 pub fn with_return_matches(mut self, value: bool) -> Self {
1106 self.scanner_features.return_matches = value;
1107 self
1108 }
1109
1110 pub fn with_multipass_v0(mut self, value: bool) -> Self {
1114 self.scanner_features.multipass_v0_enabled = value;
1115 self
1116 }
1117
1118 pub fn with_debug_observability(mut self, value: bool) -> Self {
1122 self.scanner_features.enable_debug_observability = value;
1123 self
1124 }
1125
1126 pub fn build(self) -> Result<Scanner, CreateScannerError> {
1127 #[cfg_attr(not(feature = "third-party-active-checkers"), allow(unused_mut))]
1130 let mut match_validators_per_type = AHashMap::new();
1131
1132 #[cfg(feature = "third-party-active-checkers")]
1133 for rule in self.rules.iter() {
1134 if let Some(match_validation_type) = &rule.get_third_party_active_checker()
1135 && match_validation_type.can_create_match_validator()
1136 {
1137 let internal_type = match_validation_type.get_internal_match_validation_type();
1138 let match_validator = match_validation_type.into_match_validator();
1139 if let Ok(match_validator) = match_validator {
1140 if !match_validators_per_type.contains_key(&internal_type) {
1141 match_validators_per_type.insert(internal_type, match_validator);
1142 }
1143 } else {
1144 return Err(CreateScannerError::InvalidMatchValidator(
1145 MatchValidatorCreationError::InternalError,
1146 ));
1147 }
1148 }
1149 }
1150
1151 let compiled_rules = self
1152 .rules
1153 .iter()
1154 .enumerate()
1155 .map(|(rule_index, config)| {
1156 if config.is_supporting_rule && config.match_action != MatchAction::None {
1157 return Err(CreateScannerError::SupportingRuleHasMatchAction);
1158 }
1159 let inner = config.convert_to_compiled_rule(rule_index, self.labels.clone())?;
1160 config.match_action.validate()?;
1161 let compiled_suppressions = match &config.suppressions {
1162 Some(s) => s.compile()?,
1163 None => None,
1164 };
1165 Ok(RootCompiledRule {
1166 inner,
1167 scope: config.scope.clone(),
1168 match_action: config.match_action.clone(),
1169 match_validation_type: config.get_third_party_active_checker().cloned(),
1170 suppressions: compiled_suppressions,
1171 precedence: config.precedence,
1172 is_supporting_rule: config.is_supporting_rule,
1173 sds_rule_name: metrics::compute_sds_rule_name(&config.tags),
1174 })
1175 })
1176 .collect::<Result<Vec<RootCompiledRule>, CreateScannerError>>()?;
1177
1178 let mut per_scanner_data = SharedData::new();
1179
1180 compiled_rules.iter().for_each(|rule| {
1181 rule.init_per_scanner_data(&mut per_scanner_data);
1182 });
1183
1184 let scoped_ruleset = ScopedRuleSet::new(
1185 &compiled_rules
1186 .iter()
1187 .map(|rule| rule.scope.clone())
1188 .collect::<Vec<_>>(),
1189 )
1190 .with_implicit_index_wildcards(self.scanner_features.add_implicit_index_wildcards);
1191
1192 {
1193 let stats = &*GLOBAL_STATS;
1194 stats.scanner_creations.increment(1);
1195 stats.increment_total_scanners();
1196 }
1197
1198 Ok(Scanner {
1199 rules: compiled_rules,
1200 scoped_ruleset,
1201 scanner_features: self.scanner_features,
1202 metrics: ScannerMetrics::new(&self.labels),
1203 match_validators_per_type,
1204 labels: self.labels,
1205 per_scanner_data,
1206 async_scan_timeout: self.async_scan_timeout,
1207 })
1208 }
1209}
1210
1211struct ScannerContentVisitor<'a, E: Encoding> {
1212 scanner: &'a Scanner,
1213 regex_caches: &'a mut RegexCaches,
1214 rule_matches: &'a mut InternalRuleMatchSet<E>,
1215 blocked_rules: &'a Vec<usize>,
1218 excluded_matches: &'a mut AHashMap<String, String>,
1219 per_event_data: SharedData,
1220 wildcarded_indexes: &'a AHashMap<Path<'static>, Vec<(usize, usize)>>,
1221 async_jobs: &'a mut Vec<PendingRuleJob>,
1222 event_id: Option<String>,
1223 scan_metadata: &'a AHashMap<String, String>,
1224}
1225
1226impl<'a, E: Encoding> ContentVisitor<'a> for ScannerContentVisitor<'a, E> {
1227 fn visit_content<'b>(
1228 &'b mut self,
1229 path: &Path<'a>,
1230 content: &str,
1231 mut rule_visitor: crate::scoped_ruleset::RuleIndexVisitor,
1232 exclusion_check: ExclusionCheck<'b>,
1233 ) -> Result<bool, ScannerError> {
1234 let mut path_rules_matches = vec![];
1236
1237 let mut per_string_data = SharedData::new();
1239 let wildcard_indices_per_path = self.wildcarded_indexes.get(path);
1240
1241 rule_visitor.visit_rule_indices(|rule_index| {
1242 if self.blocked_rules.contains(&rule_index) {
1243 return Ok(());
1244 }
1245 let rule = &self.scanner.rules[rule_index];
1246 {
1247 if rule.inner.allow_scanner_to_exclude_namespace() {
1248 if exclusion_check.is_excluded(rule_index) {
1250 return Ok(());
1251 }
1252 }
1253 let mut emitter = |rule_match: StringMatch| {
1255 assert_ne!(
1258 rule_match.start, rule_match.end,
1259 "empty match detected on rule with index {rule_index}"
1260 );
1261 path_rules_matches.push(InternalRuleMatch::new(rule_index, rule_match));
1262 };
1263
1264 rule.init_per_string_data(&self.scanner.labels, &mut per_string_data);
1265
1266 rule.init_per_event_data(&mut self.per_event_data);
1268
1269 let mut ctx = StringMatchesCtx {
1270 rule_index,
1271 regex_caches: self.regex_caches,
1272 exclusion_check: &exclusion_check,
1273 excluded_matches: self.excluded_matches,
1274 match_emitter: &mut emitter,
1275 wildcard_indices: wildcard_indices_per_path,
1276 enable_debug_observability: self
1277 .scanner
1278 .scanner_features
1279 .enable_debug_observability,
1280 per_string_data: &mut per_string_data,
1281 per_scanner_data: &self.scanner.per_scanner_data,
1282 per_event_data: &mut self.per_event_data,
1283 event_id: self.event_id.as_deref(),
1284 scan_metadata: self.scan_metadata,
1285 };
1286
1287 let async_status = rule.get_string_matches(content, path, &mut ctx)?;
1288
1289 match async_status {
1290 RuleStatus::Done => {
1291 }
1293 RuleStatus::Pending(fut) => {
1294 self.async_jobs.push(PendingRuleJob {
1295 fut,
1296 path: path.into_static(),
1297 });
1298 }
1299 }
1300 }
1301 Ok(())
1302 })?;
1303
1304 let needs_to_access_content = !path_rules_matches.is_empty() || !self.async_jobs.is_empty();
1309
1310 self.rule_matches
1311 .push_sync_matches(path, path_rules_matches);
1312
1313 Ok(needs_to_access_content)
1314 }
1315}
1316
1317fn get_next_regex_start(content: &str, regex_match: (usize, usize)) -> Option<usize> {
1319 if let Some((i, _)) = content[regex_match.0..].char_indices().nth(1) {
1321 Some(regex_match.0 + i)
1322 } else {
1323 None
1325 }
1326}
1327
1328fn is_false_positive_match(
1329 regex_match_range: (usize, usize),
1330 rule: &RegexCompiledRule,
1331 content: &str,
1332 check_excluded_keywords: bool,
1333) -> bool {
1334 if check_excluded_keywords
1335 && let Some(excluded_keywords) = &rule.excluded_keywords
1336 && excluded_keywords.is_false_positive_match(content, regex_match_range.0)
1337 {
1338 return true;
1339 }
1340
1341 if let Some(validator) = rule.validator.as_ref()
1342 && !validator.is_valid_match(&content[regex_match_range.0..regex_match_range.1])
1343 {
1344 return true;
1345 }
1346 false
1347}