Skip to main content

rskit_tool/
envelope.rs

1//! Executable permission envelope for tools.
2
3use serde::{Deserialize, Serialize};
4
5/// Executable permission envelope declared by a tool.
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7pub struct Envelope {
8    /// Authz scopes consumed by this tool. Empty means no named scope is required.
9    #[serde(default, skip_serializing_if = "Vec::is_empty")]
10    pub scopes: Vec<String>,
11    /// Network egress policy. Defaults to deny all egress.
12    #[serde(default)]
13    pub network: NetworkPolicy,
14    /// Filesystem access rules. Empty denies filesystem access.
15    #[serde(default, skip_serializing_if = "Vec::is_empty")]
16    pub filesystem: Vec<FilesystemRule>,
17    /// Subprocess invocation rules. Empty denies subprocess execution.
18    #[serde(default, skip_serializing_if = "Vec::is_empty")]
19    pub subprocess: Vec<SubprocessRule>,
20    /// Safety classification for executable behavior.
21    #[serde(default)]
22    pub safety: Safety,
23    /// Input predicates that require human approval before invocation.
24    #[serde(default, skip_serializing_if = "Vec::is_empty")]
25    pub sensitive_invocations: Vec<SensitivePredicate>,
26    /// Informational data classification used for redaction/observability.
27    #[serde(default)]
28    pub data_classification: DataClassification,
29}
30
31impl Default for Envelope {
32    fn default() -> Self {
33        Self {
34            scopes: Vec::new(),
35            network: NetworkPolicy::None,
36            filesystem: Vec::new(),
37            subprocess: Vec::new(),
38            safety: Safety::ReadOnly,
39            sensitive_invocations: Vec::new(),
40            data_classification: DataClassification::Public,
41        }
42    }
43}
44
45/// Network egress policy for a tool.
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
47#[serde(tag = "mode", rename_all = "snake_case")]
48pub enum NetworkPolicy {
49    /// Deny all network egress.
50    #[default]
51    None,
52    /// Allow only these host rules.
53    AllowList {
54        /// Host allow-list. Hosts match exact names, exact IP literals, or exact suffix after leading dot.
55        rules: Vec<NetworkRule>,
56    },
57}
58
59/// Single network allow-list rule.
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61pub struct NetworkRule {
62    /// Exact host, IP literal, or suffix beginning with `.`.
63    pub host: String,
64    /// Optional TCP port.
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub port: Option<u16>,
67    /// Optional scheme. Defaults to `https` when absent.
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    pub scheme: Option<String>,
70}
71
72/// Filesystem access rule.
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74pub struct FilesystemRule {
75    /// Normalized root path or explicit glob.
76    pub path: String,
77    /// Access mode allowed under `path`.
78    pub mode: FilesystemMode,
79}
80
81/// Filesystem access mode.
82#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
83#[serde(rename_all = "snake_case")]
84pub enum FilesystemMode {
85    /// Read files.
86    Read,
87    /// Write files.
88    Write,
89    /// Delete files.
90    Delete,
91}
92
93/// Subprocess execution rule.
94#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
95pub struct SubprocessRule {
96    /// Argv pattern. First element must match exactly; later elements may be literals or `{}` placeholders.
97    pub argv_pattern: Vec<String>,
98    /// Environment variable names allowed to pass through. Empty by default.
99    #[serde(default, skip_serializing_if = "Vec::is_empty")]
100    pub env_allow: Vec<String>,
101    /// Optional normalized working directory.
102    #[serde(default, skip_serializing_if = "Option::is_none")]
103    pub cwd: Option<String>,
104}
105
106/// Total safety order for executable behavior.
107#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
108#[serde(rename_all = "kebab-case")]
109pub enum Safety {
110    /// Read-only behavior.
111    #[default]
112    ReadOnly,
113    /// Mutating behavior.
114    Mutating,
115    /// Destructive behavior.
116    Destructive,
117}
118
119/// Informational data classification.
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
121#[serde(rename_all = "snake_case")]
122pub enum DataClassification {
123    /// Public data.
124    #[default]
125    Public,
126    /// Personally identifiable information.
127    Pii,
128    /// Secret or credential-bearing data.
129    Secret,
130}
131
132/// Predicate over validated input that marks an invocation as sensitive.
133#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
134pub struct SensitivePredicate {
135    /// JSONPath-like selector over the tool input.
136    pub jsonpath: String,
137    /// Matcher applied to the selected value.
138    pub matcher: SensitiveMatcher,
139}
140
141/// Matcher for a sensitive invocation predicate.
142#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
143#[serde(tag = "type", content = "value", rename_all = "snake_case")]
144pub enum SensitiveMatcher {
145    /// The selected value exists.
146    Exists,
147    /// The selected value equals this JSON value.
148    Equals(serde_json::Value),
149    /// The selected string matches this regular expression.
150    Regex(String),
151    /// The selected numeric value is greater than this threshold.
152    Gt(f64),
153    /// The selected numeric value is less than this threshold.
154    Lt(f64),
155}