@@ -196,6 +196,12 @@ function signedHostedCatalogSnapshot(params: {
196196 } ;
197197}
198198
199+ function dsseResponse ( body : BodyInit | null , init : ResponseInit = { } ) : Response {
200+ const headers = new Headers ( init . headers ) ;
201+ headers . set ( "content-type" , "application/vnd.dsse+json" ) ;
202+ return new Response ( body , { ...init , headers } ) ;
203+ }
204+
199205describe ( "official external plugin catalog" , ( ) => {
200206 it ( "keeps hosted fetch guard loading lazy for bundled catalog import paths" , ( ) => {
201207 const source = readFileSync (
@@ -343,14 +349,20 @@ describe("official external plugin catalog", () => {
343349 id : "clawhub-official" ,
344350 } ;
345351 const signed = signedHostedCatalogFeed ( { feed } ) ;
346- const fetchImpl = vi . fn ( async ( ) => new Response ( signed . body , { status : 200 } ) ) ;
352+ const fetchImpl = vi . fn ( async ( _url , init ) => {
353+ const headers = new Headers ( init ?. headers ) ;
354+ expect ( headers . get ( "accept" ) ) . toBe ( "application/vnd.dsse+json" ) ;
355+ expect ( headers . has ( "if-modified-since" ) ) . toBe ( false ) ;
356+ return dsseResponse ( signed . body , { status : 200 } ) ;
357+ } ) ;
347358
348359 const result = await loadHostedCatalog ( {
349360 env : {
350361 [ DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_CLAWHUB_TRUSTED_KEY_ID_ENV ] : "acme-root" ,
351362 [ DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_CLAWHUB_TRUSTED_PUBLIC_KEY_ENV ] :
352363 signed . publicKeyPem ,
353364 } ,
365+ ifModifiedSince : "Mon, 22 Jun 2026 00:00:00 GMT" ,
354366 fetchImpl,
355367 snapshotStore : null ,
356368 } ) ;
@@ -381,7 +393,7 @@ describe("official external plugin catalog", () => {
381393 [ DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_CLAWHUB_TRUSTED_PUBLIC_KEY_ENV ] :
382394 signed . publicKeyPem ,
383395 } ,
384- fetchImpl : vi . fn ( async ( ) => new Response ( signed . body , { status : 200 } ) ) ,
396+ fetchImpl : vi . fn ( async ( ) => dsseResponse ( signed . body , { status : 200 } ) ) ,
385397 snapshotStore : null ,
386398 } ) ;
387399
@@ -426,7 +438,7 @@ describe("official external plugin catalog", () => {
426438 [ DEFAULT_OFFICIAL_EXTERNAL_PLUGIN_CATALOG_CLAWHUB_TRUSTED_PUBLIC_KEY_ENV ] :
427439 signed . publicKeyPem ,
428440 } ,
429- fetchImpl : vi . fn ( async ( ) => new Response ( signed . body , { status : 200 } ) ) ,
441+ fetchImpl : vi . fn ( async ( ) => dsseResponse ( signed . body , { status : 200 } ) ) ,
430442 snapshotStore : null ,
431443 } ) ;
432444
@@ -770,7 +782,7 @@ describe("official external plugin catalog", () => {
770782 const accepted = await loadHostedCatalog ( {
771783 feedProfile : "acme" ,
772784 catalogConfig,
773- fetchImpl : vi . fn ( async ( ) => new Response ( newer . body , { status : 200 } ) ) ,
785+ fetchImpl : vi . fn ( async ( ) => dsseResponse ( newer . body , { status : 200 } ) ) ,
774786 now : ( ) => new Date ( "2026-06-22T00:00:10.000Z" ) ,
775787 snapshotStore,
776788 } ) ;
@@ -790,7 +802,7 @@ describe("official external plugin catalog", () => {
790802 const rolledBack = await loadHostedCatalog ( {
791803 feedProfile : "acme" ,
792804 catalogConfig,
793- fetchImpl : vi . fn ( async ( ) => new Response ( older . body , { status : 200 } ) ) ,
805+ fetchImpl : vi . fn ( async ( ) => dsseResponse ( older . body , { status : 200 } ) ) ,
794806 now : ( ) => new Date ( "2026-06-22T00:00:11.000Z" ) ,
795807 snapshotStore,
796808 } ) ;
@@ -814,7 +826,7 @@ describe("official external plugin catalog", () => {
814826 const result = await loadHostedCatalog ( {
815827 feedProfile : "acme" ,
816828 catalogConfig : signedCatalogConfig ( malformed . publicKeyPem ) ,
817- fetchImpl : vi . fn ( async ( ) => new Response ( malformed . body , { status : 200 } ) ) ,
829+ fetchImpl : vi . fn ( async ( ) => dsseResponse ( malformed . body , { status : 200 } ) ) ,
818830 snapshotStore : createInMemoryHostedCatalogSnapshotStore ( ) ,
819831 } ) ;
820832
@@ -847,7 +859,7 @@ describe("official external plugin catalog", () => {
847859 const rejected = await loadHostedCatalog ( {
848860 feedProfile : "acme" ,
849861 catalogConfig : signedCatalogConfig ( lower . publicKeyPem ) ,
850- fetchImpl : vi . fn ( async ( ) => new Response ( lower . body , { status : 200 } ) ) ,
862+ fetchImpl : vi . fn ( async ( ) => dsseResponse ( lower . body , { status : 200 } ) ) ,
851863 snapshotStore,
852864 } ) ;
853865
@@ -857,7 +869,7 @@ describe("official external plugin catalog", () => {
857869 const result = await loadHostedCatalog ( {
858870 feedProfile : "acme" ,
859871 catalogConfig : signedCatalogConfig ( valid . publicKeyPem ) ,
860- fetchImpl : vi . fn ( async ( ) => new Response ( valid . body , { status : 200 } ) ) ,
872+ fetchImpl : vi . fn ( async ( ) => dsseResponse ( valid . body , { status : 200 } ) ) ,
861873 snapshotStore,
862874 } ) ;
863875
@@ -882,7 +894,7 @@ describe("official external plugin catalog", () => {
882894 const result = await loadHostedCatalog ( {
883895 feedProfile : "acme" ,
884896 catalogConfig : signedCatalogConfig ( candidate . publicKeyPem ) ,
885- fetchImpl : vi . fn ( async ( ) => new Response ( candidate . body , { status : 200 } ) ) ,
897+ fetchImpl : vi . fn ( async ( ) => dsseResponse ( candidate . body , { status : 200 } ) ) ,
886898 snapshotStore,
887899 } ) ;
888900
@@ -906,7 +918,7 @@ describe("official external plugin catalog", () => {
906918 const unsigned = await loadHostedCatalog ( {
907919 feedProfile : "acme" ,
908920 catalogConfig,
909- fetchImpl : vi . fn ( async ( ) => new Response ( unsignedBody , { status : 200 } ) ) ,
921+ fetchImpl : vi . fn ( async ( ) => dsseResponse ( unsignedBody , { status : 200 } ) ) ,
910922 snapshotStore : createInMemoryHostedCatalogSnapshotStore ( ) ,
911923 } ) ;
912924
@@ -956,6 +968,28 @@ describe("official external plugin catalog", () => {
956968 }
957969 } ) ;
958970
971+ it ( "fails closed when a signed feed response does not use the DSSE media type" , async ( ) => {
972+ const signed = signedHostedCatalogFeed ( {
973+ feed : hostedCatalogFeed ( { sequence : 8 , pluginName : "@openclaw/wrong-media-type" } ) ,
974+ } ) ;
975+ const result = await loadHostedCatalog ( {
976+ feedProfile : "acme" ,
977+ catalogConfig : signedCatalogConfig ( signed . publicKeyPem ) ,
978+ fetchImpl : vi . fn (
979+ async ( ) =>
980+ new Response ( signed . body , {
981+ status : 200 ,
982+ headers : { "content-type" : "application/json" } ,
983+ } ) ,
984+ ) ,
985+ snapshotStore : createInMemoryHostedCatalogSnapshotStore ( ) ,
986+ } ) ;
987+
988+ expect ( result . source ) . toBe ( "bundled-fallback" ) ;
989+ expect ( result . entries ) . toEqual ( [ ] ) ;
990+ expect ( result . error ) . toContain ( "must use application/vnd.dsse+json" ) ;
991+ } ) ;
992+
959993 it ( "rejects expired signed feeds and keeps expired snapshots visible but not installable" , async ( ) => {
960994 const feed = {
961995 ...hostedCatalogFeed ( {
@@ -984,8 +1018,8 @@ describe("official external plugin catalog", () => {
9841018 const seeded = await loadHostedCatalog ( {
9851019 feedProfile : "acme" ,
9861020 catalogConfig,
987- fetchImpl : vi . fn (
988- async ( ) => new Response ( signed . body , { status : 200 , headers : { etag : '"expiring"' } } ) ,
1021+ fetchImpl : vi . fn ( async ( ) =>
1022+ dsseResponse ( signed . body , { status : 200 , headers : { etag : '"expiring"' } } ) ,
9891023 ) ,
9901024 now : ( ) => new Date ( "2026-06-22T00:00:30.000Z" ) ,
9911025 snapshotStore,
@@ -998,7 +1032,7 @@ describe("official external plugin catalog", () => {
9981032 const expiredFresh = await loadHostedCatalog ( {
9991033 feedProfile : "acme" ,
10001034 catalogConfig,
1001- fetchImpl : vi . fn ( async ( ) => new Response ( signed . body , { status : 200 } ) ) ,
1035+ fetchImpl : vi . fn ( async ( ) => dsseResponse ( signed . body , { status : 200 } ) ) ,
10021036 now : ( ) => new Date ( "2026-06-22T00:01:01.000Z" ) ,
10031037 snapshotStore : null ,
10041038 } ) ;
@@ -1063,7 +1097,7 @@ describe("official external plugin catalog", () => {
10631097 const result = await loadHostedCatalog ( {
10641098 feedProfile : "acme" ,
10651099 catalogConfig : signedCatalogConfig ( signed . publicKeyPem ) ,
1066- fetchImpl : vi . fn ( async ( ) => new Response ( signed . body , { status : 200 } ) ) ,
1100+ fetchImpl : vi . fn ( async ( ) => dsseResponse ( signed . body , { status : 200 } ) ) ,
10671101 now : ( ) => new Date ( "2026-06-22T00:00:30.000Z" ) ,
10681102 snapshotStore : null ,
10691103 } ) ;
@@ -1116,7 +1150,7 @@ describe("official external plugin catalog", () => {
11161150 const updated = await loadHostedCatalog ( {
11171151 feedProfile : "acme" ,
11181152 catalogConfig,
1119- fetchImpl : vi . fn ( async ( ) => new Response ( newer . body , { status : 200 } ) ) ,
1153+ fetchImpl : vi . fn ( async ( ) => dsseResponse ( newer . body , { status : 200 } ) ) ,
11201154 now : ( ) => new Date ( "2026-06-22T00:00:30.000Z" ) ,
11211155 snapshotStore,
11221156 } ) ;
@@ -1167,7 +1201,7 @@ describe("official external plugin catalog", () => {
11671201 feedProfile : "acme" ,
11681202 feedUrl : "https://clawhub.ai/v1/feeds/plugins" ,
11691203 catalogConfig : signedCatalogConfig ( signed . publicKeyPem ) ,
1170- fetchImpl : vi . fn ( async ( ) => new Response ( unsignedBody , { status : 200 } ) ) ,
1204+ fetchImpl : vi . fn ( async ( ) => dsseResponse ( unsignedBody , { status : 200 } ) ) ,
11711205 snapshotStore : null ,
11721206 } ) ;
11731207
0 commit comments