@@ -547,6 +547,95 @@ describe("capability router", () => {
547547 ] ) ;
548548 } ) ;
549549
550+ it ( "routes remote plugin asset reads through the runtime broker" , async ( ) => {
551+ const router = new RuntimeBrokerCapabilityRouter ( {
552+ invokeRuntime : async ( ) => ( {
553+ path : "/assets/weather.js" ,
554+ contentType : "text/javascript" ,
555+ bodyBase64 : Buffer . from ( "export const weather = true;" ) . toString (
556+ "base64" ,
557+ ) ,
558+ integrity : "sha256-weather" ,
559+ } ) ,
560+ } ) ;
561+
562+ await expect (
563+ router . plugin . getAsset ( {
564+ moduleId : "remote-weather" ,
565+ path : "/assets/weather.js" ,
566+ } ) ,
567+ ) . resolves . toEqual ( {
568+ path : "/assets/weather.js" ,
569+ contentType : "text/javascript" ,
570+ bodyBase64 : "ZXhwb3J0IGNvbnN0IHdlYXRoZXIgPSB0cnVlOw==" ,
571+ integrity : "sha256-weather" ,
572+ } ) ;
573+ } ) ;
574+
575+ it ( "rejects remote plugin assets with unsafe returned paths" , async ( ) => {
576+ const router = new RuntimeBrokerCapabilityRouter ( {
577+ invokeRuntime : async ( ) => ( {
578+ path : "../secret.js" ,
579+ contentType : "text/javascript" ,
580+ bodyBase64 : Buffer . from ( "export default {};" ) . toString ( "base64" ) ,
581+ } ) ,
582+ } ) ;
583+
584+ await expect (
585+ router . plugin . getAsset ( {
586+ moduleId : "remote-weather" ,
587+ path : "/assets/weather.js" ,
588+ } ) ,
589+ ) . rejects . toMatchObject ( {
590+ code : "CAPABILITY_DECODE_FAILED" ,
591+ method : "plugin.asset.get" ,
592+ message :
593+ "path must not contain empty, current-directory, or parent-directory segments." ,
594+ } ) ;
595+ } ) ;
596+
597+ it ( "rejects remote plugin assets with unsafe content types" , async ( ) => {
598+ const router = new RuntimeBrokerCapabilityRouter ( {
599+ invokeRuntime : async ( ) => ( {
600+ path : "/assets/weather.js" ,
601+ contentType : "text/javascript\r\nx-injected: yes" ,
602+ bodyBase64 : Buffer . from ( "export default {};" ) . toString ( "base64" ) ,
603+ } ) ,
604+ } ) ;
605+
606+ await expect (
607+ router . plugin . getAsset ( {
608+ moduleId : "remote-weather" ,
609+ path : "/assets/weather.js" ,
610+ } ) ,
611+ ) . rejects . toMatchObject ( {
612+ code : "CAPABILITY_DECODE_FAILED" ,
613+ method : "plugin.asset.get" ,
614+ message : "contentType must not contain control characters." ,
615+ } ) ;
616+ } ) ;
617+
618+ it ( "rejects remote plugin assets with invalid base64 bodies" , async ( ) => {
619+ const router = new RuntimeBrokerCapabilityRouter ( {
620+ invokeRuntime : async ( ) => ( {
621+ path : "/assets/weather.js" ,
622+ contentType : "text/javascript" ,
623+ bodyBase64 : "not base64!" ,
624+ } ) ,
625+ } ) ;
626+
627+ await expect (
628+ router . plugin . getAsset ( {
629+ moduleId : "remote-weather" ,
630+ path : "/assets/weather.js" ,
631+ } ) ,
632+ ) . rejects . toMatchObject ( {
633+ code : "CAPABILITY_DECODE_FAILED" ,
634+ method : "plugin.asset.get" ,
635+ message : "bodyBase64 must be valid base64." ,
636+ } ) ;
637+ } ) ;
638+
550639 it ( "rejects remote plugin manifests with empty module identifiers" , async ( ) => {
551640 const router = new RuntimeBrokerCapabilityRouter ( {
552641 invokeRuntime : async ( ) => ( {
0 commit comments