source_map_tauri/
security.rs1use once_cell::sync::Lazy;
2use regex::Regex;
3
4use crate::model::ArtifactDoc;
5
6pub struct RiskAssessment {
7 pub level: String,
8 pub reasons: Vec<String>,
9 pub contains_phi: bool,
10}
11
12static BEARER_RE: Lazy<Regex> =
13 Lazy::new(|| Regex::new(r"Bearer\s+[A-Za-z0-9._\-]+").expect("valid regex"));
14static URL_AUTH_RE: Lazy<Regex> =
15 Lazy::new(|| Regex::new(r"https?://[^/\s:@]+:[^/\s:@]+@").expect("valid regex"));
16
17pub fn redact_text(input: &str) -> String {
18 let step = BEARER_RE.replace_all(input, "Bearer [REDACTED_SECRET]");
19 URL_AUTH_RE
20 .replace_all(&step, "https://[REDACTED_SECRET]@")
21 .into_owned()
22}
23
24pub fn assess_risk(values: &[String]) -> RiskAssessment {
25 let joined = values.join(" ").to_lowercase();
26 let mut reasons = Vec::new();
27
28 for keyword in [
29 "patient",
30 "phi",
31 "mrn",
32 "consent",
33 "medication",
34 "lab",
35 "diagnosis",
36 "billing",
37 "insurance",
38 "discharge",
39 "audit",
40 "upload",
41 "export",
42 ] {
43 if joined.contains(keyword) {
44 reasons.push(format!("{keyword} keyword"));
45 }
46 }
47
48 let level = if joined.contains("database")
49 || joined.contains("filesystem_export")
50 || joined.contains("external_integration")
51 {
52 "critical"
53 } else if !reasons.is_empty() {
54 "high"
55 } else {
56 "low"
57 };
58
59 RiskAssessment {
60 level: level.to_owned(),
61 reasons,
62 contains_phi: false,
63 }
64}
65
66pub fn apply_artifact_security(doc: &mut ArtifactDoc) {
67 doc.comments = doc.comments.iter().map(|item| redact_text(item)).collect();
68 doc.tags = doc.tags.iter().map(|item| redact_text(item)).collect();
69 let mut samples = Vec::new();
70 if let Some(name) = &doc.name {
71 samples.push(name.clone());
72 }
73 if let Some(path) = &doc.source_path {
74 samples.push(path.clone());
75 }
76 samples.extend(doc.tags.clone());
77 samples.extend(doc.comments.clone());
78 let assessment = assess_risk(&samples);
79 if doc.risk_level == "low" || doc.risk_level.is_empty() {
80 doc.risk_level = assessment.level;
81 }
82 if doc.risk_reasons.is_empty() {
83 doc.risk_reasons = assessment.reasons;
84 }
85 doc.contains_phi = assessment.contains_phi;
86}