@@ -49,6 +49,23 @@ pub fn is_explicit_tcp_protocol(protocol: &str) -> bool {
4949 protocol. eq_ignore_ascii_case ( "tcp" )
5050}
5151
52+ /// `skip` is deliberately not offered as an alternative: it is a posture
53+ /// downgrade, not a fix.
54+ const TLS_MODE_REMEDIATION : & str = "remove the tls field to keep automatic TLS termination" ;
55+
56+ /// Reject `tls` values outside the supported set.
57+ ///
58+ /// Omitted means auto-detect and terminate for inspection; `skip` opts out.
59+ pub fn validate_tls_mode ( tls : & str ) -> Option < String > {
60+ if tls. is_empty ( ) || tls. eq_ignore_ascii_case ( "skip" ) {
61+ return None ;
62+ }
63+
64+ Some ( format ! (
65+ "unsupported tls value '{tls}'; {TLS_MODE_REMEDIATION}"
66+ ) )
67+ }
68+
5269/// Reject transport choices that an in-sandbox agent must not grant itself.
5370///
5471/// An omitted protocol remains allowed: it uses the established explicit
@@ -101,12 +118,33 @@ mod agent_transport_tests {
101118 #[ test]
102119 fn agent_cannot_request_native_tcp_or_skip_tls_inspection ( ) {
103120 assert ! ( agent_authored_transport_rejection( "tcp" , "" ) . is_some( ) ) ;
104- assert ! ( agent_authored_transport_rejection( "TCP" , "terminate " ) . is_some( ) ) ;
121+ assert ! ( agent_authored_transport_rejection( "TCP" , "" ) . is_some( ) ) ;
105122 assert ! ( agent_authored_transport_rejection( "" , "skip" ) . is_some( ) ) ;
106123 assert ! ( agent_authored_transport_rejection( "rest" , "SKIP" ) . is_some( ) ) ;
107124 }
108125}
109126
127+ #[ cfg( test) ]
128+ mod tls_mode_tests {
129+ use super :: validate_tls_mode;
130+
131+ #[ test]
132+ fn omitted_and_skip_are_accepted ( ) {
133+ for tls in [ "" , "skip" ] {
134+ assert_eq ! ( validate_tls_mode( tls) , None , "tls: {tls:?}" ) ;
135+ }
136+ }
137+
138+ #[ test]
139+ fn every_other_value_is_rejected ( ) {
140+ for tls in [ "terminate" , "passthrough" , "bogus" ] {
141+ let error = validate_tls_mode ( tls) . expect ( "must be rejected" ) ;
142+ assert ! ( error. contains( & format!( "unsupported tls value '{tls}'" ) ) ) ;
143+ assert ! ( error. contains( "remove the tls field" ) ) ;
144+ }
145+ }
146+ }
147+
110148/// Fields extracted from an endpoint definition needed for L7 semantic
111149/// validation. Both profile lint and the runtime validator construct this
112150/// from their own data representation.
@@ -120,6 +158,9 @@ pub struct L7EndpointFields<'a> {
120158 /// means no access preset.
121159 pub access : & ' a str ,
122160
161+ /// TLS handling as authored. Empty string means the default.
162+ pub tls : & ' a str ,
163+
123164 /// `true` when the endpoint has a non-empty rules list.
124165 pub has_rules : bool ,
125166
@@ -134,8 +175,8 @@ pub struct L7EndpointFields<'a> {
134175 pub allow_all_known_mcp_methods : bool ,
135176}
136177
137- /// Validate the semantic consistency of an L7 endpoint's field
138- /// combination .
178+ /// Validate an L7 endpoint's individual field values and their semantic
179+ /// consistency with each other .
139180///
140181/// Returns a list of error message strings. An empty list means the
141182/// endpoint passes validation. Messages are bare — callers prepend
@@ -226,6 +267,11 @@ pub fn validate_l7_endpoint_semantics(ep: &L7EndpointFields<'_>) -> Vec<String>
226267 errors. push ( "deny_rules require rules or access to define the base allow set" . to_string ( ) ) ;
227268 }
228269
270+ // 10. Unsupported tls value.
271+ if let Some ( reason) = validate_tls_mode ( ep. tls ) {
272+ errors. push ( reason) ;
273+ }
274+
229275 errors
230276}
231277
@@ -237,6 +283,7 @@ mod tests {
237283 L7EndpointFields {
238284 protocol : "rest" ,
239285 access : "read-only" ,
286+ tls : "" ,
240287 has_rules : false ,
241288 has_deny_rules : false ,
242289 rules_would_deny_all : false ,
@@ -255,6 +302,7 @@ mod tests {
255302 let ep = L7EndpointFields {
256303 protocol : "ftp" ,
257304 access : "" ,
305+ tls : "" ,
258306 has_rules : false ,
259307 has_deny_rules : false ,
260308 rules_would_deny_all : false ,
@@ -269,6 +317,7 @@ mod tests {
269317 let ep = L7EndpointFields {
270318 protocol : "rest" ,
271319 access : "full" ,
320+ tls : "" ,
272321 has_rules : true ,
273322 has_deny_rules : false ,
274323 rules_would_deny_all : false ,
@@ -283,6 +332,7 @@ mod tests {
283332 let ep = L7EndpointFields {
284333 protocol : "json-rpc" ,
285334 access : "full" ,
335+ tls : "" ,
286336 has_rules : false ,
287337 has_deny_rules : false ,
288338 rules_would_deny_all : false ,
@@ -301,6 +351,7 @@ mod tests {
301351 let ep = L7EndpointFields {
302352 protocol : "mcp" ,
303353 access : "full" ,
354+ tls : "" ,
304355 has_rules : false ,
305356 has_deny_rules : false ,
306357 rules_would_deny_all : false ,
@@ -319,6 +370,7 @@ mod tests {
319370 let ep = L7EndpointFields {
320371 protocol : "json-rpc" ,
321372 access : "" ,
373+ tls : "" ,
322374 has_rules : false ,
323375 has_deny_rules : false ,
324376 rules_would_deny_all : false ,
@@ -333,6 +385,7 @@ mod tests {
333385 let ep = L7EndpointFields {
334386 protocol : "rest" ,
335387 access : "" ,
388+ tls : "" ,
336389 has_rules : false ,
337390 has_deny_rules : false ,
338391 rules_would_deny_all : false ,
@@ -351,6 +404,7 @@ mod tests {
351404 let ep = L7EndpointFields {
352405 protocol : "mcp" ,
353406 access : "" ,
407+ tls : "" ,
354408 has_rules : false ,
355409 has_deny_rules : false ,
356410 rules_would_deny_all : false ,
@@ -365,6 +419,7 @@ mod tests {
365419 let ep = L7EndpointFields {
366420 protocol : "mcp" ,
367421 access : "" ,
422+ tls : "" ,
368423 has_rules : false ,
369424 has_deny_rules : false ,
370425 rules_would_deny_all : false ,
@@ -379,6 +434,7 @@ mod tests {
379434 let ep = L7EndpointFields {
380435 protocol : "rest" ,
381436 access : "" ,
437+ tls : "" ,
382438 has_rules : true ,
383439 has_deny_rules : false ,
384440 rules_would_deny_all : true ,
@@ -393,6 +449,7 @@ mod tests {
393449 let ep = L7EndpointFields {
394450 protocol : "mcp" ,
395451 access : "" ,
452+ tls : "" ,
396453 has_rules : true ,
397454 has_deny_rules : false ,
398455 rules_would_deny_all : true ,
@@ -410,6 +467,7 @@ mod tests {
410467 let ep = L7EndpointFields {
411468 protocol : "" ,
412469 access : "" ,
470+ tls : "" ,
413471 has_rules : false ,
414472 has_deny_rules : true ,
415473 rules_would_deny_all : false ,
@@ -428,6 +486,7 @@ mod tests {
428486 let ep = L7EndpointFields {
429487 protocol : "rest" ,
430488 access : "" ,
489+ tls : "" ,
431490 has_rules : false ,
432491 has_deny_rules : true ,
433492 rules_would_deny_all : false ,
@@ -446,6 +505,7 @@ mod tests {
446505 let ep = L7EndpointFields {
447506 protocol : "" ,
448507 access : "" ,
508+ tls : "" ,
449509 has_rules : false ,
450510 has_deny_rules : false ,
451511 rules_would_deny_all : false ,
@@ -460,6 +520,7 @@ mod tests {
460520 let ep = L7EndpointFields {
461521 protocol : "tcp" ,
462522 access : "" ,
523+ tls : "" ,
463524 has_rules : false ,
464525 has_deny_rules : false ,
465526 rules_would_deny_all : false ,
@@ -476,6 +537,7 @@ mod tests {
476537 let ep = L7EndpointFields {
477538 protocol : "tcp" ,
478539 access : "full" ,
540+ tls : "" ,
479541 has_rules : false ,
480542 has_deny_rules : false ,
481543 rules_would_deny_all : false ,
@@ -520,6 +582,7 @@ mod tests {
520582 let ep = L7EndpointFields {
521583 protocol : "json-rpc" ,
522584 access : "full" ,
585+ tls : "" ,
523586 has_rules : false ,
524587 has_deny_rules : false ,
525588 rules_would_deny_all : false ,
@@ -541,6 +604,7 @@ mod tests {
541604 let ep = L7EndpointFields {
542605 protocol : "json-rpc" ,
543606 access : "" ,
607+ tls : "" ,
544608 has_rules : false ,
545609 has_deny_rules : false ,
546610 rules_would_deny_all : false ,
0 commit comments