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