#[cfg(unix)]
use std::os::unix::process::CommandExt;
use std::process::{Command, Stdio};
use crate::error::Error;
use crate::vault::Vault;
use super::context::prepare_execution;
pub fn validate_env_var_name(name: &str) -> Result<(), Error> {
use crate::guard::env::{INJECT_ENV_VARS, SUSPICIOUS_ENV_VARS};
if INJECT_ENV_VARS.contains(&name) || SUSPICIOUS_ENV_VARS.contains(&name) {
return Err(Error::BinaryResolution(format!(
"env var '{name}' is on the injection blocklist; using it as a target would \
let the secret value redirect the loader."
)));
}
let mut chars = name.chars();
let Some(first) = chars.next() else {
return Err(Error::BinaryResolution(
"invalid env var name: empty string".to_string(),
));
};
if !(first.is_ascii_alphabetic() || first == '_') {
return Err(Error::BinaryResolution(format!(
"invalid env var name '{name}': must start with an ASCII letter or underscore"
)));
}
for c in chars {
if !(c.is_ascii_alphanumeric() || c == '_') {
return Err(Error::BinaryResolution(format!(
"invalid env var name '{name}': only ASCII alphanumerics and underscore allowed"
)));
}
}
Ok(())
}
#[must_use]
pub fn command_fingerprint(command: &[String]) -> String {
if command.len() <= 1 {
return String::new();
}
command[1..]
.iter()
.map(|s| s.replace('\x1f', "\x1f\x1f"))
.collect::<Vec<_>>()
.join("\x1f")
}
pub struct InjectRequest<'a> {
pub secret_name: &'a str,
pub env_var: &'a str,
pub command: &'a [String],
}
#[derive(Debug, Clone, Copy, Default)]
pub struct InjectExecOptions {
pub isolate_stdio: bool,
}
pub fn execute(vault: &Vault, request: &InjectRequest) -> Result<(), Error> {
execute_with_options(vault, request, InjectExecOptions::default())
}
pub fn execute_with_options(
vault: &Vault,
request: &InjectRequest,
options: InjectExecOptions,
) -> Result<(), Error> {
execute_multi_with_options(
vault,
&[(request.secret_name, request.env_var)],
request.command,
options,
)
}
pub fn execute_multi(
vault: &Vault,
mappings: &[(&str, &str)],
command: &[String],
) -> Result<(), Error> {
execute_multi_with_options(vault, mappings, command, InjectExecOptions::default())
}
pub fn execute_multi_with_options(
vault: &Vault,
mappings: &[(&str, &str)],
command: &[String],
options: InjectExecOptions,
) -> Result<(), Error> {
let prepared = prepare_execution(vault, mappings, command)?;
let mut cmd = Command::new(&prepared.exec_path);
#[cfg(unix)]
cmd.arg0(&prepared.binary_path);
cmd.args(&prepared.args);
cmd.env_clear();
for (k, v) in &prepared.clean_env {
cmd.env(k, v);
}
for (var, val) in &prepared.env_pairs {
cmd.env(var, val.as_str());
}
if options.isolate_stdio {
cmd.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
}
let status = cmd.status().map_err(Error::ExecFailed)?;
if status.success() {
Ok(())
} else {
Err(Error::ExecFailed(std::io::Error::other(format!(
"child process exited with status {status}"
))))
}
}