Skip to content

Commit 29b2d28

Browse files
feat(Mountain/RPC): Integrate OpenTelemetry and strict validation across Vine gRPC services
This commit enhances the robustness and observability of the `Mountain` backend's RPC layer, which implements the `Vine` protocol for communication with `Cocoon` and `Wind`. It introduces a standardized instrumentation and validation framework across `CommandService`, `SecretStorageService`, `WindowService`, and `WorkspaceService`. **Key Changes:** * **Observability (Telemetry):** Integrated OpenTelemetry spans and metrics (via `opentelemetry`) into all major RPC services. A new `Telemetry` module provides centralized `ServiceMetrics` and span creation utilities. This allows tracking of operation latency (e.g., `command_execution_latency_us`), error rates, and throughput (e.g., `secrets_stored`), gated by the `Telemetry` feature flag. * **Input Validation:** Added a dedicated `Command/Validation` module and inline validation logic to services. This includes sanitization for command IDs (format checks), secret keys (alphanumeric checks), and file paths (injection prevention). It enforces strict length limits and character filtering to prevent malformed requests from the `Cocoon` extension host. * **Refactoring & Modernization:** Unified service constructors to a `Create` pattern and refactored public methods to adhere to PascalCase naming (e.g., `register_command_impl` -> `RegisterCommand`). Enhanced error handling to convert internal errors into `tonic::Status` with context preservation. * **Defensive Coding:** Implemented timeout handling for long-running commands (30s default), file size limits (50MB max), and atomic state updates using `parking_lot` locks to ensure thread safety within the `MountainEnvironment`. These changes ensure that the `Mountain` backend is production-ready with deep insights into system performance and hardened against malformed data originating from extensions.
1 parent cb490c0 commit 29b2d28

8 files changed

Lines changed: 1537 additions & 1265 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//! # CommandValidation - Input Validation for Commands
2+
//!
3+
//! Provides comprehensive validation for command registration and
4+
//! execution requests with security checks and sanitization.
5+
//!
6+
//! ## Validation Rules
7+
//!
8+
//! - Command ID format (extension.command)
9+
//! - Title length and content
10+
//! - Extension ID validation
11+
//! - Category normalization
12+
//!
13+
//! ## Security
14+
//!
15+
//! - Input sanitization
16+
//! - Length limits
17+
//! - Character filtering
18+
19+
use log::{debug, warn};
20+
21+
pub struct CommandValidationError {
22+
pub field: String,
23+
pub message: String,
24+
}
25+
26+
/// Validate command ID format
27+
pub fn ValidateCommandId(id: &str) -> Result<(), CommandValidationError> {
28+
if id.is_empty() {
29+
return Err(CommandValidationError {
30+
field: "id".to_string(),
31+
message: "Command ID cannot be empty".to_string(),
32+
});
33+
}
34+
35+
if !id.contains('.') {
36+
return Err(CommandValidationError {
37+
field: "id".to_string(),
38+
message: "Command ID must contain dot separator (e.g., 'extension.command')".to_string(),
39+
});
40+
}
41+
42+
if id.len() > 200 {
43+
return Err(CommandValidationError {
44+
field: "id".to_string(),
45+
message: "Command ID too long (max 200 characters)".to_string(),
46+
});
47+
}
48+
49+
Ok(())
50+
}
51+
52+
/// Validate command title
53+
pub fn ValidateCommandTitle(title: &str) -> Result<(), CommandValidationError> {
54+
if title.trim().is_empty() {
55+
return Err(CommandValidationError {
56+
field: "title".to_string(),
57+
message: "Command title cannot be empty".to_string(),
58+
});
59+
}
60+
61+
if title.len() > 200 {
62+
return Err(CommandValidationError {
63+
field: "title".to_string(),
64+
message: "Command title too long (max 200 characters)".to_string(),
65+
});
66+
}
67+
68+
Ok(())
69+
}
70+
71+
/// Validate extension ID
72+
pub fn ValidateExtensionId(id: &str) -> Result<(), CommandValidationError> {
73+
if id.is_empty() {
74+
return Err(CommandValidationError {
75+
field: "extension_id".to_string(),
76+
message: "Extension ID cannot be empty".to_string(),
77+
});
78+
}
79+
80+
Ok(())
81+
}
82+
83+
/// Validate category (optional)
84+
pub fn ValidateCategory(category: &Option<String>) -> Result<(), CommandValidationError> {
85+
if let Some(cat) = category {
86+
if cat.len() > 50 {
87+
return Err(CommandValidationError {
88+
field: "category".to_string(),
89+
message: "Category too long (max 50 characters)".to_string(),
90+
});
91+
}
92+
}
93+
Ok(())
94+
}
95+
96+
/// Complete validation for command registration request
97+
pub fn ValidateRegistrationRequest(
98+
id: &str,
99+
title: &str,
100+
extension_id: &str,
101+
category: &Option<String>,
102+
_when: &Option<String>,
103+
) -> Result<(), Vec<CommandValidationError>> {
104+
let mut errors = Vec::new();
105+
106+
if let Err(e) = ValidateCommandId(id) {
107+
errors.push(e);
108+
}
109+
if let Err(e) = ValidateCommandTitle(title) {
110+
errors.push(e);
111+
}
112+
if let Err(e) = ValidateExtensionId(extension_id) {
113+
errors.push(e);
114+
}
115+
if let Err(e) = ValidateCategory(category) {
116+
errors.push(e);
117+
}
118+
119+
if errors.is_empty() {
120+
Ok(())
121+
} else {
122+
Err(errors)
123+
}
124+
}
125+

0 commit comments

Comments
 (0)