use crate::types::Character;
use std::collections::HashMap;
const BLOCKED_ENV_VARS: &[&str] = &[
"SSH_AUTH_SOCK",
"SSH_AGENT_PID",
"GPG_AGENT_INFO",
"AWS_SECRET_ACCESS_KEY",
"AWS_SESSION_TOKEN",
"GOOGLE_APPLICATION_CREDENTIALS",
"AZURE_CLIENT_SECRET",
"GITHUB_TOKEN",
"GH_TOKEN",
"GITLAB_TOKEN",
"NPM_TOKEN",
"DOCKER_PASSWORD",
"REGISTRY_PASSWORD",
"HOME",
"USER",
"LOGNAME",
"MAIL",
"SHELL",
"HISTFILE",
"HISTSIZE",
"HISTCONTROL",
"PWD",
"OLDPWD",
"SHLVL",
"TERM",
"TERM_PROGRAM",
"COLORTERM",
"DISPLAY",
"WINDOWID",
"DBUS_SESSION_BUS_ADDRESS",
"XDG_SESSION_ID",
"XDG_RUNTIME_DIR",
"DATABASE_URL",
"REDIS_URL",
"MONGODB_URI",
"POSTGRES_PASSWORD",
"MYSQL_ROOT_PASSWORD",
"JWT_SECRET",
"SESSION_SECRET",
"ENCRYPTION_KEY",
"MASTER_KEY",
"PRIVATE_KEY",
"SECRET_KEY",
];
const BLOCKED_ENV_PREFIXES: &[&str] = &[
"AWS_", "AZURE_", "GCP_", "GOOGLE_", "GITHUB_", "GITLAB_", "SSH_", "GPG_",
"_", ];
fn is_blocked_env_var(key: &str) -> bool {
if BLOCKED_ENV_VARS
.iter()
.any(|&blocked| blocked.eq_ignore_ascii_case(key))
{
return true;
}
if BLOCKED_ENV_PREFIXES
.iter()
.any(|&prefix| key.to_uppercase().starts_with(prefix))
{
return true;
}
let key_upper = key.to_uppercase();
let sensitive_keywords = [
"PASSWORD",
"SECRET",
"TOKEN",
"KEY",
"CREDENTIAL",
"AUTH",
"PRIVATE",
];
if sensitive_keywords.iter().any(|&kw| key_upper.contains(kw)) {
return true;
}
false
}
pub fn has_character_secrets(character: &Character) -> bool {
if let Some(secrets_value) = character.settings.get("secrets") {
if let Some(secrets_obj) = secrets_value.as_object() {
return !secrets_obj.is_empty();
}
}
false
}
pub fn set_default_secrets_from_env(character: &mut Character) -> bool {
let env_vars: HashMap<String, String> = std::env::vars()
.filter(|(key, _)| !is_blocked_env_var(key))
.collect();
let filtered_count = std::env::vars().count() - env_vars.len();
if filtered_count > 0 {
tracing::debug!(
"Filtered {} sensitive environment variables from character settings",
filtered_count
);
}
let existing_secrets = character
.settings
.get("secrets")
.and_then(|v| v.as_object())
.cloned()
.unwrap_or_default();
for (key, value) in &env_vars {
if !character.settings.contains_key(key) {
character
.settings
.insert(key.clone(), serde_json::Value::String(value.clone()));
}
}
let mut secrets_map = serde_json::Map::new();
for (key, value) in &env_vars {
secrets_map.insert(key.clone(), serde_json::Value::String(value.clone()));
}
for (key, value) in existing_secrets {
secrets_map.insert(key, value);
}
character.settings.insert(
"secrets".to_string(),
serde_json::Value::Object(secrets_map),
);
true
}
pub fn load_secret_from_env(character: &mut Character, key: &str, env_var: &str) -> bool {
if let Ok(value) = std::env::var(env_var) {
set_secret(character, key, &value);
true
} else {
false
}
}
pub fn get_secret(character: &Character, key: &str) -> Option<String> {
if let Some(secrets_value) = character.settings.get("secrets") {
if let Some(secrets_obj) = secrets_value.as_object() {
if let Some(value) = secrets_obj.get(key) {
if let Some(s) = value.as_str() {
return Some(s.to_string());
}
}
}
}
if let Some(value) = character.settings.get(key) {
if let Some(s) = value.as_str() {
return Some(s.to_string());
}
}
None
}
pub fn set_secret(character: &mut Character, key: &str, value: &str) {
let mut secrets_obj = character
.settings
.get("secrets")
.and_then(|v| v.as_object())
.cloned()
.unwrap_or_default();
secrets_obj.insert(
key.to_string(),
serde_json::Value::String(value.to_string()),
);
character.settings.insert(
"secrets".to_string(),
serde_json::Value::Object(secrets_obj),
);
}
pub fn remove_secret(character: &mut Character, key: &str) -> bool {
if let Some(secrets_value) = character.settings.get_mut("secrets") {
if let Some(secrets_obj) = secrets_value.as_object_mut() {
return secrets_obj.remove(key).is_some();
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Metadata;
use serde_json::json;
fn create_test_character() -> Character {
Character {
id: None,
name: "Test".to_string(),
username: None,
bio: vec![],
lore: vec![],
knowledge: vec![],
message_examples: vec![],
post_examples: vec![],
topics: vec![],
style: Default::default(),
adjectives: vec![],
settings: Metadata::new(),
templates: None,
plugins: vec![],
clients: vec![],
model_provider: None,
}
}
#[test]
fn test_has_character_secrets() {
let mut character = create_test_character();
assert!(!has_character_secrets(&character));
character
.settings
.insert("secrets".to_string(), json!({"API_KEY": "test"}));
assert!(has_character_secrets(&character));
}
#[test]
fn test_get_set_secret() {
let mut character = create_test_character();
set_secret(&mut character, "TEST_KEY", "test_value");
assert_eq!(
get_secret(&character, "TEST_KEY"),
Some("test_value".to_string())
);
}
#[test]
fn test_remove_secret() {
let mut character = create_test_character();
set_secret(&mut character, "TEST_KEY", "test_value");
assert!(get_secret(&character, "TEST_KEY").is_some());
assert!(remove_secret(&mut character, "TEST_KEY"));
assert!(get_secret(&character, "TEST_KEY").is_none());
assert!(!remove_secret(&mut character, "NONEXISTENT"));
}
#[test]
fn test_set_default_secrets_from_env() {
let mut character = create_test_character();
std::env::set_var("TEST_ENV_VAR", "test_value");
assert!(set_default_secrets_from_env(&mut character));
assert!(has_character_secrets(&character));
std::env::remove_var("TEST_ENV_VAR");
}
#[test]
fn test_env_var_filtering() {
assert!(is_blocked_env_var("AWS_SECRET_ACCESS_KEY"));
assert!(is_blocked_env_var("SSH_AUTH_SOCK"));
assert!(is_blocked_env_var("HOME"));
assert!(is_blocked_env_var("DATABASE_URL"));
assert!(is_blocked_env_var("AWS_REGION"));
assert!(is_blocked_env_var("AZURE_SUBSCRIPTION_ID"));
assert!(is_blocked_env_var("GITHUB_SHA"));
assert!(is_blocked_env_var("MY_PASSWORD"));
assert!(is_blocked_env_var("API_SECRET"));
assert!(is_blocked_env_var("ACCESS_TOKEN"));
assert!(is_blocked_env_var("ENCRYPTION_KEY"));
assert!(!is_blocked_env_var("RUST_LOG"));
assert!(!is_blocked_env_var("CARGO_HOME"));
assert!(!is_blocked_env_var("PATH")); assert!(!is_blocked_env_var("LANG"));
}
#[test]
fn test_load_secret_from_env() {
let mut character = create_test_character();
std::env::set_var("MY_SPECIFIC_SECRET", "specific_value");
assert!(load_secret_from_env(
&mut character,
"api_key",
"MY_SPECIFIC_SECRET"
));
assert_eq!(
get_secret(&character, "api_key"),
Some("specific_value".to_string())
);
assert!(!load_secret_from_env(
&mut character,
"missing",
"NONEXISTENT_VAR"
));
std::env::remove_var("MY_SPECIFIC_SECRET");
}
}