release: secrets-mcp 0.5.2
Bump version: secrets-mcp-0.5.1 tag already existed while crates had further changes. Made-with: Cursor
This commit is contained in:
@@ -18,6 +18,8 @@ use serde_json::{Map, Value};
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::validation;
|
||||
|
||||
// ── Serde helpers for numeric parameters that may arrive as strings ──────────
|
||||
|
||||
mod deser {
|
||||
@@ -593,6 +595,44 @@ fn map_to_kv_strings(map: Map<String, Value>) -> Vec<String> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Check if any KV string would trigger a server-side file read.
|
||||
///
|
||||
/// `parse_kv` in secrets-core supports two file-read syntaxes:
|
||||
/// - `key=@path` (has `=`, value starts with `@`)
|
||||
/// - `key@path` (no `=`, split on `@`)
|
||||
///
|
||||
/// Both are legitimate for CLI usage but must be rejected in the MCP context
|
||||
/// where the server process runs remotely and the caller controls the path.
|
||||
///
|
||||
/// Note: `key:=json` is intentionally skipped here. Although the value may
|
||||
/// contain `@` characters (e.g. `config:=@/etc/passwd`), the `:=` branch in
|
||||
/// `parse_kv` treats the right-hand side as raw JSON and never performs file
|
||||
/// reads. The `@` in such cases is just data, not a file reference.
|
||||
fn contains_file_reference(entries: &[String]) -> Option<String> {
|
||||
for entry in entries {
|
||||
// key:=json — safe, skip before checking for `=`
|
||||
if entry.contains(":=") {
|
||||
continue;
|
||||
}
|
||||
// key=@path
|
||||
if let Some((_, value)) = entry.split_once('=') {
|
||||
if value.starts_with('@') {
|
||||
return Some(entry.clone());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// key@path (no `=` present)
|
||||
// parse_kv treats entries without `=` that contain `@` as file-read
|
||||
// syntax (key@path). This includes strings like "user@example.com"
|
||||
// if passed without a `=` separator — which is correct to reject here
|
||||
// since the MCP server runs remotely and cannot read local files.
|
||||
if entry.contains('@') {
|
||||
return Some(entry.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Parse a UUID string, returning an MCP error on failure.
|
||||
fn parse_uuid(s: &str) -> Result<Uuid, rmcp::ErrorData> {
|
||||
s.parse::<Uuid>()
|
||||
@@ -879,10 +919,33 @@ impl SecretsService {
|
||||
if let Some(obj) = input.meta_obj {
|
||||
meta.extend(map_to_kv_strings(obj));
|
||||
}
|
||||
if let Some(offending) = contains_file_reference(&meta) {
|
||||
return Err(rmcp::ErrorData::invalid_params(
|
||||
format!("@file syntax is not allowed in MCP tools: '{}'", offending),
|
||||
None,
|
||||
));
|
||||
}
|
||||
let mut secrets = input.secrets.unwrap_or_default();
|
||||
if let Some(obj) = input.secrets_obj {
|
||||
secrets.extend(map_to_kv_strings(obj));
|
||||
}
|
||||
if let Some(offending) = contains_file_reference(&secrets) {
|
||||
return Err(rmcp::ErrorData::invalid_params(
|
||||
format!("@file syntax is not allowed in MCP tools: '{}'", offending),
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
// Input length validation
|
||||
validation::validate_input_lengths(
|
||||
&input.name,
|
||||
input.folder.as_deref(),
|
||||
input.entry_type.as_deref(),
|
||||
input.notes.as_deref(),
|
||||
)?;
|
||||
validation::validate_tags(&tags)?;
|
||||
validation::validate_meta_entries(&meta)?;
|
||||
|
||||
let secret_types = input.secret_types.unwrap_or_default();
|
||||
let secret_types_map: std::collections::HashMap<String, String> = secret_types
|
||||
.into_iter()
|
||||
@@ -962,11 +1025,34 @@ impl SecretsService {
|
||||
if let Some(obj) = input.meta_obj {
|
||||
meta.extend(map_to_kv_strings(obj));
|
||||
}
|
||||
if let Some(offending) = contains_file_reference(&meta) {
|
||||
return Err(rmcp::ErrorData::invalid_params(
|
||||
format!("@file syntax is not allowed in MCP tools: '{}'", offending),
|
||||
None,
|
||||
));
|
||||
}
|
||||
let remove_meta = input.remove_meta.unwrap_or_default();
|
||||
let mut secrets = input.secrets.unwrap_or_default();
|
||||
if let Some(obj) = input.secrets_obj {
|
||||
secrets.extend(map_to_kv_strings(obj));
|
||||
}
|
||||
if let Some(offending) = contains_file_reference(&secrets) {
|
||||
return Err(rmcp::ErrorData::invalid_params(
|
||||
format!("@file syntax is not allowed in MCP tools: '{}'", offending),
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
// Input length validation
|
||||
validation::validate_input_lengths(
|
||||
&input.name,
|
||||
input.folder.as_deref(),
|
||||
None,
|
||||
input.notes.as_deref(),
|
||||
)?;
|
||||
validation::validate_tags(&add_tags)?;
|
||||
validation::validate_meta_entries(&meta)?;
|
||||
|
||||
let secret_types = input.secret_types.unwrap_or_default();
|
||||
let secret_types_map: std::collections::HashMap<String, String> = secret_types
|
||||
.into_iter()
|
||||
|
||||
Reference in New Issue
Block a user