@@ -1015,6 +1015,133 @@ describe("Wildcard prefix support", () => {
10151015 expect ( link4 ! . properties . href ) . toBe ( "/parent" ) ;
10161016 } ) ;
10171017
1018+ it ( "wildcard with no defaultOrigin still blocks dangerous protocols" , async ( ) => {
1019+ // Test javascript: protocol is blocked
1020+ const tree1 = await processMarkdown ( "[XSS](javascript:alert('XSS'))" , {
1021+ allowedLinkPrefixes : [ "*" ] ,
1022+ } ) ;
1023+
1024+ const blocked1 = findSpanWithText ( tree1 , "[blocked]" ) ;
1025+ expect ( blocked1 ) . not . toBeNull ( ) ;
1026+
1027+ // Test data: protocol is blocked for links
1028+ const tree2 = await processMarkdown (
1029+ "[Data](data:text/html,<script>alert('XSS')</script>)" ,
1030+ {
1031+ allowedLinkPrefixes : [ "*" ] ,
1032+ } ,
1033+ ) ;
1034+
1035+ const blocked2 = findSpanWithText ( tree2 , "[blocked]" ) ;
1036+ expect ( blocked2 ) . not . toBeNull ( ) ;
1037+
1038+ // Test vbscript: protocol is blocked
1039+ const tree3 = await processMarkdown ( "[VBS](vbscript:msgbox('XSS'))" , {
1040+ allowedLinkPrefixes : [ "*" ] ,
1041+ } ) ;
1042+
1043+ const blocked3 = findSpanWithText ( tree3 , "[blocked]" ) ;
1044+ expect ( blocked3 ) . not . toBeNull ( ) ;
1045+
1046+ // Test file: protocol is blocked
1047+ const tree4 = await processMarkdown ( "[File](file:///etc/passwd)" , {
1048+ allowedLinkPrefixes : [ "*" ] ,
1049+ } ) ;
1050+
1051+ const blocked4 = findSpanWithText ( tree4 , "[blocked]" ) ;
1052+ expect ( blocked4 ) . not . toBeNull ( ) ;
1053+
1054+ // Test data:image is blocked for images without allowDataImages
1055+ const tree5 = await processMarkdown (
1056+ "" ,
1057+ {
1058+ allowedImagePrefixes : [ "*" ] ,
1059+ allowDataImages : false ,
1060+ } ,
1061+ ) ;
1062+
1063+ const blockedImg = findSpanWithText ( tree5 , "[Image blocked" ) ;
1064+ expect ( blockedImg ) . not . toBeNull ( ) ;
1065+ } ) ;
1066+
1067+ it ( "wildcard with no defaultOrigin blocks invalid/unparseable URLs" , async ( ) => {
1068+ // Test empty URL - markdown parsers typically convert () to href=""
1069+ const tree1 = await processMarkdown ( "[Empty]()" , {
1070+ allowedLinkPrefixes : [ "*" ] ,
1071+ } ) ;
1072+
1073+ const link1 = findElement ( tree1 , "a" ) ;
1074+ // Empty href should either be blocked or converted to #
1075+ if ( link1 ) {
1076+ expect ( link1 . properties . href ) . toBe ( "#" ) ;
1077+ } else {
1078+ const blocked1 = findSpanWithText ( tree1 , "[blocked]" ) ;
1079+ expect ( blocked1 ) . not . toBeNull ( ) ;
1080+ }
1081+
1082+ // Test malformed protocol-like string
1083+ const tree2 = await processMarkdown ( "[Malformed](ht!tp://invalid)" , {
1084+ allowedLinkPrefixes : [ "*" ] ,
1085+ } ) ;
1086+
1087+ // This should either be blocked or the markdown parser might treat it as text
1088+ const link2 = findElement ( tree2 , "a" ) ;
1089+ if ( link2 ) {
1090+ // If parsed as a link, it should be blocked or normalized
1091+ const blocked2 = findSpanWithText ( tree2 , "[blocked]" ) ;
1092+ expect ( blocked2 ) . not . toBeNull ( ) ;
1093+ }
1094+ } ) ;
1095+
1096+ it ( "wildcard with no defaultOrigin blocks disallowed protocols even with allowedProtocols" , async ( ) => {
1097+ // javascript: should be blocked even with allowedProtocols: ["*"]
1098+ const tree1 = await processMarkdown ( "[JS](javascript:alert(1))" , {
1099+ allowedLinkPrefixes : [ "*" ] ,
1100+ allowedProtocols : [ "*" ] ,
1101+ } ) ;
1102+
1103+ const blocked1 = findSpanWithText ( tree1 , "[blocked]" ) ;
1104+ expect ( blocked1 ) . not . toBeNull ( ) ;
1105+
1106+ // data: should be blocked for links even with allowedProtocols: ["*"]
1107+ const tree2 = await processMarkdown ( "[Data](data:text/html,test)" , {
1108+ allowedLinkPrefixes : [ "*" ] ,
1109+ allowedProtocols : [ "*" ] ,
1110+ } ) ;
1111+
1112+ const blocked2 = findSpanWithText ( tree2 , "[blocked]" ) ;
1113+ expect ( blocked2 ) . not . toBeNull ( ) ;
1114+
1115+ // file: should be blocked even with allowedProtocols: ["*"]
1116+ const tree3 = await processMarkdown ( "[File](file:///etc/passwd)" , {
1117+ allowedLinkPrefixes : [ "*" ] ,
1118+ allowedProtocols : [ "*" ] ,
1119+ } ) ;
1120+
1121+ const blocked3 = findSpanWithText ( tree3 , "[blocked]" ) ;
1122+ expect ( blocked3 ) . not . toBeNull ( ) ;
1123+ } ) ;
1124+
1125+ it ( "relative URLs are blocked when wildcard is not used and defaultOrigin is different" , async ( ) => {
1126+ // With specific prefixes and a defaultOrigin that doesn't match, relative URLs should be blocked
1127+ const tree1 = await processMarkdown ( "[Relative](/path)" , {
1128+ defaultOrigin : "https://mysite.com" ,
1129+ allowedLinkPrefixes : [ "https://example.com/" ] ,
1130+ } ) ;
1131+
1132+ const blocked1 = findSpanWithText ( tree1 , "[blocked]" ) ;
1133+ expect ( blocked1 ) . not . toBeNull ( ) ;
1134+
1135+ // Images should also be blocked
1136+ const tree2 = await processMarkdown ( "" , {
1137+ defaultOrigin : "https://mysite.com" ,
1138+ allowedImagePrefixes : [ "https://example.com/" ] ,
1139+ } ) ;
1140+
1141+ const blockedImg = findSpanWithText ( tree2 , "[Image blocked" ) ;
1142+ expect ( blockedImg ) . not . toBeNull ( ) ;
1143+ } ) ;
1144+
10181145 it ( "wildcard works alongside other prefixes" , async ( ) => {
10191146 const tree = await processMarkdown (
10201147 "[Any Link](https://random-site.com/path)" ,
0 commit comments