1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//! export builtin - mark variables for export
use async_trait::async_trait;
use super::{Builtin, Context};
use crate::error::Result;
use crate::interpreter::{ExecResult, is_internal_variable, is_valid_var_name};
/// export builtin - mark variables for export to child processes
///
/// In Bashkit's virtual environment, this primarily just sets variables.
/// The distinction between exported and non-exported isn't significant
/// since we don't spawn real child processes.
pub struct Export;
#[async_trait]
impl Builtin for Export {
async fn execute(&self, ctx: Context<'_>) -> Result<ExecResult> {
// Handle `export -p` — print all exported variables
if ctx.args.first().map(|s| s.as_str()) == Some("-p") {
let mut output = String::new();
let mut pairs: Vec<_> = ctx.env.iter().collect();
pairs.sort_by_key(|(k, _)| (*k).clone());
for (name, value) in pairs {
output.push_str(&format!("declare -x {}=\"{}\"\n", name, value));
}
return Ok(ExecResult::ok(output));
}
for arg in ctx.args {
// Handle NAME=VALUE format
if let Some(eq_pos) = arg.find('=') {
let name = &arg[..eq_pos];
let value = &arg[eq_pos + 1..];
// Validate variable name
if !is_valid_var_name(name) {
return Ok(ExecResult::err(
format!("export: `{}': not a valid identifier\n", arg),
1,
));
}
// THREAT[TM-INJ-015]: Block internal variable prefix injection via export
if is_internal_variable(name) {
continue;
}
// THREAT[TM-INJ-021]: Refuse to overwrite readonly variables
if ctx.variables.contains_key(&format!("_READONLY_{}", name)) {
continue;
}
ctx.variables.insert(name.to_string(), value.to_string());
} else {
// Just marking for export - in our model this is a no-op
// unless the variable exists, in which case we keep it
}
}
Ok(ExecResult::ok(String::new()))
}
}