a3s_runtime/contract/
process.rs1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3
4#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(deny_unknown_fields)]
6pub struct RuntimeProcessSpec {
7 pub command: Vec<String>,
9 pub args: Vec<String>,
10 pub working_directory: Option<String>,
11 pub environment: BTreeMap<String, String>,
12}
13
14impl RuntimeProcessSpec {
15 pub(crate) fn validate(&self) -> Result<(), String> {
16 if self.command.len() > 64 || self.args.len() > 256 {
17 return Err("process command or argument count exceeds Runtime limits".into());
18 }
19 for value in self.command.iter().chain(self.args.iter()) {
20 validate_process_value(value)?;
21 }
22 if let Some(path) = &self.working_directory {
23 super::validate_absolute_path("working_directory", path)?;
24 }
25 if self.environment.len() > 512 {
26 return Err("process environment exceeds 512 entries".into());
27 }
28 for (name, value) in &self.environment {
29 validate_environment_name(name)?;
30 if value.len() > 32 * 1024 || value.contains('\0') {
31 return Err(format!("environment value for {name:?} is invalid"));
32 }
33 }
34 Ok(())
35 }
36}
37
38fn validate_process_value(value: &str) -> Result<(), String> {
39 if value.is_empty() || value.len() > 32 * 1024 || value.contains('\0') {
40 return Err("process command values must be nonempty bounded non-NUL strings".into());
41 }
42 Ok(())
43}
44
45pub(crate) fn validate_environment_name(value: &str) -> Result<(), String> {
46 let mut bytes = value.bytes();
47 let Some(first) = bytes.next() else {
48 return Err("environment variable name must not be empty".into());
49 };
50 if !(first.is_ascii_alphabetic() || first == b'_')
51 || !bytes.all(|byte| byte.is_ascii_alphanumeric() || byte == b'_')
52 || value.len() > 255
53 {
54 return Err(format!("invalid environment variable name {value:?}"));
55 }
56 Ok(())
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
61pub enum SecretTarget {
62 Environment { variable: String },
63 File { path: String, mode: u32 },
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67#[serde(deny_unknown_fields)]
68pub struct SecretReference {
69 pub name: String,
70 pub reference: String,
73 pub target: SecretTarget,
74}
75
76impl SecretReference {
77 pub(crate) fn validate(&self) -> Result<(), String> {
78 super::validate_name("secret name", &self.name)?;
79 super::validate_nonempty("secret reference", &self.reference, 1024)?;
80 match &self.target {
81 SecretTarget::Environment { variable } => validate_environment_name(variable),
82 SecretTarget::File { path, mode } => {
83 super::validate_absolute_path("secret file path", path)?;
84 if *mode == 0 || *mode > 0o777 {
85 return Err("secret file mode must be between 0001 and 0777".into());
86 }
87 Ok(())
88 }
89 }
90 }
91}