use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScanResult {
pub manifest: Manifest,
pub agents: Vec<Agent>,
pub tools: Vec<Tool>,
pub models: Vec<Model>,
pub memory: Vec<Memory>,
#[serde(rename = "trustBoundaries")]
pub trust_boundaries: Vec<TrustBoundary>,
#[serde(rename = "secretFindings", skip_serializing_if = "Vec::is_empty")]
pub secret_findings: Vec<SecretFinding>,
#[serde(rename = "memoryFindings", skip_serializing_if = "Vec::is_empty")]
pub memory_findings: Vec<MemoryFinding>,
#[serde(rename = "networkFindings", skip_serializing_if = "Vec::is_empty")]
pub network_findings: Vec<NetworkFinding>,
#[serde(rename = "provenanceFindings", skip_serializing_if = "Vec::is_empty")]
pub provenance_findings: Vec<ProvenanceFinding>,
}
impl ScanResult {
pub fn new() -> Self {
Self {
manifest: Manifest::default(),
agents: Vec::new(),
tools: Vec::new(),
models: Vec::new(),
memory: Vec::new(),
trust_boundaries: Vec::new(),
secret_findings: Vec::new(),
memory_findings: Vec::new(),
network_findings: Vec::new(),
provenance_findings: Vec::new(),
}
}
pub fn to_yaml(&self) -> crate::Result<String> {
serde_yaml::to_string(self).map_err(Into::into)
}
pub fn to_json(&self) -> crate::Result<String> {
serde_json::to_string_pretty(self).map_err(Into::into)
}
}
impl Default for ScanResult {
fn default() -> Self {
Self::new()
}
}
pub type AgentAssets = ScanResult;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Manifest {
pub schema_version: String,
pub subject: Subject,
pub scan_id: String,
pub scanned_at: String,
pub scanned_by: String,
pub files: Vec<String>,
pub scan_config: ScanConfigMetadata,
#[serde(skip_serializing_if = "Option::is_none")]
pub signature: Option<Signature>,
}
impl Default for Manifest {
fn default() -> Self {
Self {
schema_version: "0.1.0".to_string(),
subject: Subject::default(),
scan_id: generate_scan_id(),
scanned_at: chrono::Utc::now().to_rfc3339(),
scanned_by: format!("raxit-cli/{}", env!("CARGO_PKG_VERSION")),
files: Vec::new(),
scan_config: ScanConfigMetadata::default(),
signature: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Subject {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
}
impl Default for Subject {
fn default() -> Self {
Self {
name: "unknown".to_string(),
version: None,
source: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScanConfigMetadata {
pub exclude_patterns: Vec<String>,
pub frameworks_detected: Vec<String>,
pub parallel_workers: usize,
pub incremental: bool,
pub files_scanned: usize,
pub files_skipped: usize,
}
impl Default for ScanConfigMetadata {
fn default() -> Self {
Self {
exclude_patterns: Vec::new(),
frameworks_detected: Vec::new(),
parallel_workers: 1,
incremental: false,
files_scanned: 0,
files_skipped: 0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Signature {
pub digest: String,
pub algorithm: String,
pub signature_value: String,
pub signed_at: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub key_metadata: Option<KeyMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attestation: Option<Attestation>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyMetadata {
pub key_id: String,
pub key_version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attestation {
pub project_id: String,
pub project_name: String,
pub organization: String,
pub signed_by: String,
}
fn generate_scan_id() -> String {
let now = chrono::Utc::now();
let random_suffix: String = (0..6)
.map(|_| format!("{:x}", rand::random::<u8>() % 16))
.collect();
format!("scan-{}-{}", now.format("%Y%m%d"), random_suffix)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScanMetadata {
pub framework: String,
#[serde(rename = "frameworkVersion")]
pub framework_version: Option<String>,
pub timestamp: String,
#[serde(rename = "raxitVersion")]
pub raxit_version: String,
pub source_path: String,
}
impl Default for ScanMetadata {
fn default() -> Self {
Self {
framework: "unknown".to_string(),
framework_version: None,
timestamp: chrono::Utc::now().to_rfc3339(),
raxit_version: env!("CARGO_PKG_VERSION").to_string(),
source_path: ".".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Agent {
pub id: String,
pub name: String,
pub location: SourceLocation,
#[serde(rename = "modelId")]
pub model_id: Option<String>,
#[serde(rename = "toolIds")]
pub tool_ids: Vec<String>,
#[serde(rename = "memoryId")]
pub memory_id: Option<String>,
#[serde(rename = "systemPrompt")]
pub system_prompt: Option<String>,
#[serde(rename = "resultType")]
pub result_type: Option<String>,
#[serde(rename = "depsType")]
pub deps_type: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tool {
pub id: String,
pub name: String,
pub location: SourceLocation,
pub description: Option<String>,
pub parameters: Option<HashMap<String, String>>,
#[serde(rename = "requiresContext")]
pub requires_context: bool,
#[serde(rename = "toolType")]
pub tool_type: String,
#[serde(rename = "dataFlows")]
pub data_flows: Vec<DataFlow>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Model {
pub id: String,
pub provider: String,
#[serde(rename = "modelName")]
pub model_name: String,
pub location: SourceLocation,
pub config: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Memory {
pub id: String,
#[serde(rename = "memoryType")]
pub memory_type: String,
pub location: SourceLocation,
pub config: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrustBoundary {
pub id: String,
#[serde(rename = "componentId")]
pub component_id: String,
#[serde(rename = "componentType")]
pub component_type: String,
#[serde(rename = "hasUntrustedInput")]
pub has_untrusted_input: bool,
#[serde(rename = "hasSensitiveAccess")]
pub has_sensitive_access: bool,
#[serde(rename = "hasExternalActions")]
pub has_external_actions: bool,
pub compliant: bool,
pub violations: Vec<String>,
pub location: SourceLocation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecretFinding {
pub id: String,
#[serde(rename = "secretType")]
pub secret_type: String,
pub location: SourceLocation,
pub severity: String,
pub message: String,
#[serde(rename = "matchedPattern", skip_serializing_if = "Option::is_none")]
pub matched_pattern: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryFinding {
pub id: String,
#[serde(rename = "memoryType")]
pub memory_type: String,
pub technology: String,
pub location: SourceLocation,
#[serde(skip_serializing_if = "Option::is_none")]
pub configuration: Option<String>,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkFinding {
pub id: String,
#[serde(rename = "networkType")]
pub network_type: String,
pub technology: String,
pub location: SourceLocation,
#[serde(skip_serializing_if = "Option::is_none")]
pub endpoint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<String>,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProvenanceFinding {
pub id: String,
#[serde(rename = "findingType")]
pub finding_type: String,
#[serde(rename = "sourceType")]
pub source_type: String,
#[serde(rename = "sinkType")]
pub sink_type: String,
#[serde(rename = "taintedVariables")]
pub tainted_variables: Vec<String>,
pub location: SourceLocation,
pub severity: String,
pub message: String,
#[serde(rename = "dataFlow", skip_serializing_if = "Option::is_none")]
pub data_flow: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataFlow {
pub variable: String,
pub source: String,
pub readers: Vec<String>,
pub writers: Vec<String>,
#[serde(rename = "taintLevel")]
pub taint_level: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceLocation {
pub file: String,
pub line: u32,
#[serde(rename = "endLine")]
pub end_line: Option<u32>,
pub function: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scan_result_serialization() {
let result = ScanResult::new();
let yaml = result.to_yaml();
assert!(yaml.is_ok());
let json = result.to_json();
assert!(json.is_ok());
}
#[test]
fn test_default_metadata() {
let result = ScanResult::default();
assert_eq!(result.manifest.schema_version, "0.1.0");
assert!(result
.manifest
.scanned_by
.contains(env!("CARGO_PKG_VERSION")));
}
}