11use crate :: findings:: Severity ;
22
3- /// Default per-run semgrep timeout (client side) in seconds .
3+ /// Per-file semgrep timeout in seconds (passed to `--timeout` flag) .
44pub const DEFAULT_SEMGREP_TIMEOUT_SECS : u64 = 300 ;
55
6+ /// Wall-clock timeout for the entire semgrep run in seconds.
7+ /// Separate from `DEFAULT_SEMGREP_TIMEOUT_SECS` to avoid aborting
8+ /// large projects that legitimately need more total time.
9+ pub const DEFAULT_SEMGREP_WALL_CLOCK_SECS : u64 = 1800 ;
10+
611/// Bundled taint-mode rules for Python (derived from presets/oss-python.toml)
712const TAINT_PYTHON_RULES : & str = include_str ! ( "rules/taint-python.yml" ) ;
813
@@ -31,8 +36,10 @@ const TAINT_CSHARP_RULES: &str = include_str!("rules/taint-csharp.yml");
3136pub struct SemgrepRunner {
3237 pub rulesets : Vec < String > ,
3338 pub exclude_rules : Vec < String > ,
34- /// Per-run timeout: passed to semgrep `--timeout` and enforced client-side .
39+ /// Per-file timeout: passed to semgrep `--timeout` flag .
3540 pub timeout_secs : u64 ,
41+ /// Wall-clock timeout for the entire semgrep run (client-side).
42+ pub wall_clock_timeout_secs : u64 ,
3643 /// Inline rule YAML documents (preset `custom_rules`), materialized to
3744 /// temp .yml files at scan time.
3845 pub custom_rules : Vec < String > ,
@@ -52,6 +59,7 @@ impl SemgrepRunner {
5259 rulesets,
5360 exclude_rules,
5461 timeout_secs : DEFAULT_SEMGREP_TIMEOUT_SECS ,
62+ wall_clock_timeout_secs : DEFAULT_SEMGREP_WALL_CLOCK_SECS ,
5563 custom_rules : Vec :: new ( ) ,
5664 languages : Vec :: new ( ) ,
5765 max_target_bytes : None ,
@@ -65,12 +73,18 @@ impl SemgrepRunner {
6573 self
6674 }
6775
68- /// Builder: set per-run timeout in seconds.
76+ /// Builder: set per-file timeout in seconds.
6977 pub fn with_timeout ( mut self , timeout_secs : u64 ) -> Self {
7078 self . timeout_secs = timeout_secs;
7179 self
7280 }
7381
82+ /// Builder: set wall-clock timeout for the entire semgrep run.
83+ pub fn with_wall_clock_timeout ( mut self , wall_clock_timeout_secs : u64 ) -> Self {
84+ self . wall_clock_timeout_secs = wall_clock_timeout_secs;
85+ self
86+ }
87+
7488 /// Builder: attach inline rule YAML documents from preset `custom_rules`.
7589 pub fn with_custom_rules ( mut self , custom_rules : Vec < String > ) -> Self {
7690 self . custom_rules = custom_rules;
@@ -196,22 +210,26 @@ pub struct RawFinding {
196210 pub message : Option < String > ,
197211}
198212
199- /// Map check_id to severity based on keyword matching
200- pub fn parse_severity ( check_id : & str ) -> Severity {
201- // Keyword-based matching (substring), preserve raise-only semantics
213+ /// Map check_id to severity based on keyword matching.
214+ /// Returns `None` when no keyword matches, so the parser can distinguish
215+ /// a real keyword-derived severity (which may upgrade an explicit
216+ /// `extra.severity`) from an unknown fallback (which must not).
217+ pub fn parse_severity ( check_id : & str ) -> Option < Severity > {
202218 match check_id. to_lowercase ( ) . as_str ( ) {
203- s if s. contains ( "critical" ) => Severity :: Critical ,
204- s if s. contains ( "high" ) => Severity :: High ,
205- s if s. contains ( "medium" ) => Severity :: Medium ,
206- s if s. contains ( "low" ) => Severity :: Low ,
207- _ => Severity :: Info ,
219+ s if s. contains ( "critical" ) => Some ( Severity :: Critical ) ,
220+ s if s. contains ( "high" ) => Some ( Severity :: High ) ,
221+ s if s. contains ( "medium" ) => Some ( Severity :: Medium ) ,
222+ s if s. contains ( "low" ) => Some ( Severity :: Low ) ,
223+ s if s. contains ( "info" ) => Some ( Severity :: Info ) ,
224+ _ => None ,
208225 }
209226}
210227
211228/// Collect rule IDs from a list of YAML documents.
212229/// Each document is expected to have a `rules` list with `id` fields.
213- /// Returns a BTreeSet of all rule IDs found, or an error naming the document index
214- /// and the serde error if any document is invalid YAML.
230+ /// Returns a BTreeSet of all rule IDs found. Malformed documents are
231+ /// skipped with a warning instead of aborting the entire collection,
232+ /// so a single bad rule does not suppress all taint rules.
215233pub fn collect_rule_ids ( docs : & [ String ] ) -> Result < std:: collections:: BTreeSet < String > , String > {
216234 use serde:: Deserialize ;
217235
@@ -228,12 +246,16 @@ pub fn collect_rule_ids(docs: &[String]) -> Result<std::collections::BTreeSet<St
228246 let mut ids = std:: collections:: BTreeSet :: new ( ) ;
229247
230248 for ( i, doc) in docs. iter ( ) . enumerate ( ) {
231- let parsed: RulesDoc = serde_yaml:: from_str ( doc)
232- . map_err ( |e| format ! ( "Invalid YAML at document index {}: {}" , i, e) ) ?;
233-
234- if let Some ( rules) = parsed. rules {
235- for rule in rules {
236- ids. insert ( rule. id ) ;
249+ match serde_yaml:: from_str :: < RulesDoc > ( doc) {
250+ Ok ( parsed) => {
251+ if let Some ( rules) = parsed. rules {
252+ for rule in rules {
253+ ids. insert ( rule. id ) ;
254+ }
255+ }
256+ }
257+ Err ( e) => {
258+ tracing:: warn!( "Skipping malformed custom rule at index {}: {}" , i, e) ;
237259 }
238260 }
239261 }
@@ -242,9 +264,9 @@ pub fn collect_rule_ids(docs: &[String]) -> Result<std::collections::BTreeSet<St
242264}
243265
244266/// Filter rules from a YAML document, excluding those whose IDs are in the exclude set.
245- /// Returns Ok(None) if no rules remain after filtering, Ok(Some(string)) with the
246- /// re-serialized document otherwise. On error, returns a string naming the problem.
247- /// The exclude set is not silently ignored — it must be valid .
267+ /// Returns Ok(None) if no rules remain after filtering or the document is malformed,
268+ /// Ok(Some(string)) with the re-serialized document otherwise. Malformed documents
269+ /// are skipped with a warning instead of aborting the entire scan .
248270pub fn filter_rules_excluding_ids (
249271 doc : & str ,
250272 exclude : & std:: collections:: BTreeSet < String > ,
@@ -265,8 +287,13 @@ pub fn filter_rules_excluding_ids(
265287 rest : std:: collections:: HashMap < String , serde_yaml:: Value > ,
266288 }
267289
268- let parsed: RulesDoc =
269- serde_yaml:: from_str ( doc) . map_err ( |e| format ! ( "Failed to parse YAML document: {}" , e) ) ?;
290+ let parsed: RulesDoc = match serde_yaml:: from_str ( doc) {
291+ Ok ( p) => p,
292+ Err ( e) => {
293+ tracing:: warn!( "Skipping malformed derived taint rule document: {}" , e) ;
294+ return Ok ( None ) ;
295+ }
296+ } ;
270297
271298 let filtered: Vec < Rule > = parsed
272299 . rules
0 commit comments