Skip to main content

systemprompt_models/bridge/opencode/
policy.rs

1//! Typed `policies` statements and the deny-then-allow lockdown.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum PolicyEffect {
11    Allow,
12    Deny,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub enum PolicyAction {
17    #[serde(rename = "tool.use")]
18    Tool,
19    #[serde(rename = "provider.use")]
20    Provider,
21    #[serde(rename = "integration.use")]
22    Integration,
23}
24
25/// One `policies[]` statement.
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct Policy {
28    pub effect: PolicyEffect,
29    pub action: PolicyAction,
30    pub resource: String,
31}
32
33impl Policy {
34    #[must_use]
35    pub fn allow(action: PolicyAction, resource: impl Into<String>) -> Self {
36        Self {
37            effect: PolicyEffect::Allow,
38            action,
39            resource: resource.into(),
40        }
41    }
42
43    #[must_use]
44    pub fn deny(action: PolicyAction, resource: impl Into<String>) -> Self {
45        Self {
46            effect: PolicyEffect::Deny,
47            action,
48            resource: resource.into(),
49        }
50    }
51}
52
53/// What [`lockdown_policies`] confines a client to.
54#[derive(Debug, Clone, Copy)]
55pub struct Lockdown<'a> {
56    pub provider: &'a str,
57    pub allowed_tools: &'a [String],
58    pub denied_tools: &'a [String],
59    pub allowed_mcp: &'a [String],
60    pub allowed_plugins: &'a [String],
61}
62
63#[must_use]
64pub fn lockdown_policies(lockdown: &Lockdown<'_>) -> Vec<Policy> {
65    let mut out = vec![
66        Policy::deny(PolicyAction::Provider, "*"),
67        Policy::allow(PolicyAction::Provider, lockdown.provider),
68        Policy::deny(PolicyAction::Integration, "mcp:*"),
69        Policy::deny(PolicyAction::Integration, "plugin:*"),
70    ];
71    out.extend(
72        lockdown
73            .allowed_plugins
74            .iter()
75            .map(|plugin| Policy::allow(PolicyAction::Integration, format!("plugin:{plugin}"))),
76    );
77    out.extend(
78        lockdown
79            .allowed_mcp
80            .iter()
81            .map(|name| Policy::allow(PolicyAction::Integration, format!("mcp:{name}"))),
82    );
83    if !lockdown.allowed_tools.is_empty() {
84        out.push(Policy::deny(PolicyAction::Tool, "*"));
85        out.extend(
86            lockdown
87                .allowed_tools
88                .iter()
89                .map(|tool| Policy::allow(PolicyAction::Tool, format!("{tool}:*"))),
90        );
91    }
92    out.extend(
93        lockdown
94            .denied_tools
95            .iter()
96            .map(|tool| Policy::deny(PolicyAction::Tool, format!("{tool}:*"))),
97    );
98    out
99}