Skip to content

Commit 754c848

Browse files
committed
Phase A: Critical security fixes for PR #426
Security hardening for terraphim_rlm crate: 1. Created validation.rs module with: - validate_snapshot_name(): Prevents path traversal attacks - validate_code_input(): Enforces MAX_CODE_SIZE (1MB) limit - validate_session_id(): Validates UUID format - validate_recursion_depth(): Prevents stack overflow - Security constants: MAX_CODE_SIZE, MAX_INPUT_SIZE, MAX_RECURSION_DEPTH 2. Fixed race condition in firecracker.rs: - Changed snapshot counter from read-then-write to atomic write lock - Added validate_snapshot_name() call before snapshot creation - Prevents TOCTOU vulnerability where concurrent snapshots could exceed limit 3. Enhanced mcp_tools.rs: - Added MAX_CODE_SIZE validation for rlm_code tool - Added MAX_CODE_SIZE validation for rlm_bash tool - Returns proper MCP error format for validation failures Refs #426
1 parent 56cbd04 commit 754c848

File tree

4 files changed

+405
-4
lines changed

4 files changed

+405
-4
lines changed

crates/terraphim_rlm/src/executor/firecracker.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,13 @@ impl super::ExecutionEnvironment for FirecrackerExecutor {
447447
) -> Result<SnapshotId, Self::Error> {
448448
log::info!("Creating snapshot '{}' for session {}", name, session_id);
449449

450-
// Check snapshot limit for this session
451-
let count = *self.snapshot_counts.read().get(session_id).unwrap_or(&0);
450+
// Validate snapshot name for security (path traversal prevention)
451+
crate::validation::validate_snapshot_name(name)?;
452+
453+
// Check snapshot limit for this session - use write lock for atomic check-and-increment
454+
// to prevent race condition where multiple concurrent snapshots could exceed the limit
455+
let mut snapshot_counts = self.snapshot_counts.write();
456+
let count = *snapshot_counts.get(session_id).unwrap_or(&0);
452457
if count >= self.config.max_snapshots_per_session {
453458
return Err(RlmError::MaxSnapshotsReached {
454459
max: self.config.max_snapshots_per_session,
@@ -498,8 +503,10 @@ impl super::ExecutionEnvironment for FirecrackerExecutor {
498503
}
499504
};
500505

501-
// Update tracking
502-
*self.snapshot_counts.write().entry(*session_id).or_insert(0) += 1;
506+
// Update tracking - use the existing write lock for atomic increment
507+
*snapshot_counts.entry(*session_id).or_insert(0) += 1;
508+
// Release the write lock by dropping it explicitly before await boundary
509+
drop(snapshot_counts);
503510

504511
let result = SnapshotId::new(name, *session_id);
505512

crates/terraphim_rlm/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ pub mod logger;
7070
// Knowledge graph validation (Phase 5)
7171
#[cfg(feature = "kg-validation")]
7272
pub mod validator;
73+
pub mod validation;
7374

7475
// MCP tools (Phase 6)
7576
#[cfg(feature = "mcp")]

crates/terraphim_rlm/src/mcp_tools.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,14 @@ impl RlmMcpService {
328328
.and_then(|v| v.as_str())
329329
.ok_or_else(|| ErrorData::invalid_params("Missing 'code' parameter", None))?;
330330

331+
// Validate code size to prevent DoS via memory exhaustion
332+
if let Err(e) = crate::validation::validate_code_input(code) {
333+
return Err(ErrorData::invalid_params(
334+
format!("Code validation failed: {}", e),
335+
None,
336+
));
337+
}
338+
331339
let session_id = self.resolve_session_id(&args).await?;
332340
// timeout_ms is available for future use when execution context supports it
333341
let _timeout_ms = args.get("timeout_ms").and_then(|v| v.as_u64());
@@ -371,6 +379,14 @@ impl RlmMcpService {
371379
.and_then(|v| v.as_str())
372380
.ok_or_else(|| ErrorData::invalid_params("Missing 'command' parameter", None))?;
373381

382+
// Validate command size to prevent DoS via memory exhaustion
383+
if let Err(e) = crate::validation::validate_code_input(command) {
384+
return Err(ErrorData::invalid_params(
385+
format!("Command validation failed: {}", e),
386+
None,
387+
));
388+
}
389+
374390
let session_id = self.resolve_session_id(&args).await?;
375391
// These are available for future use when execution context supports them
376392
let _timeout_ms = args.get("timeout_ms").and_then(|v| v.as_u64());

0 commit comments

Comments
 (0)