@@ -5,6 +5,13 @@ use serde_json::{json, Value};
55use std:: sync:: Arc ;
66use uuid:: Uuid ;
77
8+ // ── Safety guardrails ───────────────────────────────────────────────────────
9+ const MAX_INGEST_TEXT_BYTES : usize = 100_000 ; // 100KB per memory
10+ const MAX_BATCH_SIZE : usize = 100 ; // memory_ingest_batch
11+ const MAX_SEARCH_LIMIT : usize = 100 ; // memory_search
12+ const MAX_CONTEXT_TOKENS : usize = 8_000 ; // memory_context
13+ const MAX_TAG_SCAN_PER_TIER : usize = 10_000 ; // tag_list_taxonomy
14+
815/// Return the list of available tools (MCP tool schema format).
916/// Includes built-in tools and any plugin-registered tools.
1017pub fn list_tools_with_plugins ( cortex : & Arc < Cortex > ) -> Value {
@@ -580,6 +587,9 @@ fn get_embedding(args: &Value) -> Option<Vec<f32>> {
580587
581588fn tool_memory_ingest ( cortex : & Arc < Cortex > , args : & Value ) -> Result < String , String > {
582589 let text = get_str ( args, "text" ) . ok_or ( "missing 'text'" ) ?;
590+ if text. len ( ) > MAX_INGEST_TEXT_BYTES {
591+ return Err ( format ! ( "text too large: {} bytes (max {})" , text. len( ) , MAX_INGEST_TEXT_BYTES ) ) ;
592+ }
583593 let channel = get_str ( args, "channel" ) . ok_or ( "missing 'channel'" ) ?;
584594 let user_id = get_str ( args, "user_id" ) ;
585595 let salience = args. get ( "salience" ) . and_then ( |v| v. as_f64 ( ) ) . map ( |v| v as f32 ) ;
@@ -629,7 +639,7 @@ fn tool_memory_consolidate(cortex: &Arc<Cortex>) -> Result<String, String> {
629639
630640fn tool_memory_search ( cortex : & Arc < Cortex > , args : & Value ) -> Result < String , String > {
631641 let query = get_str ( args, "query" ) . ok_or ( "missing 'query'" ) ?;
632- let limit = get_usize ( args, "limit" , 10 ) ;
642+ let limit = get_usize ( args, "limit" , 10 ) . min ( MAX_SEARCH_LIMIT ) ;
633643 let channel = get_str ( args, "channel" ) ;
634644 let person_id = get_str ( args, "person_id" )
635645 . and_then ( |s| Uuid :: parse_str ( s) . ok ( ) ) ;
@@ -662,7 +672,7 @@ fn tool_memory_search(cortex: &Arc<Cortex>, args: &Value) -> Result<String, Stri
662672}
663673
664674fn tool_memory_context ( cortex : & Arc < Cortex > , args : & Value ) -> Result < String , String > {
665- let max_tokens = get_usize ( args, "max_tokens" , 2000 ) ;
675+ let max_tokens = get_usize ( args, "max_tokens" , 2000 ) . min ( MAX_CONTEXT_TOKENS ) ;
666676 let channel = get_str ( args, "channel" ) ;
667677 let person_id = get_str ( args, "person_id" )
668678 . and_then ( |s| Uuid :: parse_str ( s) . ok ( ) ) ;
@@ -852,6 +862,8 @@ fn tool_memory_compress(cortex: &Arc<Cortex>, args: &Value) -> Result<String, St
852862 . get ( "max_age_days" )
853863 . and_then ( |v| v. as_i64 ( ) )
854864 . unwrap_or ( 7 ) ;
865+ let min_messages = min_messages. max ( 1 ) ; // prevent compressing everything
866+ let max_age_days = max_age_days. max ( 1 ) ; // prevent compressing fresh memories
855867
856868 let report = cortex
857869 . run_compression ( min_messages, max_age_days)
@@ -1001,6 +1013,9 @@ fn tool_memory_ingest_batch(cortex: &Arc<Cortex>, args: &Value) -> Result<String
10011013 let items_arr = args. get ( "items" )
10021014 . and_then ( |v| v. as_array ( ) )
10031015 . ok_or ( "missing 'items' array" ) ?;
1016+ if items_arr. len ( ) > MAX_BATCH_SIZE {
1017+ return Err ( format ! ( "batch too large: {} items (max {})" , items_arr. len( ) , MAX_BATCH_SIZE ) ) ;
1018+ }
10041019
10051020 let items: Vec < cortex_core:: types:: BatchIngestItem > = items_arr. iter ( ) . map ( |item| {
10061021 cortex_core:: types:: BatchIngestItem {
@@ -1035,7 +1050,7 @@ fn tool_tag_list_taxonomy(cortex: &Arc<Cortex>) -> Result<String, String> {
10351050 ] ;
10361051
10371052 for tier in & tiers {
1038- if let Ok ( mems) = cortex. storage ( ) . list_by_tier ( * tier, 100_000 ) {
1053+ if let Ok ( mems) = cortex. storage ( ) . list_by_tier ( * tier, MAX_TAG_SCAN_PER_TIER ) {
10391054 for mem in mems {
10401055 for tag in & mem. tags {
10411056 * tag_counts. entry ( tag. clone ( ) ) . or_insert ( 0 ) += 1 ;
@@ -1103,6 +1118,9 @@ fn tool_person_merge(cortex: &Arc<Cortex>, args: &Value) -> Result<String, Strin
11031118 let source_str = get_str ( args, "source_id" ) . ok_or ( "missing 'source_id'" ) ?;
11041119 let target_id = Uuid :: parse_str ( target_str) . map_err ( |e| format ! ( "Invalid target UUID: {}" , e) ) ?;
11051120 let source_id = Uuid :: parse_str ( source_str) . map_err ( |e| format ! ( "Invalid source UUID: {}" , e) ) ?;
1121+ if target_id == source_id {
1122+ return Err ( "cannot merge a person with themselves" . to_string ( ) ) ;
1123+ }
11061124
11071125 let merged = cortex. merge_people ( target_id, source_id) . map_err ( |e| e. to_string ( ) ) ?;
11081126
0 commit comments