Skip to main content

safe_chains/registry/
types.rs

1use serde::Deserialize;
2
3use crate::policy::FlagStyle;
4use crate::verdict::SafetyLevel;
5
6#[derive(Debug, Deserialize)]
7pub(super) struct TomlFile {
8    pub command: Vec<TomlCommand>,
9}
10
11#[derive(Debug, Deserialize)]
12pub(super) struct TomlCommand {
13    pub name: String,
14    #[serde(default)]
15    pub description: Option<String>,
16    #[serde(default)]
17    pub candidate: Option<bool>,
18    #[serde(default)]
19    pub aliases: Vec<String>,
20    #[serde(default)]
21    pub url: String,
22    #[serde(default)]
23    pub level: Option<TomlLevel>,
24    #[serde(default)]
25    pub bare: Option<bool>,
26    #[serde(default)]
27    pub max_positional: Option<usize>,
28    #[serde(default)]
29    pub positional_style: Option<bool>,
30    #[serde(default)]
31    pub numeric_dash: Option<bool>,
32    #[serde(default)]
33    pub standalone: Vec<String>,
34    #[serde(default)]
35    pub valued: Vec<String>,
36    #[serde(default)]
37    pub bare_flags: Vec<String>,
38    #[serde(default)]
39    pub sub: Vec<TomlSub>,
40    #[serde(default)]
41    pub handler: Option<String>,
42    #[serde(default)]
43    pub doc_body: Option<String>,
44    #[serde(default)]
45    pub require_any: Vec<String>,
46    #[serde(default)]
47    pub first_arg: Vec<String>,
48    #[serde(default)]
49    pub wrapper: Option<TomlWrapper>,
50    #[serde(default)]
51    pub write_flags: Vec<String>,
52    #[serde(default)]
53    pub researched_version: Option<String>,
54    /// Sample invocations that double as test fixtures.
55    /// `examples_safe` must validate as Allowed; `examples_denied` must validate as Denied.
56    /// Use these to exercise aliases and canonical forms (e.g. `mise use` and `mise u`)
57    /// so drift between the TOML and runtime dispatch fails the test suite.
58    #[serde(default)]
59    pub examples_safe: Vec<String>,
60    #[serde(default)]
61    pub examples_denied: Vec<String>,
62}
63
64#[derive(Debug, Deserialize)]
65pub(super) struct TomlWrapper {
66    #[serde(default)]
67    pub standalone: Vec<String>,
68    #[serde(default)]
69    pub valued: Vec<String>,
70    #[serde(default)]
71    pub positional_skip: Option<usize>,
72    #[serde(default)]
73    pub separator: Option<String>,
74    #[serde(default)]
75    pub bare_ok: Option<bool>,
76}
77
78#[derive(Debug, Deserialize)]
79pub(super) struct TomlSub {
80    pub name: String,
81    #[serde(default)]
82    pub candidate: Option<bool>,
83    #[serde(default)]
84    pub aliases: Vec<String>,
85    #[serde(default)]
86    pub level: Option<TomlLevel>,
87    #[serde(default)]
88    pub bare: Option<bool>,
89    #[serde(default)]
90    pub max_positional: Option<usize>,
91    #[serde(default)]
92    pub positional_style: Option<bool>,
93    #[serde(default)]
94    pub numeric_dash: Option<bool>,
95    #[serde(default)]
96    pub standalone: Vec<String>,
97    #[serde(default)]
98    pub valued: Vec<String>,
99    #[serde(default)]
100    pub guard: Option<String>,
101    #[serde(default)]
102    pub guard_short: Option<String>,
103    #[serde(default)]
104    pub allow_all: Option<bool>,
105    #[serde(default)]
106    pub sub: Vec<TomlSub>,
107    #[serde(default)]
108    pub nested_bare: Option<bool>,
109    #[serde(default)]
110    pub require_any: Vec<String>,
111    #[serde(default)]
112    pub first_arg: Vec<String>,
113    #[serde(default)]
114    pub write_flags: Vec<String>,
115    #[serde(default)]
116    pub delegate_after: Option<String>,
117    #[serde(default)]
118    pub delegate_skip: Option<usize>,
119    #[serde(default)]
120    pub handler: Option<String>,
121    #[serde(default)]
122    pub doc_body: Option<String>,
123}
124
125#[derive(Debug, Clone, Copy, Deserialize)]
126pub(super) enum TomlLevel {
127    Inert,
128    SafeRead,
129    SafeWrite,
130}
131
132impl From<TomlLevel> for SafetyLevel {
133    fn from(l: TomlLevel) -> Self {
134        match l {
135            TomlLevel::Inert => SafetyLevel::Inert,
136            TomlLevel::SafeRead => SafetyLevel::SafeRead,
137            TomlLevel::SafeWrite => SafetyLevel::SafeWrite,
138        }
139    }
140}
141
142#[derive(Debug)]
143pub struct CommandSpec {
144    pub name: String,
145    pub description: String,
146    pub aliases: Vec<String>,
147    pub url: String,
148    pub category: String,
149    /// Upstream version of the underlying tool that was researched
150    /// when this spec was last updated. Free-form string — e.g.
151    /// `"1.9.0"`, `"v5.10.3"`, `"2026-05-08 master"`,
152    /// `"@northflank/cli 0.10.15"`. Internal-only: not rendered in
153    /// docs or used at runtime. Surfaces in tests and as a tripwire
154    /// when researching newer versions of the same tool.
155    pub researched_version: Option<String>,
156    /// Sample invocations that the registry test runs through `is_safe_command`.
157    /// Each `examples_safe` entry must produce `Verdict::Allowed`.
158    pub examples_safe: Vec<String>,
159    /// Sample invocations that must be denied. Use these to lock in security
160    /// boundaries (e.g. `srb tc --metrics-file=/etc/passwd` should always
161    /// be denied; recording it here catches regressions).
162    pub examples_denied: Vec<String>,
163    pub(super) kind: DispatchKind,
164}
165
166#[derive(Debug, Clone)]
167pub(super) struct SubSpec {
168    pub name: String,
169    pub kind: DispatchKind,
170}
171
172#[derive(Debug, Clone)]
173pub(super) enum DispatchKind {
174    Policy {
175        policy: OwnedPolicy,
176        level: SafetyLevel,
177    },
178    FirstArg {
179        patterns: Vec<String>,
180        level: SafetyLevel,
181    },
182    RequireAny {
183        require_any: Vec<String>,
184        policy: OwnedPolicy,
185        level: SafetyLevel,
186        accept_bare_help: bool,
187    },
188    Branching {
189        subs: Vec<SubSpec>,
190        bare_flags: Vec<String>,
191        bare_ok: bool,
192        pre_standalone: Vec<String>,
193        pre_valued: Vec<String>,
194        first_arg: Vec<String>,
195        first_arg_level: SafetyLevel,
196    },
197    WriteFlagged {
198        policy: OwnedPolicy,
199        base_level: SafetyLevel,
200        write_flags: Vec<String>,
201    },
202    DelegateAfterSeparator {
203        separator: String,
204    },
205    DelegateSkip {
206        skip: usize,
207    },
208    Wrapper {
209        standalone: Vec<String>,
210        valued: Vec<String>,
211        positional_skip: usize,
212        separator: Option<String>,
213        bare_ok: bool,
214    },
215    Custom {
216        #[allow(dead_code)]
217        handler_name: String,
218        doc_body: Option<String>,
219    },
220}
221
222#[derive(Debug, Clone)]
223pub struct OwnedPolicy {
224    pub standalone: Vec<String>,
225    pub valued: Vec<String>,
226    pub bare: bool,
227    pub max_positional: Option<usize>,
228    pub flag_style: FlagStyle,
229    pub numeric_dash: bool,
230}