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 label.
24    Account,
25    /// Stable account identifier.
26    AccountId,
27    /// Whether the credential is currently active.
28    Active,
29    /// Bot display name.
30    BotName,
31    /// Channel identifier.
32    ChannelId,
33    /// Company name.
34    Company,
35    /// Opaque provider payload.
36    Data,
37    /// Email address.
38    Email,
39    /// Human-friendly display name.
40    FriendlyName,
41    /// Provider-assigned identifier.
42    Id,
43    /// Login handle.
44    Login,
45    /// Name.
46    Name,
47    /// Organization name.
48    Organization,
49    /// Granted permissions.
50    Permissions,
51    /// Subscription plan.
52    Plan,
53    /// Project name.
54    Project,
55    /// Region.
56    Region,
57    /// Resource identifier or name.
58    Resource,
59    /// Result collection returned by the probe.
60    Results,
61    /// Granted scopes.
62    Scope,
63    /// Licensed seat count.
64    SeatCount,
65    /// Status string.
66    Status,
67    /// Store name.
68    StoreName,
69    /// Team name.
70    Team,
71    /// Team identifier.
72    TeamId,
73    /// Total count reported by the probe.
74    Total,
75    /// User label.
76    User,
77    /// User identifier.
78    UserId,
79    /// User principal name.
80    UserPrincipalName,
81    /// Username.
82    Username,
83    /// UUID.
84    Uuid,
85    /// Workplace name.
86    WorkplaceName,
87}
88
89impl ProviderEvidenceRole {
90    /// Resolve the detector TOML name to a reviewed provider-neutral role.
91    pub fn from_metadata_name(name: &str) -> Option<Self> {
92        Some(match name {
93            "account" => Self::Account,
94            "account_id" | "accountID" => Self::AccountId,
95            "active" => Self::Active,
96            "bot_name" => Self::BotName,
97            "channel_id" => Self::ChannelId,
98            "company" => Self::Company,
99            "data" => Self::Data,
100            "email" => Self::Email,
101            "friendly_name" => Self::FriendlyName,
102            "id" => Self::Id,
103            "login" => Self::Login,
104            "name" => Self::Name,
105            "organization" => Self::Organization,
106            "permissions" => Self::Permissions,
107            "plan" => Self::Plan,
108            "project" => Self::Project,
109            "region" => Self::Region,
110            "resource" => Self::Resource,
111            "results" => Self::Results,
112            "scope" => Self::Scope,
113            "seat_count" | "seats" => Self::SeatCount,
114            "status" => Self::Status,
115            "store_name" => Self::StoreName,
116            "team" => Self::Team,
117            "team_id" | "teamId" => Self::TeamId,
118            "total" => Self::Total,
119            "user" => Self::User,
120            "user_id" => Self::UserId,
121            "user_principal_name" | "userPrincipalName" => Self::UserPrincipalName,
122            "username" => Self::Username,
123            "uuid" => Self::Uuid,
124            "workplace_name" => Self::WorkplaceName,
125            _ => return None,
126        })
127    }
128
129    /// Canonical report key for this semantic role.
130    pub const fn as_str(self) -> &'static str {
131        match self {
132            Self::Account => "account",
133            Self::AccountId => "account_id",
134            Self::Active => "active",
135            Self::BotName => "bot_name",
136            Self::ChannelId => "channel_id",
137            Self::Company => "company",
138            Self::Data => "data",
139            Self::Email => "email",
140            Self::FriendlyName => "friendly_name",
141            Self::Id => "id",
142            Self::Login => "login",
143            Self::Name => "name",
144            Self::Organization => "organization",
145            Self::Permissions => "permissions",
146            Self::Plan => "plan",
147            Self::Project => "project",
148            Self::Region => "region",
149            Self::Resource => "resource",
150            Self::Results => "results",
151            Self::Scope => "scope",
152            Self::SeatCount => "seat_count",
153            Self::Status => "status",
154            Self::StoreName => "store_name",
155            Self::Team => "team",
156            Self::TeamId => "team_id",
157            Self::Total => "total",
158            Self::User => "user",
159            Self::UserId => "user_id",
160            Self::UserPrincipalName => "user_principal_name",
161            Self::Username => "username",
162            Self::Uuid => "uuid",
163            Self::WorkplaceName => "workplace_name",
164        }
165    }
166}