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 fn get_third_party_active_checker(&self) -> Option<&MatchValidationType> {
190 #[allow(deprecated)]
191 self.third_party_active_checker
192 .as_ref()
193 .or(self.match_validation_type.as_ref())
194 }
195}
196
197impl<T> Deref for RootRuleConfig<T> {
198 type Target = T;
199
200 fn deref(&self) -> &Self::Target {
201 &self.inner
202 }
203}
204pub struct RootCompiledRule {
205 pub inner: Box<dyn CompiledRule>,
206 pub scope: Scope,
207 pub match_action: MatchAction,
208 pub match_validation_type: Option<MatchValidationType>,
209 pub suppressions: Option<CompiledSuppressions>,
210 pub precedence: Precedence,
211 pub is_supporting_rule: bool,
212}
213
214impl RootCompiledRule {
215 pub fn internal_match_validation_type(&self) -> Option<InternalMatchValidationType> {
216 self.match_validation_type
217 .as_ref()
218 .map(|x| x.get_internal_match_validation_type())
219 }
220}
221
222impl Deref for RootCompiledRule {
223 type Target = dyn CompiledRule;
224
225 fn deref(&self) -> &Self::Target {
226 self.inner.as_ref()
227 }
228}
229
230pub struct StringMatchesCtx<'a> {
231 rule_index: usize,
232 pub regex_caches: &'a mut RegexCaches,
233 pub exclusion_check: &'a ExclusionCheck<'a>,
234 pub excluded_matches: &'a mut AHashMap<String, String>,
235 pub match_emitter: &'a mut dyn MatchEmitter,
236 pub wildcard_indices: Option<&'a Vec<(usize, usize)>>,
237 pub enable_debug_observability: bool,
238
239 pub per_string_data: &'a mut SharedData,
241 pub per_scanner_data: &'a SharedData,
242 pub per_event_data: &'a mut SharedData,
243 pub event_id: Option<&'a str>,
244
245 pub scan_metadata: &'a AHashMap<String, String>,
247}
248
249impl StringMatchesCtx<'_> {
250 pub fn process_async(
260 &self,
261 func: impl for<'a> FnOnce(
262 &'a mut AsyncStringMatchesCtx,
263 )
264 -> Pin<Box<dyn Future<Output = Result<(), ScannerError>> + Send + 'a>>
265 + Send
266 + 'static,
267 ) -> RuleResult {
268 let rule_index = self.rule_index;
269
270 let fut = TOKIO_RUNTIME.spawn(async move {
273 let start = Instant::now();
274 let mut ctx = AsyncStringMatchesCtx {
275 rule_matches: vec![],
276 };
277 (func)(&mut ctx).await?;
278 let io_duration = start.elapsed();
279
280 Ok(AsyncRuleInfo {
281 rule_index,
282 rule_matches: ctx.rule_matches,
283 io_duration,
284 })
285 });
286
287 Ok(RuleStatus::Pending(fut))
288 }
289}
290
291pub struct AsyncStringMatchesCtx {
292 rule_matches: Vec<StringMatch>,
293}
294
295impl AsyncStringMatchesCtx {
296 pub fn emit_match(&mut self, string_match: StringMatch) {
297 self.rule_matches.push(string_match);
298 }
299}
300
301#[must_use]
302pub enum RuleStatus {
303 Done,
304 Pending(PendingRuleResult),
305}
306
307pub type PendingRuleResult = JoinHandle<Result<AsyncRuleInfo, ScannerError>>;
309
310pub struct PendingRuleJob {
311 fut: PendingRuleResult,
312 path: Path<'static>,
313}
314
315pub struct AsyncRuleInfo {
316 rule_index: usize,
317 rule_matches: Vec<StringMatch>,
318 io_duration: Duration,
319}
320
321pub type RuleResult = Result<RuleStatus, ScannerError>;
323
324pub trait CompiledRule: Send + Sync {
326 fn init_per_scanner_data(&self, _per_scanner_data: &mut SharedData) {
327 }
329
330 fn init_per_string_data(&self, _labels: &Labels, _per_string_data: &mut SharedData) {
331 }
333
334 fn init_per_event_data(&self, _per_event_data: &mut SharedData) {
335 }
337
338 fn get_string_matches(
339 &self,
340 content: &str,
341 path: &Path,
342 ctx: &mut StringMatchesCtx<'_>,
343 ) -> RuleResult;
344
345 fn should_exclude_multipass_v0(&self) -> bool {
348 false
350 }
351
352 fn on_excluded_match_multipass_v0(
353 &self,
354 _path: &Path,
355 _excluded_path: &str,
356 _enable_debug_observability: bool,
357 ) {
358 }
360
361 fn as_regex_rule(&self) -> Option<&RegexCompiledRule> {
362 None
363 }
364
365 fn as_regex_rule_mut(&mut self) -> Option<&mut RegexCompiledRule> {
366 None
367 }
368
369 fn allow_scanner_to_exclude_namespace(&self) -> bool {
370 true
371 }
372}
373
374impl<T> RuleConfig for Box<T>
375where
376 T: RuleConfig + ?Sized,
377{
378 fn convert_to_compiled_rule(
379 &self,
380 rule_index: usize,
381 labels: Labels,
382 ) -> Result<Box<dyn CompiledRule>, CreateScannerError> {
383 self.as_ref().convert_to_compiled_rule(rule_index, labels)
384 }
385}
386
387#[derive(Debug, PartialEq, Clone)]
388struct ScannerFeatures {
389 pub add_implicit_index_wildcards: bool,
390 pub multipass_v0_enabled: bool,
391 pub return_matches: bool,
392 pub enable_debug_observability: bool,
393}
394
395impl Default for ScannerFeatures {
396 fn default() -> Self {
397 Self {
398 add_implicit_index_wildcards: false,
399 multipass_v0_enabled: true,
400 return_matches: false,
401 enable_debug_observability: false,
402 }
403 }
404}
405
406pub struct ScanOptions {
407 pub blocked_rules_idx: Vec<usize>,
410 pub wildcarded_indices: AHashMap<Path<'static>, Vec<(usize, usize)>>,
412 pub validate_matches: bool,
415 pub scan_metadata: AHashMap<String, String>,
419}
420
421impl Default for ScanOptions {
422 fn default() -> Self {
423 Self {
424 blocked_rules_idx: vec![],
425 wildcarded_indices: AHashMap::new(),
426 validate_matches: false,
427 scan_metadata: AHashMap::new(),
428 }
429 }
430}
431
432pub struct ScanOptionBuilder {
433 blocked_rules_idx: Vec<usize>,
434 wildcarded_indices: AHashMap<Path<'static>, Vec<(usize, usize)>>,
435 validate_matches: bool,
436 scan_metadata: AHashMap<String, String>,
437}
438
439impl ScanOptionBuilder {
440 pub fn new() -> Self {
441 Self {
442 blocked_rules_idx: vec![],
443 wildcarded_indices: AHashMap::new(),
444 validate_matches: false,
445 scan_metadata: AHashMap::new(),
446 }
447 }
448
449 pub fn with_blocked_rules_idx(mut self, blocked_rules_idx: Vec<usize>) -> Self {
450 self.blocked_rules_idx = blocked_rules_idx;
451 self
452 }
453
454 pub fn with_wildcarded_indices(
455 mut self,
456 wildcarded_indices: AHashMap<Path<'static>, Vec<(usize, usize)>>,
457 ) -> Self {
458 self.wildcarded_indices = wildcarded_indices;
459 self
460 }
461
462 pub fn with_validate_matching(mut self, validate_matches: bool) -> Self {
463 self.validate_matches = validate_matches;
464 self
465 }
466
467 pub fn with_scan_metadata(mut self, scan_metadata: AHashMap<String, String>) -> Self {
468 self.scan_metadata = scan_metadata;
469 self
470 }
471
472 pub fn build(self) -> ScanOptions {
473 ScanOptions {
474 blocked_rules_idx: self.blocked_rules_idx,
475 wildcarded_indices: self.wildcarded_indices,
476 validate_matches: self.validate_matches,
477 scan_metadata: self.scan_metadata,
478 }
479 }
480}
481
482pub struct Scanner {
483 rules: Vec<RootCompiledRule>,
484 scoped_ruleset: ScopedRuleSet,
485 scanner_features: ScannerFeatures,
486 metrics: ScannerMetrics,
487 labels: Labels,
488 #[cfg_attr(not(feature = "third-party-active-checkers"), allow(dead_code))]
491 match_validators_per_type: AHashMap<InternalMatchValidationType, Box<dyn MatchValidator>>,
492 per_scanner_data: SharedData,
493 async_scan_timeout: Duration,
494}
495
496impl Scanner {
497 pub fn builder(rules: &[RootRuleConfig<Arc<dyn RuleConfig>>]) -> ScannerBuilder<'_> {
498 ScannerBuilder::new(rules)
499 }
500
501 pub fn scan<E: Event>(&self, event: &mut E) -> Result<Vec<RuleMatch>, ScannerError> {
506 self.scan_with_options(event, ScanOptions::default())
507 }
508
509 pub fn scan_with_options<E: Event>(
514 &self,
515 event: &mut E,
516 options: ScanOptions,
517 ) -> Result<Vec<RuleMatch>, ScannerError> {
518 let start = Instant::now();
519 let validate = options.validate_matches;
520 let result = block_on(self.internal_scan_collect(event, options));
523 match result {
524 Ok((mut rule_matches, io_duration)) => {
525 self.finalize_matches(&mut rule_matches, validate);
526 self.record_metrics(&rule_matches, start, Some(io_duration));
527 Ok(rule_matches)
528 }
529 Err(e) => {
530 self.record_metrics(&[], start, None);
531 Err(e)
532 }
533 }
534 }
535
536 pub async fn scan_async<E: Event>(
540 &self,
541 event: &mut E,
542 ) -> Result<Vec<RuleMatch>, ScannerError> {
543 self.scan_async_with_options(event, ScanOptions::default())
544 .await
545 }
546
547 pub async fn scan_async_with_options<E: Event>(
548 &self,
549 event: &mut E,
550 options: ScanOptions,
551 ) -> Result<Vec<RuleMatch>, ScannerError> {
552 let start = Instant::now();
553 let validate = options.validate_matches;
554 let fut = self.internal_scan_collect(event, options);
555
556 let timeout_result = {
559 let _tokio_guard = TOKIO_RUNTIME.enter();
560 timeout(self.async_scan_timeout, fut)
561 };
562
563 let result = timeout_result.await.unwrap_or(Err(ScannerError::Transient(
564 "Async scan timeout".to_string(),
565 )));
566
567 match result {
568 Ok((mut rule_matches, io_duration)) => {
569 self.finalize_matches(&mut rule_matches, validate);
570 self.record_metrics(&rule_matches, start, Some(io_duration));
571 Ok(rule_matches)
572 }
573 Err(e) => {
574 self.record_metrics(&[], start, None);
575 Err(e)
576 }
577 }
578 }
579
580 fn record_metrics(
581 &self,
582 output_rule_matches: &[RuleMatch],
583 start: Instant,
584 io_duration: Option<Duration>,
585 ) {
586 self.metrics.num_scanned_events.increment(1);
588 self.metrics
590 .match_count
591 .increment(output_rule_matches.len() as u64);
592
593 if let Some(io_duration) = io_duration {
594 let total_duration = start.elapsed();
595 let cpu_duration = total_duration.saturating_sub(io_duration);
596 self.metrics
597 .cpu_duration
598 .increment(cpu_duration.as_nanos() as u64);
599 }
600 }
601
602 fn process_rule_matches<E: Event>(
603 &self,
604 event: &mut E,
605 rule_matches: InternalRuleMatchSet<E::Encoding>,
606 excluded_matches: AHashMap<String, String>,
607 output_rule_matches: &mut Vec<RuleMatch>,
608 need_match_content: bool,
609 ) {
610 if rule_matches.is_empty() {
611 return;
612 }
613 access_regex_caches(|regex_caches| {
614 for (path, mut rule_matches) in rule_matches.into_iter() {
615 event.visit_string_mut(&path, |content| {
617 rule_matches.sort_unstable_by_key(|rule_match| rule_match.utf8_start);
619
620 <<E as Event>::Encoding>::calculate_indices(
621 content,
622 rule_matches.iter_mut().map(
623 |rule_match: &mut InternalRuleMatch<E::Encoding>| EncodeIndices {
624 utf8_start: rule_match.utf8_start,
625 utf8_end: rule_match.utf8_end,
626 custom_start: &mut rule_match.custom_start,
627 custom_end: &mut rule_match.custom_end,
628 },
629 ),
630 );
631
632 if self.scanner_features.multipass_v0_enabled {
633 rule_matches.retain(|rule_match| {
636 if self.rules[rule_match.rule_index]
637 .inner
638 .should_exclude_multipass_v0()
639 {
640 let match_content =
641 &content[rule_match.utf8_start..rule_match.utf8_end];
642 let excluded_path = excluded_matches.get(match_content);
643 if let Some(excluded_path) = excluded_path {
644 self.rules[rule_match.rule_index]
645 .on_excluded_match_multipass_v0(
646 &path,
647 excluded_path,
648 self.scanner_features.enable_debug_observability,
649 );
650 }
651 excluded_path.is_none()
652 } else {
653 true
654 }
655 });
656 }
657
658 self.suppress_matches::<E::Encoding>(&mut rule_matches, content, regex_caches);
659
660 self.sort_and_remove_overlapping_rules::<E::Encoding>(&mut rule_matches);
661
662 let will_mutate = rule_matches.iter().any(|rule_match| {
663 self.rules[rule_match.rule_index].match_action.is_mutating()
664 });
665
666 self.apply_match_actions(
667 content,
668 &path,
669 rule_matches,
670 output_rule_matches,
671 need_match_content,
672 );
673
674 will_mutate
675 });
676 }
677 });
678 }
679
680 async fn internal_scan_collect<E: Event>(
681 &self,
682 event: &mut E,
683 options: ScanOptions,
684 ) -> Result<(Vec<RuleMatch>, Duration), ScannerError> {
685 let need_match_content = self.scanner_features.return_matches || options.validate_matches;
688 let mut rule_matches = InternalRuleMatchSet::new();
690 let mut excluded_matches = AHashMap::new();
691 let mut async_jobs = vec![];
692
693 access_regex_caches(|regex_caches| {
694 self.scoped_ruleset.visit_string_rule_combinations(
695 event,
696 ScannerContentVisitor {
697 scanner: self,
698 regex_caches,
699 rule_matches: &mut rule_matches,
700 blocked_rules: &options.blocked_rules_idx,
701 excluded_matches: &mut excluded_matches,
702 per_event_data: SharedData::new(),
703 wildcarded_indexes: &options.wildcarded_indices,
704 async_jobs: &mut async_jobs,
705 event_id: event.get_id().map(|s| s.to_string()),
706 scan_metadata: &options.scan_metadata,
707 },
708 )
709 })?;
710
711 let mut total_io_duration = Duration::ZERO;
714 for job in async_jobs {
715 let rule_info = job.fut.await.unwrap()?;
716 total_io_duration += rule_info.io_duration;
717 rule_matches.push_async_matches(
718 &job.path,
719 rule_info
720 .rule_matches
721 .into_iter()
722 .map(|x| InternalRuleMatch::new(rule_info.rule_index, x)),
723 );
724 }
725
726 let mut output_rule_matches = vec![];
727
728 self.process_rule_matches(
729 event,
730 rule_matches,
731 excluded_matches,
732 &mut output_rule_matches,
733 need_match_content,
734 );
735
736 Ok((output_rule_matches, total_io_duration))
737 }
738
739 pub fn suppress_matches<E: Encoding>(
740 &self,
741 rule_matches: &mut Vec<InternalRuleMatch<E>>,
742 content: &str,
743 regex_caches: &mut RegexCaches,
744 ) {
745 rule_matches.retain(|rule_match| {
746 if let Some(suppressions) = &self.rules[rule_match.rule_index].suppressions {
747 let match_should_be_suppressed = suppressions.should_match_be_suppressed(
748 &content[rule_match.utf8_start..rule_match.utf8_end],
749 regex_caches,
750 );
751
752 if match_should_be_suppressed {
753 self.metrics.suppressed_match_count.increment(1);
754 }
755 !match_should_be_suppressed
756 } else {
757 true
758 }
759 });
760 }
761
762 #[cfg(feature = "third-party-active-checkers")]
763 pub fn validate_matches(&self, rule_matches: &mut Vec<RuleMatch>) {
764 let mut match_validator_rule_match_per_type = AHashMap::new();
766
767 let mut validated_rule_matches = vec![];
768
769 for mut rule_match in rule_matches.drain(..) {
770 let rule = &self.rules[rule_match.rule_index];
771 if let Some(match_validation_type) = rule.internal_match_validation_type() {
772 match_validator_rule_match_per_type
773 .entry(match_validation_type)
774 .or_insert_with(Vec::new)
775 .push(rule_match)
776 } else {
777 rule_match.match_status.merge(MatchStatus::NotAvailable);
779 validated_rule_matches.push(rule_match);
780 }
781 }
782
783 if !match_validator_rule_match_per_type.is_empty() {
785 let run_validation = || {
786 use rayon::prelude::*;
787
788 match_validator_rule_match_per_type.par_iter_mut().for_each(
789 |(match_validation_type, matches_per_type)| {
790 let match_validator =
791 self.match_validators_per_type.get(match_validation_type);
792 if let Some(match_validator) = match_validator {
793 match_validator
794 .as_ref()
795 .validate(matches_per_type, &self.rules)
796 }
797 },
798 );
799 };
800
801 if rayon::current_thread_index().is_some() {
812 std::thread::scope(|s| {
813 s.spawn(|| RAYON_THREAD_POOL.install(run_validation));
814 });
815 } else {
816 RAYON_THREAD_POOL.install(run_validation);
817 }
818 }
819
820 for (_, mut matches) in match_validator_rule_match_per_type {
822 validated_rule_matches.append(&mut matches);
823 }
824
825 validated_rule_matches.sort_by_key(|rule_match| rule_match.start_index);
827 *rule_matches = validated_rule_matches;
828 }
829
830 fn finalize_matches(&self, rule_matches: &mut Vec<RuleMatch>, validate: bool) {
835 #[cfg(feature = "third-party-active-checkers")]
836 if validate {
837 self.validate_matches(rule_matches);
838 }
839 #[cfg(not(feature = "third-party-active-checkers"))]
840 let _ = validate;
841 rule_matches.retain(|rule_match| !self.rules[rule_match.rule_index].is_supporting_rule);
845 }
846
847 fn apply_match_actions<E: Encoding>(
850 &self,
851 content: &mut String,
852 path: &Path<'static>,
853 rule_matches: Vec<InternalRuleMatch<E>>,
854 output_rule_matches: &mut Vec<RuleMatch>,
855 need_match_content: bool,
856 ) {
857 let mut utf8_byte_delta: isize = 0;
858 let mut custom_index_delta: <E>::IndexShift = <E>::zero_shift();
859
860 for rule_match in rule_matches {
861 output_rule_matches.push(self.apply_match_actions_for_string::<E>(
862 content,
863 path.clone(),
864 rule_match,
865 &mut utf8_byte_delta,
866 &mut custom_index_delta,
867 need_match_content,
868 ));
869 }
870 }
871
872 fn apply_match_actions_for_string<E: Encoding>(
874 &self,
875 content: &mut String,
876 path: Path<'static>,
877 rule_match: InternalRuleMatch<E>,
878 utf8_byte_delta: &mut isize,
880
881 custom_index_delta: &mut <E>::IndexShift,
883 need_match_content: bool,
884 ) -> RuleMatch {
885 let rule = &self.rules[rule_match.rule_index];
886
887 let custom_start =
888 (<E>::get_index(&rule_match.custom_start, rule_match.utf8_start) as isize
889 + <E>::get_shift(custom_index_delta, *utf8_byte_delta)) as usize;
890
891 let mut matched_content_copy = None;
892
893 if need_match_content {
894 let mutated_utf8_match_start =
896 (rule_match.utf8_start as isize + *utf8_byte_delta) as usize;
897 let mutated_utf8_match_end = (rule_match.utf8_end as isize + *utf8_byte_delta) as usize;
898
899 debug_assert!(content.is_char_boundary(mutated_utf8_match_start));
901 debug_assert!(content.is_char_boundary(mutated_utf8_match_end));
902
903 let matched_content = &content[mutated_utf8_match_start..mutated_utf8_match_end];
904 matched_content_copy = Some(matched_content.to_string());
905 }
906
907 if rule.match_action.is_mutating() {
908 let mutated_utf8_match_start =
909 (rule_match.utf8_start as isize + *utf8_byte_delta) as usize;
910 let mutated_utf8_match_end = (rule_match.utf8_end as isize + *utf8_byte_delta) as usize;
911
912 debug_assert!(content.is_char_boundary(mutated_utf8_match_start));
914 debug_assert!(content.is_char_boundary(mutated_utf8_match_end));
915
916 let matched_content = &content[mutated_utf8_match_start..mutated_utf8_match_end];
917 if let Some(replacement) = rule.match_action.get_replacement(matched_content) {
918 let before_replacement = &matched_content[replacement.start..replacement.end];
919
920 <E>::adjust_shift(
922 custom_index_delta,
923 before_replacement,
924 &replacement.replacement,
925 );
926 *utf8_byte_delta +=
927 replacement.replacement.len() as isize - before_replacement.len() as isize;
928
929 let replacement_start = mutated_utf8_match_start + replacement.start;
930 let replacement_end = mutated_utf8_match_start + replacement.end;
931 content.replace_range(replacement_start..replacement_end, &replacement.replacement);
932 }
933 }
934
935 let shift_offset = <E>::get_shift(custom_index_delta, *utf8_byte_delta);
936 let custom_end = (<E>::get_index(&rule_match.custom_end, rule_match.utf8_end) as isize
937 + shift_offset) as usize;
938
939 let rule = &self.rules[rule_match.rule_index];
940
941 let match_status: MatchStatus = if rule.match_validation_type.is_some() {
942 MatchStatus::NotChecked
943 } else {
944 MatchStatus::NotAvailable
945 };
946
947 RuleMatch {
948 rule_index: rule_match.rule_index,
949 path,
950 replacement_type: rule.match_action.replacement_type(),
951 start_index: custom_start,
952 end_index_exclusive: custom_end,
953 shift_offset,
954 match_value: matched_content_copy,
955 match_status,
956 keyword: rule_match.keyword,
957 }
958 }
959
960 fn sort_and_remove_overlapping_rules<E: Encoding>(
961 &self,
962 rule_matches: &mut Vec<InternalRuleMatch<E>>,
963 ) {
964 rule_matches.sort_unstable_by(|a, b| {
968 let ord = self.rules[a.rule_index]
970 .match_action
971 .is_mutating()
972 .cmp(&self.rules[b.rule_index].match_action.is_mutating())
973 .reverse();
974
975 let ord = ord.then(a.utf8_start.cmp(&b.utf8_start));
977
978 let ord = ord.then(a.len().cmp(&b.len()).reverse());
980
981 let ord = ord.then(
983 self.rules[a.rule_index]
984 .precedence
985 .cmp(&self.rules[b.rule_index].precedence)
986 .reverse(),
987 );
988
989 let ord = ord.then(a.rule_index.cmp(&b.rule_index));
991
992 ord.reverse()
994 });
995
996 let mut retained_rules: Vec<InternalRuleMatch<E>> = vec![];
997
998 'rule_matches: while let Some(rule_match) = rule_matches.pop() {
999 if self.rules[rule_match.rule_index].match_action.is_mutating() {
1000 if let Some(last) = retained_rules.last()
1002 && last.utf8_end > rule_match.utf8_start
1003 {
1004 continue;
1005 }
1006 } else {
1007 for retained_rule in &retained_rules {
1010 if retained_rule.utf8_start < rule_match.utf8_end
1011 && retained_rule.utf8_end > rule_match.utf8_start
1012 {
1013 continue 'rule_matches;
1014 }
1015 }
1016 };
1017 retained_rules.push(rule_match);
1018 }
1019
1020 retained_rules.sort_unstable_by_key(|rule_match| rule_match.utf8_start);
1022
1023 *rule_matches = retained_rules;
1024 }
1025}
1026
1027impl Drop for Scanner {
1028 fn drop(&mut self) {
1029 let stats = &*GLOBAL_STATS;
1030 stats.scanner_deletions.increment(1);
1031 stats.decrement_total_scanners();
1032 }
1033}
1034
1035#[derive(Default)]
1036pub struct ScannerBuilder<'a> {
1037 rules: &'a [RootRuleConfig<Arc<dyn RuleConfig>>],
1038 labels: Labels,
1039 scanner_features: ScannerFeatures,
1040 async_scan_timeout: Duration,
1041}
1042
1043impl ScannerBuilder<'_> {
1044 pub fn new(rules: &[RootRuleConfig<Arc<dyn RuleConfig>>]) -> ScannerBuilder<'_> {
1045 ScannerBuilder {
1046 rules,
1047 labels: Labels::empty(),
1048 scanner_features: ScannerFeatures::default(),
1049 async_scan_timeout: Duration::from_secs(60 * 5),
1050 }
1051 }
1052
1053 pub fn labels(mut self, labels: Labels) -> Self {
1054 self.labels = labels;
1055 self
1056 }
1057
1058 pub fn with_async_scan_timeout(mut self, duration: Duration) -> Self {
1059 self.async_scan_timeout = duration;
1060 self
1061 }
1062
1063 pub fn with_implicit_wildcard_indexes_for_scopes(mut self, value: bool) -> Self {
1064 self.scanner_features.add_implicit_index_wildcards = value;
1065 self
1066 }
1067
1068 pub fn with_return_matches(mut self, value: bool) -> Self {
1069 self.scanner_features.return_matches = value;
1070 self
1071 }
1072
1073 pub fn with_multipass_v0(mut self, value: bool) -> Self {
1077 self.scanner_features.multipass_v0_enabled = value;
1078 self
1079 }
1080
1081 pub fn with_debug_observability(mut self, value: bool) -> Self {
1085 self.scanner_features.enable_debug_observability = value;
1086 self
1087 }
1088
1089 pub fn build(self) -> Result<Scanner, CreateScannerError> {
1090 #[cfg_attr(not(feature = "third-party-active-checkers"), allow(unused_mut))]
1093 let mut match_validators_per_type = AHashMap::new();
1094
1095 #[cfg(feature = "third-party-active-checkers")]
1096 for rule in self.rules.iter() {
1097 if let Some(match_validation_type) = &rule.get_third_party_active_checker()
1098 && match_validation_type.can_create_match_validator()
1099 {
1100 let internal_type = match_validation_type.get_internal_match_validation_type();
1101 let match_validator = match_validation_type.into_match_validator();
1102 if let Ok(match_validator) = match_validator {
1103 if !match_validators_per_type.contains_key(&internal_type) {
1104 match_validators_per_type.insert(internal_type, match_validator);
1105 }
1106 } else {
1107 return Err(CreateScannerError::InvalidMatchValidator(
1108 MatchValidatorCreationError::InternalError,
1109 ));
1110 }
1111 }
1112 }
1113
1114 let compiled_rules = self
1115 .rules
1116 .iter()
1117 .enumerate()
1118 .map(|(rule_index, config)| {
1119 if config.is_supporting_rule && config.match_action != MatchAction::None {
1120 return Err(CreateScannerError::SupportingRuleHasMatchAction);
1121 }
1122 let inner = config.convert_to_compiled_rule(rule_index, self.labels.clone())?;
1123 config.match_action.validate()?;
1124 let compiled_suppressions = match &config.suppressions {
1125 Some(s) => s.compile()?,
1126 None => None,
1127 };
1128 Ok(RootCompiledRule {
1129 inner,
1130 scope: config.scope.clone(),
1131 match_action: config.match_action.clone(),
1132 match_validation_type: config.get_third_party_active_checker().cloned(),
1133 suppressions: compiled_suppressions,
1134 precedence: config.precedence,
1135 is_supporting_rule: config.is_supporting_rule,
1136 })
1137 })
1138 .collect::<Result<Vec<RootCompiledRule>, CreateScannerError>>()?;
1139
1140 let mut per_scanner_data = SharedData::new();
1141
1142 compiled_rules.iter().for_each(|rule| {
1143 rule.init_per_scanner_data(&mut per_scanner_data);
1144 });
1145
1146 let scoped_ruleset = ScopedRuleSet::new(
1147 &compiled_rules
1148 .iter()
1149 .map(|rule| rule.scope.clone())
1150 .collect::<Vec<_>>(),
1151 )
1152 .with_implicit_index_wildcards(self.scanner_features.add_implicit_index_wildcards);
1153
1154 {
1155 let stats = &*GLOBAL_STATS;
1156 stats.scanner_creations.increment(1);
1157 stats.increment_total_scanners();
1158 }
1159
1160 Ok(Scanner {
1161 rules: compiled_rules,
1162 scoped_ruleset,
1163 scanner_features: self.scanner_features,
1164 metrics: ScannerMetrics::new(&self.labels),
1165 match_validators_per_type,
1166 labels: self.labels,
1167 per_scanner_data,
1168 async_scan_timeout: self.async_scan_timeout,
1169 })
1170 }
1171}
1172
1173struct ScannerContentVisitor<'a, E: Encoding> {
1174 scanner: &'a Scanner,
1175 regex_caches: &'a mut RegexCaches,
1176 rule_matches: &'a mut InternalRuleMatchSet<E>,
1177 blocked_rules: &'a Vec<usize>,
1180 excluded_matches: &'a mut AHashMap<String, String>,
1181 per_event_data: SharedData,
1182 wildcarded_indexes: &'a AHashMap<Path<'static>, Vec<(usize, usize)>>,
1183 async_jobs: &'a mut Vec<PendingRuleJob>,
1184 event_id: Option<String>,
1185 scan_metadata: &'a AHashMap<String, String>,
1186}
1187
1188impl<'a, E: Encoding> ContentVisitor<'a> for ScannerContentVisitor<'a, E> {
1189 fn visit_content<'b>(
1190 &'b mut self,
1191 path: &Path<'a>,
1192 content: &str,
1193 mut rule_visitor: crate::scoped_ruleset::RuleIndexVisitor,
1194 exclusion_check: ExclusionCheck<'b>,
1195 ) -> Result<bool, ScannerError> {
1196 let mut path_rules_matches = vec![];
1198
1199 let mut per_string_data = SharedData::new();
1201 let wildcard_indices_per_path = self.wildcarded_indexes.get(path);
1202
1203 rule_visitor.visit_rule_indices(|rule_index| {
1204 if self.blocked_rules.contains(&rule_index) {
1205 return Ok(());
1206 }
1207 let rule = &self.scanner.rules[rule_index];
1208 {
1209 if rule.inner.allow_scanner_to_exclude_namespace() {
1210 if exclusion_check.is_excluded(rule_index) {
1212 return Ok(());
1213 }
1214 }
1215 let mut emitter = |rule_match: StringMatch| {
1217 assert_ne!(
1220 rule_match.start, rule_match.end,
1221 "empty match detected on rule with index {rule_index}"
1222 );
1223 path_rules_matches.push(InternalRuleMatch::new(rule_index, rule_match));
1224 };
1225
1226 rule.init_per_string_data(&self.scanner.labels, &mut per_string_data);
1227
1228 rule.init_per_event_data(&mut self.per_event_data);
1230
1231 let mut ctx = StringMatchesCtx {
1232 rule_index,
1233 regex_caches: self.regex_caches,
1234 exclusion_check: &exclusion_check,
1235 excluded_matches: self.excluded_matches,
1236 match_emitter: &mut emitter,
1237 wildcard_indices: wildcard_indices_per_path,
1238 enable_debug_observability: self
1239 .scanner
1240 .scanner_features
1241 .enable_debug_observability,
1242 per_string_data: &mut per_string_data,
1243 per_scanner_data: &self.scanner.per_scanner_data,
1244 per_event_data: &mut self.per_event_data,
1245 event_id: self.event_id.as_deref(),
1246 scan_metadata: self.scan_metadata,
1247 };
1248
1249 let async_status = rule.get_string_matches(content, path, &mut ctx)?;
1250
1251 match async_status {
1252 RuleStatus::Done => {
1253 }
1255 RuleStatus::Pending(fut) => {
1256 self.async_jobs.push(PendingRuleJob {
1257 fut,
1258 path: path.into_static(),
1259 });
1260 }
1261 }
1262 }
1263 Ok(())
1264 })?;
1265
1266 let needs_to_access_content = !path_rules_matches.is_empty() || !self.async_jobs.is_empty();
1271
1272 self.rule_matches
1273 .push_sync_matches(path, path_rules_matches);
1274
1275 Ok(needs_to_access_content)
1276 }
1277}
1278
1279fn get_next_regex_start(content: &str, regex_match: (usize, usize)) -> Option<usize> {
1281 if let Some((i, _)) = content[regex_match.0..].char_indices().nth(1) {
1283 Some(regex_match.0 + i)
1284 } else {
1285 None
1287 }
1288}
1289
1290fn is_false_positive_match(
1291 regex_match_range: (usize, usize),
1292 rule: &RegexCompiledRule,
1293 content: &str,
1294 check_excluded_keywords: bool,
1295) -> bool {
1296 if check_excluded_keywords
1297 && let Some(excluded_keywords) = &rule.excluded_keywords
1298 && excluded_keywords.is_false_positive_match(content, regex_match_range.0)
1299 {
1300 return true;
1301 }
1302
1303 if let Some(validator) = rule.validator.as_ref()
1304 && !validator.is_valid_match(&content[regex_match_range.0..regex_match_range.1])
1305 {
1306 return true;
1307 }
1308 false
1309}