1pub(crate) mod capability;
7pub mod data_surface;
8pub mod dependency_surface;
9pub mod execution_surface;
10pub mod provenance_surface;
11pub mod taint_builder;
12pub mod tool_surface;
13
14use serde::{Deserialize, Serialize};
15use std::path::PathBuf;
16
17pub use data_surface::DataSurface;
18pub use dependency_surface::DependencySurface;
19pub use execution_surface::ExecutionSurface;
20pub use provenance_surface::ProvenanceSurface;
21pub use tool_surface::{
22 Capability, CapabilityDeclaration, CapabilityDeclarationSource, CapabilityEvidence, ToolSurface,
23};
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ScanTarget {
28 pub name: String,
30 pub framework: Framework,
32 pub root_path: PathBuf,
34 pub tools: Vec<ToolSurface>,
36 pub execution: ExecutionSurface,
38 pub data: DataSurface,
40 pub dependencies: DependencySurface,
42 pub provenance: ProvenanceSurface,
44 pub source_files: Vec<SourceFile>,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
50#[serde(rename_all = "snake_case")]
51pub enum Framework {
52 Mcp,
53 OpenClaw,
54 HermesAgent,
55 LangChain,
56 CrewAi,
57 GptActions,
58 CursorRules,
59 Unknown,
60}
61
62impl std::fmt::Display for Framework {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self {
65 Self::Mcp => write!(f, "MCP"),
66 Self::OpenClaw => write!(f, "OpenClaw"),
67 Self::HermesAgent => write!(f, "Hermes Agent"),
68 Self::LangChain => write!(f, "LangChain"),
69 Self::CrewAi => write!(f, "CrewAI"),
70 Self::GptActions => write!(f, "GPT Actions"),
71 Self::CursorRules => write!(f, "Cursor Rules"),
72 Self::Unknown => write!(f, "Unknown"),
73 }
74 }
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct SourceFile {
80 pub path: PathBuf,
81 pub language: Language,
82 pub content: String,
83 pub size_bytes: u64,
84 pub content_hash: String,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
89#[serde(rename_all = "lowercase")]
90pub enum Language {
91 Python,
92 TypeScript,
93 JavaScript,
94 Shell,
95 Json,
96 Toml,
97 Yaml,
98 Markdown,
99 Unknown,
100}
101
102impl Language {
103 pub fn from_extension(ext: &str) -> Self {
104 match ext.to_lowercase().as_str() {
105 "py" => Self::Python,
106 "ts" | "tsx" => Self::TypeScript,
107 "js" | "jsx" | "mjs" | "cjs" => Self::JavaScript,
108 "sh" | "bash" | "zsh" => Self::Shell,
109 "json" => Self::Json,
110 "toml" => Self::Toml,
111 "yml" | "yaml" => Self::Yaml,
112 "md" | "markdown" => Self::Markdown,
113 _ => Self::Unknown,
114 }
115 }
116
117 pub fn is_documentation(&self) -> bool {
118 matches!(self, Self::Markdown)
119 }
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
124pub struct SourceLocation {
125 pub file: PathBuf,
126 pub line: usize,
127 pub column: usize,
128 pub end_line: Option<usize>,
129 pub end_column: Option<usize>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
137#[serde(rename_all = "snake_case")]
138pub enum ArgumentSource {
139 Literal(String),
141 Parameter { name: String },
143 EnvVar { name: String },
145 Interpolated,
147 Unknown,
149 Sanitized { sanitizer: String },
151}
152
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
161pub enum SinkClass {
162 Command,
164 FilePath,
166 NetworkUrl,
168 DynamicExec,
170}
171
172impl ArgumentSource {
173 pub fn is_tainted(&self) -> bool {
180 !matches!(self, Self::Literal(_) | Self::Sanitized { .. })
181 }
182
183 pub fn is_tainted_for_sink(&self, sink: SinkClass) -> bool {
189 match self {
190 Self::Literal(_) => false,
191 Self::Sanitized { sanitizer } => {
192 !crate::analysis::cross_file::sanitizer_allows_sink(sanitizer, sink)
193 }
194 _ => true,
195 }
196 }
197}