@@ -1200,6 +1200,12 @@ fn proto_to_opa_data_json(proto: &ProtoSandboxPolicy, entrypoint_pid: u32) -> St
12001200 if !query. is_empty ( ) {
12011201 allow[ "query" ] = query. into ( ) ;
12021202 }
1203+ let params = a. map_or_else ( serde_json:: Map :: new, |allow| {
1204+ l7_matchers_to_json ( & allow. params )
1205+ } ) ;
1206+ if !params. is_empty ( ) {
1207+ allow[ "params" ] = params. into ( ) ;
1208+ }
12031209 serde_json:: json!( { "allow" : allow } )
12041210 } )
12051211 . collect ( ) ;
@@ -1239,6 +1245,10 @@ fn proto_to_opa_data_json(proto: &ProtoSandboxPolicy, entrypoint_pid: u32) -> St
12391245 if !query. is_empty ( ) {
12401246 deny[ "query" ] = query. into ( ) ;
12411247 }
1248+ let params = l7_matchers_to_json ( & d. params ) ;
1249+ if !params. is_empty ( ) {
1250+ deny[ "params" ] = params. into ( ) ;
1251+ }
12421252 deny
12431253 } )
12441254 . collect ( ) ;
@@ -2843,6 +2853,97 @@ network_policies:
28432853 assert ! ( !eval_l7( & engine, & deny_input) ) ;
28442854 }
28452855
2856+ #[ test]
2857+ fn l7_mcp_tool_params_from_proto_are_enforced ( ) {
2858+ // Regression: the proto load path (from_proto) must carry the rule
2859+ // `params` matcher map. If it is dropped, a tools/call allow rule
2860+ // narrowed to one tool degrades to allow-any-tool in production, even
2861+ // though the YAML/add_data_json path enforces it correctly.
2862+ let mut params = std:: collections:: HashMap :: new ( ) ;
2863+ params. insert (
2864+ "name" . to_string ( ) ,
2865+ L7QueryMatcher {
2866+ glob : String :: new ( ) ,
2867+ any : vec ! [ "read_status" . to_string( ) , "submit_*" . to_string( ) ] ,
2868+ } ,
2869+ ) ;
2870+
2871+ let mut network_policies = std:: collections:: HashMap :: new ( ) ;
2872+ network_policies. insert (
2873+ "mcp_proto" . to_string ( ) ,
2874+ NetworkPolicyRule {
2875+ name : "mcp_proto" . to_string ( ) ,
2876+ endpoints : vec ! [ NetworkEndpoint {
2877+ host: "mcp.proto.com" . to_string( ) ,
2878+ port: 8000 ,
2879+ path: "/mcp" . to_string( ) ,
2880+ protocol: "mcp" . to_string( ) ,
2881+ enforcement: "enforce" . to_string( ) ,
2882+ rules: vec![ L7Rule {
2883+ allow: Some ( L7Allow {
2884+ method: "tools/call" . to_string( ) ,
2885+ path: String :: new( ) ,
2886+ command: String :: new( ) ,
2887+ query: std:: collections:: HashMap :: new( ) ,
2888+ operation_type: String :: new( ) ,
2889+ operation_name: String :: new( ) ,
2890+ fields: Vec :: new( ) ,
2891+ params,
2892+ } ) ,
2893+ } ] ,
2894+ ..Default :: default ( )
2895+ } ] ,
2896+ binaries : vec ! [ NetworkBinary {
2897+ path: "/usr/bin/curl" . to_string( ) ,
2898+ ..Default :: default ( )
2899+ } ] ,
2900+ } ,
2901+ ) ;
2902+
2903+ let proto = ProtoSandboxPolicy {
2904+ version : 1 ,
2905+ filesystem : Some ( ProtoFs {
2906+ include_workdir : true ,
2907+ read_only : vec ! [ ] ,
2908+ read_write : vec ! [ ] ,
2909+ } ) ,
2910+ landlock : Some ( openshell_core:: proto:: LandlockPolicy {
2911+ compatibility : "best_effort" . to_string ( ) ,
2912+ } ) ,
2913+ process : Some ( ProtoProc {
2914+ run_as_user : "sandbox" . to_string ( ) ,
2915+ run_as_group : "sandbox" . to_string ( ) ,
2916+ } ) ,
2917+ network_policies,
2918+ } ;
2919+
2920+ let engine = OpaEngine :: from_proto ( & proto) . expect ( "engine from proto" ) ;
2921+
2922+ let allowed_tool = l7_jsonrpc_input_with_params (
2923+ "mcp.proto.com" ,
2924+ 8000 ,
2925+ "/mcp" ,
2926+ "tools/call" ,
2927+ serde_json:: json!( { "name" : "read_status" } ) ,
2928+ ) ;
2929+ assert ! (
2930+ eval_l7( & engine, & allowed_tool) ,
2931+ "tools/call for an allowed tool should be permitted"
2932+ ) ;
2933+
2934+ let blocked_tool = l7_jsonrpc_input_with_params (
2935+ "mcp.proto.com" ,
2936+ 8000 ,
2937+ "/mcp" ,
2938+ "tools/call" ,
2939+ serde_json:: json!( { "name" : "blocked_action" } ) ,
2940+ ) ;
2941+ assert ! (
2942+ !eval_l7( & engine, & blocked_tool) ,
2943+ "tools/call for a non-matching tool must be denied (params matcher must survive the proto load path)"
2944+ ) ;
2945+ }
2946+
28462947 #[ test]
28472948 fn l7_jsonrpc_endpoint_ignores_rest_shaped_allow_rules ( ) {
28482949 let data = serde_json:: json!( {
0 commit comments