fastskill_core/validation/
content_safety.rs1use crate::core::service::ServiceError;
4use crate::validation::result::{ErrorSeverity, ValidationResult};
5use std::path::Path;
6use tokio::fs;
7
8pub(crate) enum PatternCheckContext<'a> {
10 SkillFile,
11 ScriptFile(&'a Path),
12}
13
14pub(crate) struct DangerousPatternCheck<'a> {
16 pub content: &'a str,
17 pub patterns: &'a [String],
18 pub field: &'a str,
19 pub context: PatternCheckContext<'a>,
20}
21
22pub(crate) fn add_dangerous_pattern_errors(
23 mut result: ValidationResult,
24 check: DangerousPatternCheck<'_>,
25) -> ValidationResult {
26 let message = |p: &str| match &check.context {
27 PatternCheckContext::SkillFile => {
28 format!("Potentially dangerous pattern found in SKILL.md: {}", p)
29 }
30 PatternCheckContext::ScriptFile(path) => format!(
31 "Potentially dangerous pattern found in script {}: {}",
32 path.display(),
33 p
34 ),
35 };
36 for pattern in check.patterns {
37 if check.content.contains(pattern) {
38 result = result.with_error(check.field, &message(pattern), ErrorSeverity::Critical);
39 }
40 }
41 result
42}
43
44pub(crate) async fn validate_skill_file_content(
45 path: &Path,
46 result: ValidationResult,
47 patterns: &[String],
48) -> Result<ValidationResult, ServiceError> {
49 let content = match fs::read_to_string(path).await {
50 Ok(c) => c,
51 Err(e) => {
52 return Ok(result.with_error(
53 "content",
54 &format!("Cannot read SKILL.md content for safety validation: {}", e),
55 ErrorSeverity::Error,
56 ));
57 }
58 };
59 let result = add_dangerous_pattern_errors(
60 result,
61 DangerousPatternCheck {
62 content: &content,
63 patterns,
64 field: "content",
65 context: PatternCheckContext::SkillFile,
66 },
67 );
68 let result = if content.len() > 50_000 {
69 result.with_warning(
70 "content",
71 "SKILL.md content is very large - consider moving detailed information to reference files",
72 )
73 } else {
74 result
75 };
76 Ok(result)
77}
78
79pub(crate) async fn validate_script_file_content(
80 path: &Path,
81 result: ValidationResult,
82 patterns: &[String],
83) -> Result<ValidationResult, ServiceError> {
84 let content = match fs::read_to_string(path).await {
85 Ok(c) => c,
86 Err(e) => {
87 return Ok(result.with_warning(
88 "script_content",
89 &format!(
90 "Cannot read script file {} for safety validation: {}",
91 path.display(),
92 e
93 ),
94 ));
95 }
96 };
97 let result = add_dangerous_pattern_errors(
98 result,
99 DangerousPatternCheck {
100 content: &content,
101 patterns,
102 field: "script_content",
103 context: PatternCheckContext::ScriptFile(path),
104 },
105 );
106 Ok(result)
107}
108
109pub(crate) fn default_dangerous_patterns() -> Vec<String> {
110 vec![
111 "import os".to_string(),
112 "import subprocess".to_string(),
113 "import sys".to_string(),
114 "exec(".to_string(),
115 "eval(".to_string(),
116 "system(".to_string(),
117 "popen(".to_string(),
118 "rm -rf".to_string(),
119 "sudo".to_string(),
120 "chmod 777".to_string(),
121 "chown".to_string(),
122 "su ".to_string(),
123 "passwd".to_string(),
124 ]
125}