feat(core): FK for user_id columns; MCP search requires user
- Add fk_entries_user_id, fk_entries_history_user_id, fk_audit_log_user_id (ON DELETE SET NULL) - Add scripts/cleanup-orphan-user-ids.sql for pre-deploy orphan user_id cleanup - Remove deprecated SERVER_MASTER_KEY / per-user key wrap helpers from secrets-core - secrets-mcp: require authenticated user for secrets_search; improve body-read failure response - Bump secrets-mcp to 0.2.1 Made-with: Cursor
This commit is contained in:
@@ -5,11 +5,11 @@ use axum::{
|
||||
body::{Body, Bytes, to_bytes},
|
||||
extract::{ConnectInfo, Request},
|
||||
http::{
|
||||
HeaderMap, Method,
|
||||
HeaderMap, Method, StatusCode,
|
||||
header::{CONTENT_LENGTH, CONTENT_TYPE, USER_AGENT},
|
||||
},
|
||||
middleware::Next,
|
||||
response::Response,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
|
||||
/// Axum middleware that logs structured info for every HTTP request.
|
||||
@@ -68,10 +68,23 @@ pub async fn request_logging_middleware(req: Request, next: Next) -> Response {
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(path, error = %e, "failed to buffer MCP request body for logging");
|
||||
// Reconstruct with empty body; request was consumed — return 500.
|
||||
// This branch is highly unlikely in practice.
|
||||
let resp = next.run(Request::from_parts(parts, Body::empty())).await;
|
||||
return resp;
|
||||
let elapsed = start.elapsed().as_millis();
|
||||
tracing::info!(
|
||||
method = method.as_str(),
|
||||
path,
|
||||
status = StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
|
||||
elapsed_ms = elapsed,
|
||||
client_ip = ip.as_deref(),
|
||||
ua = ua.as_deref(),
|
||||
content_length = content_len,
|
||||
mcp_session = mcp_session.as_deref(),
|
||||
"mcp request",
|
||||
);
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"failed to read request body",
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,8 +298,8 @@ struct EnvMapInput {
|
||||
#[tool_router]
|
||||
impl SecretsService {
|
||||
#[tool(
|
||||
description = "Search entries in the secrets store. Returns entries with metadata and \
|
||||
secret field names (not values). Use secrets_get to decrypt secret values.",
|
||||
description = "Search entries in the secrets store. Requires Bearer API key. Returns \
|
||||
entries with metadata and secret field names (not values). Use secrets_get to decrypt secret values.",
|
||||
annotations(
|
||||
title = "Search Secrets",
|
||||
read_only_hint = true,
|
||||
@@ -312,7 +312,7 @@ impl SecretsService {
|
||||
ctx: RequestContext<RoleServer>,
|
||||
) -> Result<CallToolResult, rmcp::ErrorData> {
|
||||
let t = Instant::now();
|
||||
let user_id = Self::user_id_from_ctx(&ctx)?;
|
||||
let user_id = Self::require_user_id(&ctx)?;
|
||||
tracing::info!(
|
||||
tool = "secrets_search",
|
||||
?user_id,
|
||||
@@ -334,11 +334,11 @@ impl SecretsService {
|
||||
sort: input.sort.as_deref().unwrap_or("name"),
|
||||
limit: input.limit.unwrap_or(20),
|
||||
offset: input.offset.unwrap_or(0),
|
||||
user_id,
|
||||
user_id: Some(user_id),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(|e| mcp_err_internal_logged("secrets_search", user_id, e))?;
|
||||
.map_err(|e| mcp_err_internal_logged("secrets_search", Some(user_id), e))?;
|
||||
|
||||
let summary = input.summary.unwrap_or(false);
|
||||
let entries: Vec<serde_json::Value> = result
|
||||
@@ -849,7 +849,7 @@ impl ServerHandler for SecretsService {
|
||||
"Manage cross-device secrets and configuration securely. \
|
||||
Data is encrypted with your passphrase-derived key. \
|
||||
Include your 64-char hex key in the X-Encryption-Key header for all read/write operations. \
|
||||
Use secrets_search to discover entries (no key needed), \
|
||||
Use secrets_search to discover entries (Bearer token required; encryption key not needed), \
|
||||
secrets_get to decrypt secret values, \
|
||||
and secrets_add/secrets_update to write encrypted secrets."
|
||||
.to_string(),
|
||||
|
||||
Reference in New Issue
Block a user