envseal 0.3.10

Write-only secret vault with process-level access control — post-agent secret management
Documentation
//! Event types written to the audit log.
//!
//! Each variant serializes via Serde with a stable `event` discriminator
//! string so log entries remain machine-readable across versions. Adding a
//! variant is fine; renaming or removing one is a breaking change to
//! external log consumers.

use serde::{Deserialize, Serialize};

/// Events recorded in the audit log.
///
/// `Serialize` is used by the writer (`audit::log_at`).
/// `Deserialize` is used by the reader (`audit::parse_entry`) so
/// CLI / desktop GUI / external consumers can parse a log line back
/// into a typed enum without re-implementing the JSON shape.
#[derive(Debug, Deserialize, Serialize)]
#[serde(tag = "event")]
pub enum AuditEvent {
    /// A GUI approval popup was shown.
    #[serde(rename = "approval_requested")]
    ApprovalRequested {
        /// Binary requesting access.
        binary: String,
        /// Secret name requested.
        secret: String,
    },

    /// The user responded to an approval popup.
    #[serde(rename = "approval_result")]
    ApprovalResult {
        /// Binary that requested access.
        binary: String,
        /// Secret name.
        secret: String,
        /// User decision: `AllowOnce`, `AllowAlways`, `Deny`, or `error`.
        decision: String,
    },

    /// A secret was stored in the vault.
    #[serde(rename = "secret_stored")]
    SecretStored {
        /// Name of the secret.
        name: String,
    },

    /// A secret was revoked (deleted).
    #[serde(rename = "secret_revoked")]
    SecretRevoked {
        /// Name of the secret.
        name: String,
    },

    /// One detector signal was recorded — fired by every signal that
    /// flowed through `guard::evaluate(...) -> Decision` regardless
    /// of action (Log / Warn / Block). Was previously called
    /// `EnvironmentBlocked`, which was misleading because `Warn`
    /// and `Log` actions never block. Renamed in 0.3.0; the JSON
    /// event tag is also updated. Audit logs pre-dating 0.3.0 carry
    /// `event: "environment_blocked"` and remain readable through
    /// the deserialize alias.
    #[serde(rename = "signal_recorded", alias = "environment_blocked")]
    SignalRecorded {
        /// Security tier active at the time the signal fired.
        tier: String,
        /// `<severity> [<signal-id>] <label>` — denormalized form
        /// for grep-friendly forensics.
        classification: String,
    },

    /// Challenge code was attempted.
    #[serde(rename = "challenge_attempted")]
    ChallengeAttempted {
        /// Whether the challenge was passed.
        passed: bool,
    },

    /// Security tier was changed.
    #[serde(rename = "tier_changed")]
    TierChanged {
        /// Previous tier.
        from: String,
        /// New tier.
        to: String,
    },

    /// Rate limiting triggered (anti-approval-fatigue).
    #[serde(rename = "rate_limited")]
    RateLimited {
        /// Why the rate limit was triggered.
        reason: String,
    },

    /// Secret was accessed for injection/pipe delivery.
    #[serde(rename = "secret_accessed")]
    SecretAccessed {
        /// Binary path that requested secret access.
        binary: String,
        /// Secret name being accessed.
        secret: String,
    },

    /// Supervised leak detection event.
    #[serde(rename = "supervisor_leak_detected")]
    SupervisorLeakDetected {
        /// Secret name associated with leaked plaintext.
        secret: String,
        /// Binary responsible for the leak.
        binary: String,
        /// Number of leaked bytes observed.
        leak_count: usize,
    },

    /// A vault `store` call failed (used by `__preexec` to record
    /// rejected captures without creating noisy interactive errors).
    #[serde(rename = "secret_store_failed")]
    SecretStoreFailed {
        /// Name we attempted to store under.
        name: String,
        /// Reason from the underlying error.
        reason: String,
    },

    /// A FIDO2 authenticator was enrolled. The `credential_id_hash`
    /// is the SHA-256 of the credential id — the id itself is opaque
    /// but logging the full bytes would leak it to anyone who reads
    /// the audit file. The hash uniquely identifies the credential
    /// for forensics without exposing it.
    #[serde(rename = "fido2_enrolled")]
    Fido2Enrolled {
        /// SHA-256 of the credential id, hex-encoded.
        credential_id_hash: String,
    },

    /// FIDO2 enrollment was disabled — the vault was re-wrapped from
    /// the v3 envelope back into the legacy v1 layout.
    #[serde(rename = "fido2_disabled")]
    Fido2Disabled {
        /// SHA-256 of the credential id that was just disabled, hex.
        credential_id_hash: String,
    },

    /// A FIDO2 unlock attempt was made. `succeeded = true` means the
    /// authenticator returned a valid hmac-secret response and the
    /// AES-GCM unwrap succeeded. `false` covers both wrong-key and
    /// no-touch outcomes — the audit record cannot distinguish them
    /// because the authenticator never reports the difference.
    #[serde(rename = "fido2_unlock")]
    Fido2Unlock {
        /// SHA-256 of the credential id presented, hex.
        credential_id_hash: String,
        /// Whether the unlock succeeded.
        succeeded: bool,
    },
}