Skip to content

Commit 4da4c75

Browse files
author
AI Assistant
committed
fix(security): resolve issue #1 panel report findings
Adds path traversal prevention on target_rules_file, restricts api_key_env exfiltration, truncates LLM logs to prevent OOM, and removes dead code.
1 parent d89f8be commit 4da4c75

4 files changed

Lines changed: 22 additions & 41 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/llm_client.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ pub async fn ask_llm(
3737
// 2. Generic OpenAI-compatible endpoint
3838
let url = config.base_url.clone().unwrap_or_else(|| "https://api.openai.com/v1/chat/completions".to_string());
3939
let env_var = config.api_key_env.as_deref().unwrap_or("OPENAI_API_KEY");
40+
41+
// P0 Security Fix: Prevent exfiltration of arbitrary host env vars (like AWS_SECRET_ACCESS_KEY or SSH_PRIVATE_KEY) via malicious plasticity.json
42+
if !env_var.ends_with("_API_KEY") && !env_var.ends_with("_TOKEN") && env_var != "API_KEY" {
43+
anyhow::bail!("Security Exception: To prevent credential exfiltration, `api_key_env` must end in '_API_KEY' or '_TOKEN'. Attempted to use: {}", env_var);
44+
}
45+
4046
let api_key = std::env::var(env_var).unwrap_or_default();
4147

4248
if api_key.is_empty() {

src/main.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ async fn run_single_manifest(manifest_path: &Path) -> Result<(bool, u32, manifes
3030

3131
let target_rules_file = PathBuf::from(&manifest.optimization.target_rules_file);
3232

33+
// Prevent Path Traversal (P0 Fix)
34+
if target_rules_file.is_absolute() || target_rules_file.components().any(|c| c.as_os_str() == "..") {
35+
anyhow::bail!("Security Exception: target_rules_file must be a safe, relative path inside the project directory.");
36+
}
37+
3338
for epoch in 1..=max_epochs {
3439
println!("\n--- Epoch {} / {} ---", epoch, max_epochs);
3540

src/optimizer.rs

Lines changed: 10 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -31,46 +31,16 @@ DO NOT suggest rules that are already in the Existing Rules array. The agent alr
3131

3232
let rules_json = serde_json::to_string_pretty(existing_rules).unwrap_or_else(|_| "[]".to_string());
3333

34-
let user_prompt = format!("Task: {}\n\nExisting Rules Already Attempted (Do not repeat these):\n{}\n\nFailing Logs:\n{}", task_prompt, rules_json, failing_logs);
34+
// Truncate logs to prevent LLM context overflow or API rejection (P1 Fix)
35+
let max_log_len = 8000;
36+
let truncated_logs = if failing_logs.len() > max_log_len {
37+
let skip = failing_logs.len() - max_log_len;
38+
format!("...[TRUNCATED]...\n{}", &failing_logs[skip..])
39+
} else {
40+
failing_logs.to_string()
41+
};
42+
43+
let user_prompt = format!("Task: {}\n\nExisting Rules Already Attempted (Do not repeat these):\n{}\n\nFailing Logs:\n{}", task_prompt, rules_json, truncated_logs);
3544

3645
ask_llm(config, system_prompt, &user_prompt).await
3746
}
38-
39-
/// Runs the meta-optimizer and writes a rules overlay if the score is below the pass threshold.
40-
pub async fn run_optimizer(score: f64, pass_threshold: f64, task_prompt: &str, stderr: &str, config: &MetaLlmConfig) -> Result<()> {
41-
if score >= pass_threshold {
42-
println!("Score {} >= pass threshold {}. No optimization needed.", score, pass_threshold);
43-
return Ok(());
44-
}
45-
46-
println!("Score {} < pass threshold {}. Generating rules overlay...", score, pass_threshold);
47-
48-
let existing_rules: Vec<String> = vec![];
49-
let new_rule = run_llm_optimizer(config, stderr, task_prompt, &existing_rules).await?;
50-
51-
let mocked_rules = vec![
52-
new_rule,
53-
];
54-
55-
let rule_set = RuleSet {
56-
rules: mocked_rules,
57-
metadata: Metadata {
58-
generation_reason: format!("Generated due to failing task: '{}', Error output: '{}'", task_prompt, stderr),
59-
original_score: score,
60-
},
61-
};
62-
63-
let target_dir = Path::new(".neuroplasticity");
64-
if !target_dir.exists() {
65-
fs::create_dir_all(target_dir).context("Failed to create .neuroplasticity directory")?;
66-
}
67-
68-
let rules_path = target_dir.join("rules.json");
69-
let rules_json = serde_json::to_string_pretty(&rule_set).context("Failed to serialize rule set")?;
70-
71-
fs::write(&rules_path, rules_json).context("Failed to write rules.json")?;
72-
73-
println!("Rules overlay successfully written to {:?}", rules_path);
74-
75-
Ok(())
76-
}

0 commit comments

Comments
 (0)