Skip to main content

yeti_types/plugins/
trust.rs

1//! Plugin trust tier โ€” ADR-014 ยง4.
2//!
3//! Controls which host primitives a wasm component may import at link time.
4//!
5//! | Tier       | Allowed primitives                                           |
6//! |------------|--------------------------------------------------------------|
7//! | `Signed`   | All: `transport.tcp`, `transport.tls`, `secrets`, plus safe  |
8//! | `Unsigned` | Safe only: `http`, `persistence`, `pubsub`, `file`, `clock`, |
9//! |            |            `crypto`, `process`, `quota`                      |
10//!
11//! In `environment = "development"` all components are treated as `Signed`
12//! so unsigned plugins can be built and tested locally without a CI signing
13//! pipeline. In `environment = "production"` unsigned components attempting
14//! to use privileged primitives are rejected at link time.
15
16/// Trust classification assigned to a wasm component at load time.
17#[derive(
18    Debug, Default, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize,
19)]
20#[serde(rename_all = "snake_case")]
21pub enum PluginTrustTier {
22    /// Component is signed by a trusted key (cosign / sigil).
23    ///
24    /// May import raw transport (`transport.tcp`, `transport.tls`) and
25    /// the `secrets` interface. All safe-tier primitives also available.
26    Signed,
27
28    /// Component is unsigned (customer wasm or unsigned local plugin).
29    ///
30    /// May only import the safe high-level primitives: `http`, `persistence`,
31    /// `pubsub`, `file`, `clock`, `crypto`, `process`, `quota`.
32    /// Attempting to link against privileged primitives traps at instantiation.
33    #[default]
34    Unsigned,
35}
36
37impl PluginTrustTier {
38    /// Whether this tier may access raw TCP/TLS transport or the secrets interface.
39    #[must_use]
40    pub const fn is_privileged(&self) -> bool {
41        matches!(self, Self::Signed)
42    }
43
44    /// Determine the effective trust tier for a component given the environment
45    /// and whether a valid signature was found.
46    ///
47    /// In development mode the signing check is bypassed โ€” unsigned plugins load
48    /// and receive `Signed` privileges so local development doesn't require a
49    /// full cosign pipeline.
50    #[must_use]
51    pub fn resolve(is_signed: bool, environment: &str) -> Self {
52        if environment == "development" || is_signed {
53            Self::Signed
54        } else {
55            Self::Unsigned
56        }
57    }
58}
59
60impl std::fmt::Display for PluginTrustTier {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        match self {
63            Self::Signed => write!(f, "signed"),
64            Self::Unsigned => write!(f, "unsigned"),
65        }
66    }
67}