Skip to main content

haloforge_plugin_api/
permissions.rs

1/// Fine-grained permissions a plugin must declare in its manifest.
2/// The host checks these at install time (user approval) and at runtime (sandbox enforcement).
3#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
4#[serde(tag = "type", content = "value", rename_all = "snake_case")]
5pub enum Permission {
6    /// Read any host table.
7    DatabaseReadAll,
8    /// Read a specific host table (e.g. "launch_profiles").
9    DatabaseRead(String),
10    /// Write to a specific host table. Restricted tier.
11    DatabaseWrite(String),
12    /// Create tables in the plugin's own namespace.
13    DatabaseCreateTables,
14
15    /// Read any filesystem path (prompts user on first use).
16    FilesystemRead,
17    /// Read within the HaloForge app-data directory only.
18    FilesystemReadAppData,
19    /// Write any filesystem path (prompts user on first use).
20    FilesystemWrite,
21    /// Write within the HaloForge app-data directory only.
22    FilesystemWriteAppData,
23
24    /// Make outbound HTTP requests to any URL.
25    NetworkHttp,
26    /// Make outbound HTTP requests to a specific domain only.
27    NetworkHttpDomain(String),
28
29    /// Register new Tauri IPC commands.
30    IpcRegister,
31
32    /// Emit events on the app event bus.
33    EventsEmit,
34    /// Listen to app lifecycle events.
35    EventsListen,
36
37    /// Inject into UI slots (implied by capability_levels 1/2).
38    UiInject,
39
40    /// Spawn any child process (high risk — Restricted tier).
41    ProcessSpawn,
42    /// Spawn only executables from a declared whitelist.
43    ProcessSpawnWhitelist(Vec<String>),
44
45    /// Show desktop toast notifications.
46    Notifications,
47
48    /// Read the clipboard.
49    ClipboardRead,
50    /// Write to the clipboard.
51    ClipboardWrite,
52
53    /// Navigate within the host UI (module switches, opening settings tabs).
54    HostNavigation,
55    /// Consume file-open intents routed by the host shell.
56    HostFileIntents,
57    /// Reuse the host AIChat transport and model selection.
58    HostAIChatAccess,
59    /// Read the active host theme and design tokens.
60    HostThemeRead,
61    /// Subscribe to stable host events exposed to plugins.
62    HostEventSubscribe,
63
64    /// Read app config (theme, language).
65    AppConfigRead,
66}
67
68impl Permission {
69    /// Approval tier for this permission.
70    pub fn tier(&self) -> PermissionTier {
71        match self {
72            Self::UiInject
73            | Self::EventsListen
74            | Self::DatabaseCreateTables
75            | Self::AppConfigRead
76            | Self::Notifications
77            | Self::HostThemeRead => PermissionTier::Transparent,
78
79            Self::DatabaseReadAll
80            | Self::DatabaseRead(_)
81            | Self::IpcRegister
82            | Self::EventsEmit
83            | Self::NetworkHttpDomain(_)
84            | Self::HostNavigation
85            | Self::HostFileIntents
86            | Self::HostAIChatAccess
87            | Self::HostEventSubscribe => PermissionTier::Standard,
88
89            Self::FilesystemRead
90            | Self::FilesystemWrite
91            | Self::FilesystemReadAppData
92            | Self::FilesystemWriteAppData
93            | Self::NetworkHttp
94            | Self::ProcessSpawnWhitelist(_)
95            | Self::ClipboardRead
96            | Self::ClipboardWrite => PermissionTier::Sensitive,
97
98            Self::DatabaseWrite(_)
99            | Self::ProcessSpawn => PermissionTier::Restricted,
100        }
101    }
102
103    /// Human-readable description shown in the permission prompt.
104    pub fn description(&self) -> String {
105        match self {
106            Self::DatabaseReadAll           => "Read all app data".into(),
107            Self::DatabaseRead(t)           => format!("Read table: {t}"),
108            Self::DatabaseWrite(t)          => format!("Write to table: {t}"),
109            Self::DatabaseCreateTables      => "Create plugin-owned database tables".into(),
110            Self::FilesystemRead            => "Read files from your filesystem".into(),
111            Self::FilesystemReadAppData     => "Read files in the app data directory".into(),
112            Self::FilesystemWrite           => "Write files to your filesystem".into(),
113            Self::FilesystemWriteAppData    => "Write files in the app data directory".into(),
114            Self::NetworkHttp               => "Make outbound HTTP requests".into(),
115            Self::NetworkHttpDomain(d)      => format!("Make HTTP requests to: {d}"),
116            Self::IpcRegister               => "Register new app commands".into(),
117            Self::EventsEmit                => "Emit app events".into(),
118            Self::EventsListen              => "Listen to app lifecycle events".into(),
119            Self::UiInject                  => "Inject UI components".into(),
120            Self::ProcessSpawn              => "Spawn arbitrary child processes".into(),
121            Self::ProcessSpawnWhitelist(v)  => format!("Spawn processes: {}", v.join(", ")),
122            Self::Notifications             => "Show desktop notifications".into(),
123            Self::ClipboardRead             => "Read the clipboard".into(),
124            Self::ClipboardWrite            => "Write to the clipboard".into(),
125            Self::HostNavigation            => "Navigate within HaloForge".into(),
126            Self::HostFileIntents           => "Receive file-open intents from HaloForge".into(),
127            Self::HostAIChatAccess          => "Use HaloForge AI models and chat transport".into(),
128            Self::HostThemeRead             => "Read HaloForge theme tokens".into(),
129            Self::HostEventSubscribe        => "Subscribe to HaloForge host events".into(),
130            Self::AppConfigRead             => "Read app configuration".into(),
131        }
132    }
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
136pub enum PermissionTier {
137    /// Auto-granted at install time with no user prompt.
138    Transparent = 0,
139    /// Shown once at install time; user approves/denies.
140    Standard = 1,
141    /// Shown at install + confirmation on first actual use.
142    Sensitive = 2,
143    /// Disabled by default; user must manually enable in Plugin Manager.
144    Restricted = 3,
145}
146
147#[cfg(test)]
148mod tests {
149    use super::{Permission, PermissionTier};
150
151    #[test]
152    fn host_permissions_have_expected_tiers() {
153        assert_eq!(Permission::HostThemeRead.tier(), PermissionTier::Transparent);
154        assert_eq!(Permission::HostNavigation.tier(), PermissionTier::Standard);
155        assert_eq!(Permission::HostAIChatAccess.tier(), PermissionTier::Standard);
156    }
157}