Skip to main content

roder_ext_chrome/
policy.rs

1//! Action classification for browser commands.
2//!
3//! Roder policy gates browser actions in two layers beyond the per-origin site
4//! permissions enforced inside the extension:
5//!
6//! * **Protected** actions (eval, downloads, uploads, navigation, form submits)
7//!   require explicit approval and only run in `control` mode.
8//! * **Prohibited** actions (CAPTCHA bypass, raw payment/credential handling) are
9//!   refused outright and never reach the browser.
10
11use roder_api::chrome::ChromePermissionMode;
12
13/// How a browser command is treated by Roder policy.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum ChromeActionClass {
16    /// Read-only inspection; allowed in `assist`/`control` when the site permits.
17    Inspect,
18    /// Ordinary interaction (click/type/scroll); allowed in `control`.
19    Interact,
20    /// High-risk; requires explicit approval and `control` mode.
21    Protected,
22    /// Refused outright.
23    Prohibited,
24}
25
26/// Classify a wire command `kind` (e.g. `"page/click"`).
27pub fn classify_action(kind: &str) -> ChromeActionClass {
28    match kind {
29        // Refused outright.
30        "page/captcha" | "page/solveCaptcha" => ChromeActionClass::Prohibited,
31
32        // High-risk, approval + control mode.
33        "page/eval" | "page/upload" | "page/download" | "tab/navigate" => {
34            ChromeActionClass::Protected
35        }
36
37        // Interaction.
38        "page/click" | "page/type" | "page/keypress" | "page/scroll" | "page/select"
39        | "tab/open" | "tab/close" | "tab/activate" | "tabs/group" => ChromeActionClass::Interact,
40
41        // Everything else (snapshot, screenshot, tabs/list, debug reads, chat,
42        // permissions queries, recording) is inspection.
43        _ => ChromeActionClass::Inspect,
44    }
45}
46
47/// Returns `Err(reason)` if `kind` may not run under `mode`. The extension also
48/// enforces per-origin site permissions; this is the host-side action gate.
49pub fn guard(kind: &str, mode: ChromePermissionMode) -> Result<(), String> {
50    match classify_action(kind) {
51        ChromeActionClass::Prohibited => Err(format!(
52            "browser action {kind:?} is prohibited and cannot be performed"
53        )),
54        ChromeActionClass::Protected if mode != ChromePermissionMode::Control => Err(format!(
55            "browser action {kind:?} is protected and requires control mode plus explicit approval"
56        )),
57        ChromeActionClass::Interact if mode == ChromePermissionMode::Observe => Err(format!(
58            "browser action {kind:?} requires assist or control mode"
59        )),
60        _ => Ok(()),
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn prohibited_actions_are_refused_in_every_mode() {
70        for mode in [
71            ChromePermissionMode::Observe,
72            ChromePermissionMode::Assist,
73            ChromePermissionMode::Control,
74        ] {
75            assert!(guard("page/captcha", mode).is_err());
76        }
77    }
78
79    #[test]
80    fn protected_actions_require_control_mode() {
81        assert!(guard("page/eval", ChromePermissionMode::Assist).is_err());
82        assert!(guard("page/eval", ChromePermissionMode::Control).is_ok());
83    }
84
85    #[test]
86    fn inspection_allowed_in_assist() {
87        assert!(guard("page/snapshot", ChromePermissionMode::Assist).is_ok());
88        assert_eq!(classify_action("tabs/list"), ChromeActionClass::Inspect);
89    }
90}