agentshield/adapter/
openclaw.rs1use std::path::{Path, PathBuf};
2
3use crate::analysis::cross_file::apply_cross_file_sanitization;
4use crate::config::ScanPathFilter;
5use crate::error::Result;
6use crate::ir::taint_builder::build_data_surface;
7use crate::ir::*;
8use crate::parser;
9
10pub struct OpenClawAdapter;
14
15impl super::Adapter for OpenClawAdapter {
16 fn framework(&self) -> Framework {
17 Framework::OpenClaw
18 }
19
20 fn detect(&self, root: &Path) -> bool {
21 root.join("SKILL.md").exists()
22 }
23
24 fn load(&self, root: &Path, ignore_tests: bool) -> Result<Vec<ScanTarget>> {
25 let filter = ScanPathFilter::for_ignore_tests(ignore_tests);
26 self.load_with_filter(root, &filter)
27 }
28
29 fn load_with_filter(&self, root: &Path, filter: &ScanPathFilter) -> Result<Vec<ScanTarget>> {
30 let name = root
31 .file_name()
32 .map(|n| n.to_string_lossy().to_string())
33 .unwrap_or_else(|| "openclaw-skill".into());
34
35 let mut source_files = Vec::new();
36 let mut execution = execution_surface::ExecutionSurface::default();
37
38 let walker = ignore::WalkBuilder::new(root)
40 .hidden(true)
41 .git_ignore(true)
42 .max_depth(Some(3))
43 .build();
44
45 for entry in walker.flatten() {
46 let path = entry.path();
47 if !path.is_file() {
48 continue;
49 }
50
51 if filter.ignore_tests() && super::mcp::is_test_file(path) {
52 continue;
53 }
54
55 if !filter.allows_path(root, path) {
56 continue;
57 }
58
59 let ext = path
60 .extension()
61 .map(|e| e.to_string_lossy().to_string())
62 .unwrap_or_default();
63 let lang = Language::from_extension(&ext);
64
65 if !matches!(
66 lang,
67 Language::Python | Language::Shell | Language::Markdown
68 ) {
69 continue;
70 }
71
72 let metadata = std::fs::metadata(path)?;
73 if metadata.len() > 1_048_576 {
74 continue;
75 }
76
77 if let Ok(content) = std::fs::read_to_string(path) {
78 use sha2::Digest;
79 let hash = format!(
80 "{:x}",
81 sha2::Sha256::new()
82 .chain_update(content.as_bytes())
83 .finalize()
84 );
85 source_files.push(SourceFile {
86 path: path.to_path_buf(),
87 language: lang,
88 size_bytes: metadata.len(),
89 content_hash: hash,
90 content,
91 });
92 }
93 }
94
95 let mut parsed_files: Vec<(PathBuf, parser::ParsedFile)> = Vec::new();
97 for sf in &source_files {
98 if let Some(parser) = parser::parser_for_language(sf.language) {
99 if let Ok(parsed) = parser.parse_file(&sf.path, &sf.content) {
100 parsed_files.push((sf.path.clone(), parsed));
101 }
102 }
103 }
104
105 apply_cross_file_sanitization(&mut parsed_files);
107
108 for (_, parsed) in parsed_files {
110 execution.commands.extend(parsed.commands);
111 execution.file_operations.extend(parsed.file_operations);
112 execution
113 .network_operations
114 .extend(parsed.network_operations);
115 execution.env_accesses.extend(parsed.env_accesses);
116 execution.dynamic_exec.extend(parsed.dynamic_exec);
117 }
118
119 let tools = vec![];
120 let data = build_data_surface(&tools, &execution);
121
122 Ok(vec![ScanTarget {
123 name,
124 framework: Framework::OpenClaw,
125 root_path: root.to_path_buf(),
126 tools,
127 execution,
128 data,
129 dependencies: Default::default(),
130 provenance: Default::default(),
131 source_files,
132 }])
133 }
134}