@@ -17,7 +17,11 @@ function directivePermits(directive: string, imageUrl: string): boolean {
1717 if ( ! source . startsWith ( "http" ) ) return false ;
1818 const parsed = new URL ( source ) ;
1919 if ( parsed . protocol !== url . protocol || parsed . host !== url . host ) return false ;
20- return parsed . pathname === "/" || parsed . pathname === url . pathname ;
20+ // CSP path matching: a source path ending in "/" matches by prefix, otherwise it
21+ // must match exactly. The query string is never part of the match.
22+ return parsed . pathname . endsWith ( "/" )
23+ ? url . pathname . startsWith ( parsed . pathname )
24+ : parsed . pathname === url . pathname ;
2125 } ) ;
2226}
2327
@@ -104,9 +108,9 @@ describe("parseCspImageOrigins", () => {
104108} ) ;
105109
106110describe ( "buildImgSrcDirective" , ( ) => {
107- it ( "is self, data, blob, the SSO avatar hosts and the favicon endpoint by default" , ( ) => {
111+ it ( "is self, data, blob, the SSO avatar hosts, the favicon endpoints and the changelog by default" , ( ) => {
108112 expect ( buildImgSrcDirective ( ) ) . toBe (
109- "img-src 'self' data: blob: https://avatars.githubusercontent.com https://lh3.googleusercontent.com https://www.google.com/s2/favicons"
113+ "img-src 'self' data: blob: https://avatars.githubusercontent.com https://lh3.googleusercontent.com https://www.google.com/s2/favicons https://t0.gstatic.com/faviconV2 https://t1.gstatic.com/faviconV2 https://t2.gstatic.com/faviconV2 https://t3.gstatic.com/faviconV2 https://trigger.dev/changelog/ "
110114 ) ;
111115 } ) ;
112116
@@ -120,6 +124,29 @@ describe("buildImgSrcDirective", () => {
120124 ) ;
121125 } ) ;
122126
127+ it ( "permits the gstatic shard the favicon endpoint redirects to" , ( ) => {
128+ expect (
129+ directivePermits (
130+ buildImgSrcDirective ( ) ,
131+ "https://t2.gstatic.com/faviconV2?url=https://example.com&size=128"
132+ )
133+ ) . toBe ( true ) ;
134+ } ) ;
135+
136+ it ( "permits nothing else on a gstatic shard, and no shard we did not list" , ( ) => {
137+ const directive = buildImgSrcDirective ( ) ;
138+ expect ( directivePermits ( directive , "https://t2.gstatic.com/beacon.png" ) ) . toBe ( false ) ;
139+ expect ( directivePermits ( directive , "https://t9.gstatic.com/faviconV2" ) ) . toBe ( false ) ;
140+ } ) ;
141+
142+ it ( "permits changelog images by path prefix, and nothing else on our domain" , ( ) => {
143+ const directive = buildImgSrcDirective ( ) ;
144+ expect ( directivePermits ( directive , "https://trigger.dev/changelog/some-post/image.png" ) ) . toBe (
145+ true
146+ ) ;
147+ expect ( directivePermits ( directive , "https://trigger.dev/anything.png" ) ) . toBe ( false ) ;
148+ } ) ;
149+
123150 it ( "permits both OAuth avatar hosts" , ( ) => {
124151 const directive = buildImgSrcDirective ( ) ;
125152 expect ( directivePermits ( directive , "https://avatars.githubusercontent.com/u/1?v=4" ) ) . toBe ( true ) ;
0 commit comments