#![allow(missing_docs)]
use crate::SensitiveString;
use serde::Serialize;
use std::sync::Arc;
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SourceCoverageGapKind {
Inaccessible,
Truncated,
}
impl std::fmt::Display for SourceCoverageGapKind {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(match self {
Self::Inaccessible => "inaccessible",
Self::Truncated => "truncated",
})
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Chunk {
pub data: SensitiveString,
pub metadata: ChunkMetadata,
}
impl From<String> for Chunk {
fn from(data: String) -> Self {
Self {
data: data.into(),
metadata: ChunkMetadata::default(),
}
}
}
impl From<&str> for Chunk {
fn from(data: &str) -> Self {
Self::from(data.to_string())
}
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct ChunkMetadata {
#[serde(with = "crate::finding::serde_arc_str")]
pub source_type: Arc<str>,
#[serde(with = "crate::finding::serde_arc_str_opt")]
pub path: Option<Arc<str>>,
#[serde(with = "crate::finding::serde_arc_str_opt")]
pub commit: Option<Arc<str>>,
#[serde(with = "crate::finding::serde_arc_str_opt")]
pub author: Option<Arc<str>>,
#[serde(with = "crate::finding::serde_arc_str_opt")]
pub date: Option<Arc<str>>,
pub base_offset: usize,
#[serde(default)]
pub base_line: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mtime_ns: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub size_bytes: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub decoded_span: Option<(usize, usize)>,
}
pub trait Source: Send + Sync {
fn name(&self) -> &str;
fn chunks(&self) -> Box<dyn Iterator<Item = Result<Chunk, SourceError>> + '_>;
fn as_any(&self) -> &dyn std::any::Any;
fn chunk_identities_are_contiguous(&self) -> bool {
false
}
}
#[derive(Debug, Error)]
pub enum SourceError {
#[error(
"failed to read source: {0}. Fix: check the path exists, is readable, and is not a broken symlink"
)]
Io(#[from] std::io::Error),
#[error(
"failed to access git source: {0}. Fix: run inside a valid git repository and verify the requested refs exist"
)]
Git(String),
#[error(
"source coverage gap ({kind}) in {adapter} surface {surface} at {target}: {detail}. Fix: grant read access or raise the relevant source limit, then rerun the affected surface"
)]
Coverage {
adapter: String,
surface: String,
target: String,
kind: SourceCoverageGapKind,
detail: String,
},
#[error("unknown source '{name}'. Fix: use a source name listed by `keyhog scan --help`")]
UnknownSource {
name: String,
},
#[error(
"source '{source_name}' is unavailable because this KeyHog artifact was built without the '{feature}' feature. Fix: install an artifact that includes '{feature}' or choose an enabled source"
)]
FeatureUnavailable {
source_name: String,
feature: String,
},
#[error(
"invalid configuration for source '{source_name}': {detail}. Fix: use the parameter format documented by `keyhog scan --help`"
)]
InvalidConfiguration {
source_name: String,
detail: String,
},
#[error(
"source name '{name}' is no longer accepted; use '{replacement}'. Fix: update the source identifier and rerun the scan"
)]
DeprecatedSourceName {
name: String,
replacement: String,
},
#[error(
"failed to read source: {0}. Fix: adjust the source settings or input so KeyHog can read plain text safely"
)]
Other(String),
}