Skip to main content

keyhog_core/spec/
evidence.rs

1use serde::{Deserialize, Serialize};
2
3/// Confidentiality policy for detector-owned provider evidence.
4#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(rename_all = "snake_case")]
6pub enum ProviderEvidenceSensitivity {
7    /// Emit the selected scalar value in reports.
8    Public,
9    /// Emit only a stable SHA-256 digest of the selected scalar value. This is
10    /// the fail-closed default for detector metadata that predates the field.
11    #[default]
12    Hashed,
13    /// Never admit the selected value to finding metadata or reports.
14    Secret,
15}
16
17/// Stable semantic role of provider evidence exposed in findings.
18///
19/// This vocabulary is provider-neutral. Detector TOML owns which response
20/// selector supplies a role, while reporters receive only these reviewed keys.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum ProviderEvidenceRole {
23    Account,
24    AccountId,
25    Active,
26    BotName,
27    ChannelId,
28    Company,
29    Data,
30    Email,
31    FriendlyName,
32    Id,
33    Login,
34    Name,
35    Organization,
36    Permissions,
37    Plan,
38    Project,
39    Region,
40    Resource,
41    Results,
42    Scope,
43    SeatCount,
44    Status,
45    StoreName,
46    Team,
47    TeamId,
48    Total,
49    User,
50    UserId,
51    UserPrincipalName,
52    Username,
53    Uuid,
54    WorkplaceName,
55}
56
57impl ProviderEvidenceRole {
58    /// Resolve the detector TOML name to a reviewed provider-neutral role.
59    pub fn from_metadata_name(name: &str) -> Option<Self> {
60        Some(match name {
61            "account" => Self::Account,
62            "account_id" | "accountID" => Self::AccountId,
63            "active" => Self::Active,
64            "bot_name" => Self::BotName,
65            "channel_id" => Self::ChannelId,
66            "company" => Self::Company,
67            "data" => Self::Data,
68            "email" => Self::Email,
69            "friendly_name" => Self::FriendlyName,
70            "id" => Self::Id,
71            "login" => Self::Login,
72            "name" => Self::Name,
73            "organization" => Self::Organization,
74            "permissions" => Self::Permissions,
75            "plan" => Self::Plan,
76            "project" => Self::Project,
77            "region" => Self::Region,
78            "resource" => Self::Resource,
79            "results" => Self::Results,
80            "scope" => Self::Scope,
81            "seat_count" | "seats" => Self::SeatCount,
82            "status" => Self::Status,
83            "store_name" => Self::StoreName,
84            "team" => Self::Team,
85            "team_id" | "teamId" => Self::TeamId,
86            "total" => Self::Total,
87            "user" => Self::User,
88            "user_id" => Self::UserId,
89            "user_principal_name" | "userPrincipalName" => Self::UserPrincipalName,
90            "username" => Self::Username,
91            "uuid" => Self::Uuid,
92            "workplace_name" => Self::WorkplaceName,
93            _ => return None,
94        })
95    }
96
97    /// Canonical report key for this semantic role.
98    pub const fn as_str(self) -> &'static str {
99        match self {
100            Self::Account => "account",
101            Self::AccountId => "account_id",
102            Self::Active => "active",
103            Self::BotName => "bot_name",
104            Self::ChannelId => "channel_id",
105            Self::Company => "company",
106            Self::Data => "data",
107            Self::Email => "email",
108            Self::FriendlyName => "friendly_name",
109            Self::Id => "id",
110            Self::Login => "login",
111            Self::Name => "name",
112            Self::Organization => "organization",
113            Self::Permissions => "permissions",
114            Self::Plan => "plan",
115            Self::Project => "project",
116            Self::Region => "region",
117            Self::Resource => "resource",
118            Self::Results => "results",
119            Self::Scope => "scope",
120            Self::SeatCount => "seat_count",
121            Self::Status => "status",
122            Self::StoreName => "store_name",
123            Self::Team => "team",
124            Self::TeamId => "team_id",
125            Self::Total => "total",
126            Self::User => "user",
127            Self::UserId => "user_id",
128            Self::UserPrincipalName => "user_principal_name",
129            Self::Username => "username",
130            Self::Uuid => "uuid",
131            Self::WorkplaceName => "workplace_name",
132        }
133    }
134}