22
33use super :: types:: ModelPreset ;
44
5+ /// Default model for Chutes provider.
6+ /// This is the fallback model when no specific model is provided.
7+ pub const DEFAULT_CHUTES_MODEL : & str = "moonshotai/Kimi-K2.5-TEE" ;
8+
59/// Available model presets.
610pub const MODEL_PRESETS : & [ ModelPreset ] = & [
711 ModelPreset {
@@ -806,6 +810,17 @@ pub const MODEL_PRESETS: &[ModelPreset] = &[
806810 supports_tools : true ,
807811 supports_reasoning : false ,
808812 } ,
813+ // Chutes TEE models (Trusted Execution Environment)
814+ // Security requirement: Only models with '-TEE' suffix are allowed
815+ ModelPreset {
816+ id : "moonshotai/Kimi-K2.5-TEE" ,
817+ name : "Kimi K2.5 (TEE)" ,
818+ provider : "chutes" ,
819+ context_window : 262_144 ,
820+ supports_vision : false ,
821+ supports_tools : true ,
822+ supports_reasoning : true ,
823+ } ,
809824] ;
810825
811826/// Get a model preset by ID.
@@ -820,3 +835,249 @@ pub fn get_models_for_provider(provider: &str) -> Vec<&'static ModelPreset> {
820835 . filter ( |m| m. provider == provider)
821836 . collect ( )
822837}
838+
839+ /// Validates that a model is allowed for the Chutes provider.
840+ /// Chutes only allows TEE (Trusted Execution Environment) models for security.
841+ /// Any model ending with '-TEE' suffix (case-insensitive) is accepted.
842+ /// Returns Ok(()) if valid, Err with message if invalid.
843+ ///
844+ /// # Security
845+ /// This function performs strict validation to prevent bypass attacks:
846+ /// - Rejects null bytes and control characters (prevents C-string truncation attacks)
847+ /// - Only allows safe ASCII characters: alphanumeric, hyphen, underscore, dot, forward slash
848+ /// - Case-insensitive suffix check for -TEE
849+ pub fn validate_chutes_model ( model : & str ) -> Result < ( ) , String > {
850+ let model = model. trim ( ) ;
851+
852+ // Check for empty model
853+ if model. is_empty ( ) {
854+ return Err ( "Model name cannot be empty for Chutes provider" . to_string ( ) ) ;
855+ }
856+
857+ // SECURITY: Reject null bytes and control characters (CWE-626, CWE-158)
858+ // This prevents null byte injection attacks where "malicious\0-TEE" would
859+ // pass validation but be truncated to "malicious" by C libraries/APIs
860+ if model. bytes ( ) . any ( |b| b == 0 || b < 0x20 ) {
861+ return Err (
862+ "Model name contains invalid characters (null bytes or control characters)" . to_string ( ) ,
863+ ) ;
864+ }
865+
866+ // SECURITY: Only allow safe ASCII characters for model names
867+ // Allowed: a-z, A-Z, 0-9, hyphen (-), underscore (_), dot (.), forward slash (/)
868+ // This prevents Unicode homoglyph attacks and other encoding-based bypasses
869+ if !model
870+ . chars ( )
871+ . all ( |c| c. is_ascii_alphanumeric ( ) || matches ! ( c, '-' | '_' | '.' | '/' ) )
872+ {
873+ return Err (
874+ "Model name contains invalid characters. Only alphanumeric characters, \
875+ hyphens, underscores, dots, and forward slashes are allowed."
876+ . to_string ( ) ,
877+ ) ;
878+ }
879+
880+ // Check suffix (case-insensitive) - any model ending with -TEE is allowed
881+ if !model. to_uppercase ( ) . ends_with ( "-TEE" ) {
882+ return Err ( format ! (
883+ "Chutes provider only allows TEE models (models ending with '-TEE'). \
884+ Model '{}' is not a TEE model. Default model: {}",
885+ model, DEFAULT_CHUTES_MODEL
886+ ) ) ;
887+ }
888+
889+ Ok ( ( ) )
890+ }
891+
892+ /// Checks if a provider restricts custom models.
893+ /// All providers allow custom models, but Chutes requires -TEE suffix.
894+ pub fn provider_allows_custom_models ( provider : & str ) -> bool {
895+ // All providers allow custom models
896+ // Chutes allows any model with -TEE suffix (validated via validate_chutes_model)
897+ let _ = provider; // Used for potential future provider-specific restrictions
898+ true
899+ }
900+
901+ #[ cfg( test) ]
902+ mod tests {
903+ use super :: * ;
904+
905+ #[ test]
906+ fn test_validate_chutes_model_valid ( ) {
907+ // Default TEE model
908+ assert ! ( validate_chutes_model( "moonshotai/Kimi-K2.5-TEE" ) . is_ok( ) ) ;
909+ // Case insensitive
910+ assert ! ( validate_chutes_model( "moonshotai/kimi-k2.5-tee" ) . is_ok( ) ) ;
911+ assert ! ( validate_chutes_model( "MOONSHOTAI/KIMI-K2.5-TEE" ) . is_ok( ) ) ;
912+ // Whitespace handling
913+ assert ! ( validate_chutes_model( " moonshotai/Kimi-K2.5-TEE " ) . is_ok( ) ) ;
914+ // Any model with -TEE suffix is valid
915+ assert ! ( validate_chutes_model( "custom-model-TEE" ) . is_ok( ) ) ;
916+ assert ! ( validate_chutes_model( "some-provider/my-model-TEE" ) . is_ok( ) ) ;
917+ assert ! ( validate_chutes_model( "another-model-tee" ) . is_ok( ) ) ;
918+ assert ! ( validate_chutes_model( "UPPERCASE-MODEL-TEE" ) . is_ok( ) ) ;
919+ // Allowed special characters
920+ assert ! ( validate_chutes_model( "provider_name/model.v1-TEE" ) . is_ok( ) ) ;
921+ assert ! ( validate_chutes_model( "my_custom_model-TEE" ) . is_ok( ) ) ;
922+ }
923+
924+ #[ test]
925+ fn test_validate_chutes_model_invalid ( ) {
926+ // Not a TEE model (no -TEE suffix)
927+ assert ! ( validate_chutes_model( "gpt-4" ) . is_err( ) ) ;
928+ assert ! ( validate_chutes_model( "claude-3" ) . is_err( ) ) ;
929+ assert ! ( validate_chutes_model( "some-model" ) . is_err( ) ) ;
930+
931+ // TEE in wrong position (not at the end)
932+ assert ! ( validate_chutes_model( "model-TEE-v2" ) . is_err( ) ) ;
933+ assert ! ( validate_chutes_model( "TEE-model" ) . is_err( ) ) ;
934+ assert ! ( validate_chutes_model( "my-TEE-model-v1" ) . is_err( ) ) ;
935+
936+ // Empty string
937+ let result = validate_chutes_model ( "" ) ;
938+ assert ! ( result. is_err( ) ) ;
939+ assert ! ( result. unwrap_err( ) . contains( "cannot be empty" ) ) ;
940+
941+ // Whitespace only
942+ let result = validate_chutes_model ( " " ) ;
943+ assert ! ( result. is_err( ) ) ;
944+ assert ! ( result. unwrap_err( ) . contains( "cannot be empty" ) ) ;
945+ }
946+
947+ // ===========================================
948+ // SECURITY TESTS: Bypass attempt prevention
949+ // ===========================================
950+
951+ #[ test]
952+ fn test_validate_chutes_model_null_byte_injection ( ) {
953+ // SECURITY: Null byte injection attack (CWE-626, CWE-158)
954+ // Attacker tries to bypass TEE check by appending -TEE after a null byte
955+ // C libraries would see only "gpt-4" but our validation would see "gpt-4\0-TEE"
956+ let malicious_with_null = "gpt-4\0 -TEE" ;
957+ let result = validate_chutes_model ( malicious_with_null) ;
958+ assert ! ( result. is_err( ) , "Null byte injection should be rejected" ) ;
959+ assert ! (
960+ result. unwrap_err( ) . contains( "invalid characters" ) ,
961+ "Error should mention invalid characters"
962+ ) ;
963+
964+ // More null byte attack variants
965+ assert ! ( validate_chutes_model( "claude-3\0 -TEE" ) . is_err( ) ) ;
966+ assert ! ( validate_chutes_model( "\0 model-TEE" ) . is_err( ) ) ;
967+ assert ! ( validate_chutes_model( "model\0 -TEE\0 " ) . is_err( ) ) ;
968+ }
969+
970+ #[ test]
971+ fn test_validate_chutes_model_control_characters ( ) {
972+ // SECURITY: Control character injection
973+ // Characters below 0x20 (space) could cause parsing issues
974+ assert ! ( validate_chutes_model( "model\t -TEE" ) . is_err( ) ) ; // Tab
975+ assert ! ( validate_chutes_model( "model\n -TEE" ) . is_err( ) ) ; // Newline
976+ assert ! ( validate_chutes_model( "model\r -TEE" ) . is_err( ) ) ; // Carriage return
977+ assert ! ( validate_chutes_model( "model\x1b -TEE" ) . is_err( ) ) ; // Escape
978+ assert ! ( validate_chutes_model( "model\x07 -TEE" ) . is_err( ) ) ; // Bell
979+ }
980+
981+ #[ test]
982+ fn test_validate_chutes_model_unicode_attacks ( ) {
983+ // SECURITY: Unicode homoglyph attacks
984+ // Attacker tries to use visually similar Unicode characters
985+
986+ // Cyrillic 'Е' (U+0415) looks like Latin 'E' but is different
987+ assert ! ( validate_chutes_model( "model-TЕЕ" ) . is_err( ) ) ; // Cyrillic E
988+
989+ // Fullwidth characters
990+ assert ! ( validate_chutes_model( "model-TEE" ) . is_err( ) ) ; // Fullwidth TEE
991+
992+ // Other Unicode tricks
993+ assert ! ( validate_chutes_model( "model-TEE\u{200B} " ) . is_err( ) ) ; // Zero-width space at end
994+ assert ! ( validate_chutes_model( "model\u{FEFF} -TEE" ) . is_err( ) ) ; // BOM in middle
995+
996+ // Combining characters
997+ assert ! ( validate_chutes_model( "model-TE\u{0301} E" ) . is_err( ) ) ; // E with combining acute
998+ }
999+
1000+ #[ test]
1001+ fn test_validate_chutes_model_special_characters ( ) {
1002+ // SECURITY: Reject potentially dangerous special characters
1003+ // These could cause issues in shell commands, URLs, or other contexts
1004+
1005+ assert ! ( validate_chutes_model( "model;-TEE" ) . is_err( ) ) ; // Semicolon (command separator)
1006+ assert ! ( validate_chutes_model( "model&-TEE" ) . is_err( ) ) ; // Ampersand
1007+ assert ! ( validate_chutes_model( "model|-TEE" ) . is_err( ) ) ; // Pipe
1008+ assert ! ( validate_chutes_model( "model`-TEE" ) . is_err( ) ) ; // Backtick
1009+ assert ! ( validate_chutes_model( "model$-TEE" ) . is_err( ) ) ; // Dollar sign
1010+ assert ! ( validate_chutes_model( "model'-TEE" ) . is_err( ) ) ; // Single quote
1011+ assert ! ( validate_chutes_model( "model\" -TEE" ) . is_err( ) ) ; // Double quote
1012+ assert ! ( validate_chutes_model( "model<-TEE" ) . is_err( ) ) ; // Less than
1013+ assert ! ( validate_chutes_model( "model>-TEE" ) . is_err( ) ) ; // Greater than
1014+ assert ! ( validate_chutes_model( "model(-TEE" ) . is_err( ) ) ; // Parenthesis
1015+ assert ! ( validate_chutes_model( "model)-TEE" ) . is_err( ) ) ;
1016+ assert ! ( validate_chutes_model( "model{-TEE" ) . is_err( ) ) ; // Braces
1017+ assert ! ( validate_chutes_model( "model}-TEE" ) . is_err( ) ) ;
1018+ assert ! ( validate_chutes_model( "model[-TEE" ) . is_err( ) ) ; // Brackets
1019+ assert ! ( validate_chutes_model( "model]-TEE" ) . is_err( ) ) ;
1020+ assert ! ( validate_chutes_model( "model\\ -TEE" ) . is_err( ) ) ; // Backslash
1021+ assert ! ( validate_chutes_model( "model!-TEE" ) . is_err( ) ) ; // Exclamation
1022+ assert ! ( validate_chutes_model( "model@-TEE" ) . is_err( ) ) ; // At sign
1023+ assert ! ( validate_chutes_model( "model#-TEE" ) . is_err( ) ) ; // Hash
1024+ assert ! ( validate_chutes_model( "model%-TEE" ) . is_err( ) ) ; // Percent
1025+ assert ! ( validate_chutes_model( "model^-TEE" ) . is_err( ) ) ; // Caret
1026+ assert ! ( validate_chutes_model( "model*-TEE" ) . is_err( ) ) ; // Asterisk
1027+ assert ! ( validate_chutes_model( "model=-TEE" ) . is_err( ) ) ; // Equals
1028+ assert ! ( validate_chutes_model( "model+-TEE" ) . is_err( ) ) ; // Plus
1029+ assert ! ( validate_chutes_model( "model~-TEE" ) . is_err( ) ) ; // Tilde
1030+ assert ! ( validate_chutes_model( "model?-TEE" ) . is_err( ) ) ; // Question mark
1031+ assert ! ( validate_chutes_model( "model:-TEE" ) . is_err( ) ) ; // Colon
1032+ assert ! ( validate_chutes_model( "model,-TEE" ) . is_err( ) ) ; // Comma
1033+ assert ! ( validate_chutes_model( "model -TEE" ) . is_err( ) ) ; // Space in middle
1034+ }
1035+
1036+ #[ test]
1037+ fn test_validate_chutes_model_allowed_characters ( ) {
1038+ // Verify that only allowed characters pass
1039+ // Allowed: a-z, A-Z, 0-9, hyphen (-), underscore (_), dot (.), forward slash (/)
1040+
1041+ // All allowed characters
1042+ assert ! ( validate_chutes_model( "abc123-TEE" ) . is_ok( ) ) ;
1043+ assert ! ( validate_chutes_model( "ABC123-TEE" ) . is_ok( ) ) ;
1044+ assert ! ( validate_chutes_model( "model_name-TEE" ) . is_ok( ) ) ;
1045+ assert ! ( validate_chutes_model( "model.v1-TEE" ) . is_ok( ) ) ;
1046+ assert ! ( validate_chutes_model( "provider/model-TEE" ) . is_ok( ) ) ;
1047+ assert ! ( validate_chutes_model( "my-model-TEE" ) . is_ok( ) ) ;
1048+ assert ! ( validate_chutes_model( "Provider123/Model_v1.0-TEE" ) . is_ok( ) ) ;
1049+ }
1050+
1051+ #[ test]
1052+ fn test_validate_chutes_model_error_message ( ) {
1053+ let result = validate_chutes_model ( "invalid-model" ) ;
1054+ assert ! ( result. is_err( ) ) ;
1055+ let err = result. unwrap_err ( ) ;
1056+ // Error message should mention the default model
1057+ assert ! ( err. contains( DEFAULT_CHUTES_MODEL ) ) ;
1058+ assert ! ( err. contains( "-TEE" ) ) ;
1059+ }
1060+
1061+ #[ test]
1062+ fn test_provider_allows_custom_models ( ) {
1063+ // All providers allow custom models
1064+ assert ! ( provider_allows_custom_models( "chutes" ) ) ;
1065+ assert ! ( provider_allows_custom_models( "Chutes" ) ) ;
1066+ assert ! ( provider_allows_custom_models( "CHUTES" ) ) ;
1067+ assert ! ( provider_allows_custom_models( "cortex" ) ) ;
1068+ assert ! ( provider_allows_custom_models( "openai" ) ) ;
1069+ assert ! ( provider_allows_custom_models( "anthropic" ) ) ;
1070+ }
1071+
1072+ #[ test]
1073+ fn test_default_chutes_model ( ) {
1074+ // Verify the default model is a valid TEE model
1075+ assert ! ( DEFAULT_CHUTES_MODEL . to_uppercase( ) . ends_with( "-TEE" ) ) ;
1076+ assert_eq ! ( DEFAULT_CHUTES_MODEL , "moonshotai/Kimi-K2.5-TEE" ) ;
1077+ // Verify the default model passes our own validation
1078+ assert ! (
1079+ validate_chutes_model( DEFAULT_CHUTES_MODEL ) . is_ok( ) ,
1080+ "Default Chutes model must pass validation"
1081+ ) ;
1082+ }
1083+ }
0 commit comments