cipherscope/
lib.rs

1use ahash::{AHashMap as HashMap, AHashSet as HashSet};
2use anyhow::Result;
3use serde::Serialize;
4use std::sync::LazyLock;
5
6pub mod patterns;
7pub mod scan;
8
9pub const DEFAULT_PATTERNS: &str = include_str!("../patterns.toml");
10
11#[derive(Serialize, Clone)]
12pub struct Evidence {
13    pub line: usize,
14    pub column: usize,
15}
16
17#[derive(Serialize, Clone)]
18pub struct Finding {
19    #[serde(rename = "assetType")]
20    pub asset_type: String,
21    pub identifier: String,
22    pub path: String,
23    pub evidence: Evidence,
24    #[serde(skip_serializing_if = "map_is_empty")]
25    pub metadata: HashMap<String, serde_json::Value>,
26}
27
28fn map_is_empty(m: &HashMap<String, serde_json::Value>) -> bool {
29    m.is_empty()
30}
31
32static PATTERNS: LazyLock<patterns::PatternSet> = LazyLock::new(|| {
33    patterns::PatternSet::from_toml(DEFAULT_PATTERNS).expect("valid patterns.toml")
34});
35
36pub fn scan_snippet(
37    content: &str,
38    lang: patterns::Language,
39    source_label: &str,
40) -> Result<Vec<Finding>> {
41    if !scan::has_anchor_hint(lang, content, &PATTERNS) {
42        return Ok(Vec::new());
43    }
44
45    let tree = scan::parse(lang, content)?;
46    let lib_hits = scan::find_library_anchors(lang, content, &tree, &PATTERNS);
47    if lib_hits.is_empty() {
48        return Ok(Vec::new());
49    }
50
51    let mut findings = Vec::new();
52    let mut seen: HashSet<String> = HashSet::new();
53    let mut alg_hits_all = Vec::new();
54
55    for lib in lib_hits {
56        let evidence = Evidence {
57            line: lib.line,
58            column: lib.column,
59        };
60        let finding = Finding {
61            asset_type: "library".to_string(),
62            identifier: lib.library_name.to_string(),
63            path: source_label.to_string(),
64            evidence,
65            metadata: HashMap::new(),
66        };
67        let key = format!("lib|{}", finding.identifier);
68        if seen.insert(key) {
69            findings.push(finding);
70        }
71
72        let alg_hits = scan::find_algorithms(lang, content, &tree, &PATTERNS, lib.library_name);
73        alg_hits_all.extend(alg_hits);
74    }
75
76    let alg_hits_all = scan::dedupe_more_specific_hits(alg_hits_all);
77    for alg in alg_hits_all {
78        let mut metadata = HashMap::new();
79        for (k, v) in alg.metadata {
80            metadata.insert(k.to_string(), v);
81        }
82        let evidence = Evidence {
83            line: alg.line,
84            column: alg.column,
85        };
86        let finding = Finding {
87            asset_type: "algorithm".to_string(),
88            identifier: alg.algorithm_name.to_string(),
89            path: source_label.to_string(),
90            evidence,
91            metadata,
92        };
93        let key = format!(
94            "alg|{}|{}:{}",
95            finding.identifier, finding.evidence.line, finding.evidence.column
96        );
97        if seen.insert(key) {
98            findings.push(finding);
99        }
100    }
101
102    Ok(findings)
103}