1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ScanResult {
11 pub manifest: Manifest,
13
14 pub agents: Vec<Agent>,
16
17 pub tools: Vec<Tool>,
19
20 pub models: Vec<Model>,
22
23 pub memory: Vec<Memory>,
25
26 #[serde(rename = "trustBoundaries")]
28 pub trust_boundaries: Vec<TrustBoundary>,
29
30 #[serde(rename = "secretFindings", skip_serializing_if = "Vec::is_empty")]
32 pub secret_findings: Vec<SecretFinding>,
33
34 #[serde(rename = "memoryFindings", skip_serializing_if = "Vec::is_empty")]
36 pub memory_findings: Vec<MemoryFinding>,
37
38 #[serde(rename = "networkFindings", skip_serializing_if = "Vec::is_empty")]
40 pub network_findings: Vec<NetworkFinding>,
41
42 #[serde(rename = "provenanceFindings", skip_serializing_if = "Vec::is_empty")]
44 pub provenance_findings: Vec<ProvenanceFinding>,
45}
46
47impl ScanResult {
48 pub fn new() -> Self {
49 Self {
50 manifest: Manifest::default(),
51 agents: Vec::new(),
52 tools: Vec::new(),
53 models: Vec::new(),
54 memory: Vec::new(),
55 trust_boundaries: Vec::new(),
56 secret_findings: Vec::new(),
57 memory_findings: Vec::new(),
58 network_findings: Vec::new(),
59 provenance_findings: Vec::new(),
60 }
61 }
62
63 pub fn to_yaml(&self) -> crate::Result<String> {
64 serde_yaml::to_string(self).map_err(Into::into)
65 }
66
67 pub fn to_json(&self) -> crate::Result<String> {
68 serde_json::to_string_pretty(self).map_err(Into::into)
69 }
70}
71
72impl Default for ScanResult {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78pub type AgentAssets = ScanResult;
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct Manifest {
84 pub schema_version: String,
86
87 pub subject: Subject,
89
90 pub scan_id: String,
92
93 pub scanned_at: String,
95
96 pub scanned_by: String,
98
99 pub files: Vec<String>,
101
102 pub scan_config: ScanConfigMetadata,
104
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub signature: Option<Signature>,
108}
109
110impl Default for Manifest {
111 fn default() -> Self {
112 Self {
113 schema_version: "0.1.0".to_string(),
114 subject: Subject::default(),
115 scan_id: generate_scan_id(),
116 scanned_at: chrono::Utc::now().to_rfc3339(),
117 scanned_by: format!("raxit-cli/{}", env!("CARGO_PKG_VERSION")),
118 files: Vec::new(),
119 scan_config: ScanConfigMetadata::default(),
120 signature: None,
121 }
122 }
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct Subject {
128 pub name: String,
130
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub version: Option<String>,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub source: Option<String>,
138}
139
140impl Default for Subject {
141 fn default() -> Self {
142 Self {
143 name: "unknown".to_string(),
144 version: None,
145 source: None,
146 }
147 }
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct ScanConfigMetadata {
153 pub exclude_patterns: Vec<String>,
155
156 pub frameworks_detected: Vec<String>,
158
159 pub parallel_workers: usize,
161
162 pub incremental: bool,
164
165 pub files_scanned: usize,
167
168 pub files_skipped: usize,
170}
171
172impl Default for ScanConfigMetadata {
173 fn default() -> Self {
174 Self {
175 exclude_patterns: Vec::new(),
176 frameworks_detected: Vec::new(),
177 parallel_workers: 1,
178 incremental: false,
179 files_scanned: 0,
180 files_skipped: 0,
181 }
182 }
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct Signature {
188 pub digest: String,
190
191 pub algorithm: String,
193
194 pub signature_value: String,
196
197 pub signed_at: String,
199
200 #[serde(skip_serializing_if = "Option::is_none")]
202 pub key_metadata: Option<KeyMetadata>,
203
204 #[serde(skip_serializing_if = "Option::is_none")]
206 pub attestation: Option<Attestation>,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct KeyMetadata {
212 pub key_id: String,
213 pub key_version: String,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct Attestation {
219 pub project_id: String,
220 pub project_name: String,
221 pub organization: String,
222 pub signed_by: String,
223}
224
225fn generate_scan_id() -> String {
227 let now = chrono::Utc::now();
228 let random_suffix: String = (0..6)
229 .map(|_| format!("{:x}", rand::random::<u8>() % 16))
230 .collect();
231 format!("scan-{}-{}", now.format("%Y%m%d"), random_suffix)
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct ScanMetadata {
237 pub framework: String,
239
240 #[serde(rename = "frameworkVersion")]
242 pub framework_version: Option<String>,
243
244 pub timestamp: String,
246
247 #[serde(rename = "raxitVersion")]
249 pub raxit_version: String,
250
251 pub source_path: String,
253}
254
255impl Default for ScanMetadata {
256 fn default() -> Self {
257 Self {
258 framework: "unknown".to_string(),
259 framework_version: None,
260 timestamp: chrono::Utc::now().to_rfc3339(),
261 raxit_version: env!("CARGO_PKG_VERSION").to_string(),
262 source_path: ".".to_string(),
263 }
264 }
265}
266
267#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct Agent {
270 pub id: String,
272
273 pub name: String,
275
276 pub location: SourceLocation,
278
279 #[serde(rename = "modelId")]
281 pub model_id: Option<String>,
282
283 #[serde(rename = "toolIds")]
285 pub tool_ids: Vec<String>,
286
287 #[serde(rename = "memoryId")]
289 pub memory_id: Option<String>,
290
291 #[serde(rename = "systemPrompt")]
293 pub system_prompt: Option<String>,
294
295 #[serde(rename = "resultType")]
297 pub result_type: Option<String>,
298
299 #[serde(rename = "depsType")]
301 pub deps_type: Option<String>,
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize)]
306pub struct Tool {
307 pub id: String,
309
310 pub name: String,
312
313 pub location: SourceLocation,
315
316 pub description: Option<String>,
318
319 pub parameters: Option<HashMap<String, String>>,
321
322 #[serde(rename = "requiresContext")]
324 pub requires_context: bool,
325
326 #[serde(rename = "toolType")]
328 pub tool_type: String,
329
330 #[serde(rename = "dataFlows")]
332 pub data_flows: Vec<DataFlow>,
333}
334
335#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct Model {
338 pub id: String,
340
341 pub provider: String,
343
344 #[serde(rename = "modelName")]
346 pub model_name: String,
347
348 pub location: SourceLocation,
350
351 pub config: HashMap<String, String>,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct Memory {
358 pub id: String,
360
361 #[serde(rename = "memoryType")]
363 pub memory_type: String,
364
365 pub location: SourceLocation,
367
368 pub config: HashMap<String, String>,
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct TrustBoundary {
375 pub id: String,
377
378 #[serde(rename = "componentId")]
380 pub component_id: String,
381
382 #[serde(rename = "componentType")]
384 pub component_type: String,
385
386 #[serde(rename = "hasUntrustedInput")]
388 pub has_untrusted_input: bool,
389
390 #[serde(rename = "hasSensitiveAccess")]
392 pub has_sensitive_access: bool,
393
394 #[serde(rename = "hasExternalActions")]
396 pub has_external_actions: bool,
397
398 pub compliant: bool,
400
401 pub violations: Vec<String>,
403
404 pub location: SourceLocation,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize)]
410pub struct SecretFinding {
411 pub id: String,
413
414 #[serde(rename = "secretType")]
416 pub secret_type: String,
417
418 pub location: SourceLocation,
420
421 pub severity: String,
423
424 pub message: String,
426
427 #[serde(rename = "matchedPattern", skip_serializing_if = "Option::is_none")]
429 pub matched_pattern: Option<String>,
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize)]
434pub struct MemoryFinding {
435 pub id: String,
437
438 #[serde(rename = "memoryType")]
440 pub memory_type: String,
441
442 pub technology: String,
444
445 pub location: SourceLocation,
447
448 #[serde(skip_serializing_if = "Option::is_none")]
450 pub configuration: Option<String>,
451
452 pub message: String,
454}
455
456#[derive(Debug, Clone, Serialize, Deserialize)]
458pub struct NetworkFinding {
459 pub id: String,
461
462 #[serde(rename = "networkType")]
464 pub network_type: String,
465
466 pub technology: String,
468
469 pub location: SourceLocation,
471
472 #[serde(skip_serializing_if = "Option::is_none")]
474 pub endpoint: Option<String>,
475
476 #[serde(skip_serializing_if = "Option::is_none")]
478 pub method: Option<String>,
479
480 pub message: String,
482}
483
484#[derive(Debug, Clone, Serialize, Deserialize)]
486pub struct ProvenanceFinding {
487 pub id: String,
489
490 #[serde(rename = "findingType")]
492 pub finding_type: String,
493
494 #[serde(rename = "sourceType")]
496 pub source_type: String,
497
498 #[serde(rename = "sinkType")]
500 pub sink_type: String,
501
502 #[serde(rename = "taintedVariables")]
504 pub tainted_variables: Vec<String>,
505
506 pub location: SourceLocation,
508
509 pub severity: String,
511
512 pub message: String,
514
515 #[serde(rename = "dataFlow", skip_serializing_if = "Option::is_none")]
517 pub data_flow: Option<String>,
518}
519
520#[derive(Debug, Clone, Serialize, Deserialize)]
522pub struct DataFlow {
523 pub variable: String,
525
526 pub source: String,
528
529 pub readers: Vec<String>,
531
532 pub writers: Vec<String>,
534
535 #[serde(rename = "taintLevel")]
537 pub taint_level: String,
538}
539
540#[derive(Debug, Clone, Serialize, Deserialize)]
542pub struct SourceLocation {
543 pub file: String,
545
546 pub line: u32,
548
549 #[serde(rename = "endLine")]
551 pub end_line: Option<u32>,
552
553 pub function: Option<String>,
555}
556
557#[cfg(test)]
558mod tests {
559 use super::*;
560
561 #[test]
562 fn test_scan_result_serialization() {
563 let result = ScanResult::new();
564 let yaml = result.to_yaml();
565 assert!(yaml.is_ok());
566
567 let json = result.to_json();
568 assert!(json.is_ok());
569 }
570
571 #[test]
572 fn test_default_metadata() {
573 let result = ScanResult::default();
574 assert_eq!(result.manifest.schema_version, "0.1.0");
575 assert!(result
576 .manifest
577 .scanned_by
578 .contains(env!("CARGO_PKG_VERSION")));
579 }
580}