Skip to content

Commit c3a6997

Browse files
committed
feat(security): wire withSigning on all proxy/embed handlers
All 10 server handlers now enforce HMAC signature verification: - google-static-maps-proxy, google-maps-geocode-proxy, gravatar-proxy - x-embed, bluesky-embed, instagram-embed - createImageProxyHandler (covers x-embed-image, bluesky-embed-image, instagram-embed-image, instagram-embed-asset) Registry entries updated with requiresSigning: true so the module enforces NUXT_SCRIPTS_PROXY_SECRET in production when any of these scripts are enabled.
1 parent d13999a commit c3a6997

8 files changed

Lines changed: 31 additions & 24 deletions

File tree

packages/script/src/registry.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -621,8 +621,8 @@ export async function registry(resolve?: (path: string) => Promise<string>): Pro
621621
envDefaults: { apiKey: '' },
622622
category: 'content',
623623
serverHandlers: [
624-
{ route: '/_scripts/proxy/google-static-maps', handler: './runtime/server/google-static-maps-proxy' },
625-
{ route: '/_scripts/proxy/google-maps-geocode', handler: './runtime/server/google-maps-geocode-proxy' },
624+
{ route: '/_scripts/proxy/google-static-maps', handler: './runtime/server/google-static-maps-proxy', requiresSigning: true },
625+
{ route: '/_scripts/proxy/google-maps-geocode', handler: './runtime/server/google-maps-geocode-proxy', requiresSigning: true },
626626
],
627627
}),
628628
def('blueskyEmbed', {
@@ -631,8 +631,8 @@ export async function registry(resolve?: (path: string) => Promise<string>): Pro
631631
label: 'Bluesky Embed',
632632
category: 'content',
633633
serverHandlers: [
634-
{ route: '/_scripts/embed/bluesky', handler: './runtime/server/bluesky-embed' },
635-
{ route: '/_scripts/embed/bluesky-image', handler: './runtime/server/bluesky-embed-image' },
634+
{ route: '/_scripts/embed/bluesky', handler: './runtime/server/bluesky-embed', requiresSigning: true },
635+
{ route: '/_scripts/embed/bluesky-image', handler: './runtime/server/bluesky-embed-image', requiresSigning: true },
636636
],
637637
}),
638638
def('instagramEmbed', {
@@ -641,9 +641,9 @@ export async function registry(resolve?: (path: string) => Promise<string>): Pro
641641
label: 'Instagram Embed',
642642
category: 'content',
643643
serverHandlers: [
644-
{ route: '/_scripts/embed/instagram', handler: './runtime/server/instagram-embed' },
645-
{ route: '/_scripts/embed/instagram-image', handler: './runtime/server/instagram-embed-image' },
646-
{ route: '/_scripts/embed/instagram-asset', handler: './runtime/server/instagram-embed-asset' },
644+
{ route: '/_scripts/embed/instagram', handler: './runtime/server/instagram-embed', requiresSigning: true },
645+
{ route: '/_scripts/embed/instagram-image', handler: './runtime/server/instagram-embed-image', requiresSigning: true },
646+
{ route: '/_scripts/embed/instagram-asset', handler: './runtime/server/instagram-embed-asset', requiresSigning: true },
647647
],
648648
}),
649649
def('xEmbed', {
@@ -652,8 +652,8 @@ export async function registry(resolve?: (path: string) => Promise<string>): Pro
652652
label: 'X Embed',
653653
category: 'content',
654654
serverHandlers: [
655-
{ route: '/_scripts/embed/x', handler: './runtime/server/x-embed' },
656-
{ route: '/_scripts/embed/x-image', handler: './runtime/server/x-embed-image' },
655+
{ route: '/_scripts/embed/x', handler: './runtime/server/x-embed', requiresSigning: true },
656+
{ route: '/_scripts/embed/x-image', handler: './runtime/server/x-embed-image', requiresSigning: true },
657657
],
658658
}),
659659
// support
@@ -757,7 +757,7 @@ export async function registry(resolve?: (path: string) => Promise<string>): Pro
757757
privacy: PRIVACY_IP_ONLY,
758758
},
759759
serverHandlers: [
760-
{ route: '/_scripts/proxy/gravatar', handler: './runtime/server/gravatar-proxy' },
760+
{ route: '/_scripts/proxy/gravatar', handler: './runtime/server/gravatar-proxy', requiresSigning: true },
761761
],
762762
}),
763763
])

packages/script/src/runtime/server/bluesky-embed.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createError, defineEventHandler, getQuery, setHeader } from 'h3'
22
import { $fetch } from 'ofetch'
3+
import { withSigning } from './utils/withSigning'
34

45
interface PostThreadResponse {
56
thread: {
@@ -22,7 +23,7 @@ interface PostThreadResponse {
2223

2324
const BSKY_POST_URL_RE = /^https:\/\/bsky\.app\/profile\/([^/]+)\/post\/([^/?]+)$/
2425

25-
export default defineEventHandler(async (event) => {
26+
export default withSigning(defineEventHandler(async (event) => {
2627
const query = getQuery(event)
2728
const postUrl = query.url as string
2829

@@ -92,4 +93,4 @@ export default defineEventHandler(async (event) => {
9293
setHeader(event, 'Cache-Control', 'public, max-age=600, s-maxage=600')
9394

9495
return post
95-
})
96+
}))

packages/script/src/runtime/server/google-maps-geocode-proxy.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { useRuntimeConfig } from '#imports'
22
import { createError, defineEventHandler, getQuery, setHeader } from 'h3'
33
import { $fetch } from 'ofetch'
44
import { withQuery } from 'ufo'
5+
import { withSigning } from './utils/withSigning'
56

6-
export default defineEventHandler(async (event) => {
7+
export default withSigning(defineEventHandler(async (event) => {
78
const runtimeConfig = useRuntimeConfig()
89
const privateConfig = (runtimeConfig['nuxt-scripts'] as any)?.googleMapsGeocodeProxy
910

@@ -38,4 +39,4 @@ export default defineEventHandler(async (event) => {
3839
setHeader(event, 'Cache-Control', 'public, max-age=86400, s-maxage=86400')
3940

4041
return data
41-
})
42+
}))

packages/script/src/runtime/server/google-static-maps-proxy.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { useRuntimeConfig } from '#imports'
22
import { createError, defineEventHandler, getQuery, setHeader } from 'h3'
33
import { $fetch } from 'ofetch'
44
import { withQuery } from 'ufo'
5+
import { withSigning } from './utils/withSigning'
56

6-
export default defineEventHandler(async (event) => {
7+
export default withSigning(defineEventHandler(async (event) => {
78
const runtimeConfig = useRuntimeConfig()
89
const publicConfig = (runtimeConfig.public['nuxt-scripts'] as any)?.googleStaticMapsProxy
910
const privateConfig = (runtimeConfig['nuxt-scripts'] as any)?.googleStaticMapsProxy
@@ -51,4 +52,4 @@ export default defineEventHandler(async (event) => {
5152
setHeader(event, 'Vary', 'Accept-Encoding')
5253

5354
return response._data
54-
})
55+
}))

packages/script/src/runtime/server/gravatar-proxy.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { useRuntimeConfig } from '#imports'
22
import { createError, defineEventHandler, getQuery, setHeader } from 'h3'
33
import { $fetch } from 'ofetch'
44
import { withQuery } from 'ufo'
5+
import { withSigning } from './utils/withSigning'
56

6-
export default defineEventHandler(async (event) => {
7+
export default withSigning(defineEventHandler(async (event) => {
78
const runtimeConfig = useRuntimeConfig()
89
const proxyConfig = (runtimeConfig.public['nuxt-scripts'] as any)?.gravatarProxy
910

@@ -55,4 +56,4 @@ export default defineEventHandler(async (event) => {
5556
setHeader(event, 'Vary', 'Accept-Encoding')
5657

5758
return response._data
58-
})
59+
}))

packages/script/src/runtime/server/instagram-embed.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createError, defineEventHandler, getQuery, setHeader } from 'h3'
22
import { $fetch } from 'ofetch'
33
import { ELEMENT_NODE, parse, renderSync, TEXT_NODE, walkSync } from 'ultrahtml'
4+
import { withSigning } from './utils/withSigning'
45

56
export const RSRC_RE = /url\(\/rsrc\.php([^)]+)\)/g
67
export const AMP_RE = /&amp;/g
@@ -186,7 +187,7 @@ function extractBlock(css: string, openBrace: number): { content: string, end: n
186187
return null
187188
}
188189

189-
export default defineEventHandler(async (event) => {
190+
export default withSigning(defineEventHandler(async (event) => {
190191
// Derive the scripts prefix from the handler's own route path.
191192
// The route is registered as `<prefix>/embed/instagram`, so strip `/embed/instagram`.
192193
const handlerPath = event.path?.split('?')[0] || ''
@@ -331,4 +332,4 @@ export default defineEventHandler(async (event) => {
331332
setHeader(event, 'Cache-Control', 'public, max-age=600, s-maxage=600')
332333

333334
return result
334-
})
335+
}))

packages/script/src/runtime/server/utils/image-proxy.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createError, defineEventHandler, getQuery, setHeader } from 'h3'
22
import { $fetch } from 'ofetch'
3+
import { withSigning } from './withSigning'
34

45
const AMP_RE = /&amp;/g
56

@@ -25,7 +26,7 @@ export function createImageProxyHandler(config: ImageProxyConfig) {
2526
decodeAmpersands = false,
2627
} = config
2728

28-
return defineEventHandler(async (event) => {
29+
return withSigning(defineEventHandler(async (event) => {
2930
const query = getQuery(event)
3031
let url = query.url as string
3132

@@ -95,5 +96,5 @@ export function createImageProxyHandler(config: ImageProxyConfig) {
9596
setHeader(event, 'Cache-Control', `public, max-age=${cacheMaxAge}, s-maxage=${cacheMaxAge}`)
9697

9798
return response._data
98-
})
99+
}))
99100
}

packages/script/src/runtime/server/x-embed.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createError, defineEventHandler, getQuery, setHeader } from 'h3'
22
import { $fetch } from 'ofetch'
3+
import { withSigning } from './utils/withSigning'
34

45
interface TweetData {
56
id_str: string
@@ -45,7 +46,7 @@ interface TweetData {
4546

4647
const TWEET_ID_RE = /^\d+$/
4748

48-
export default defineEventHandler(async (event) => {
49+
export default withSigning(defineEventHandler(async (event) => {
4950
const query = getQuery(event)
5051
const tweetId = query.id as string
5152

@@ -81,4 +82,4 @@ export default defineEventHandler(async (event) => {
8182
setHeader(event, 'Cache-Control', 'public, max-age=600, s-maxage=600')
8283

8384
return tweetData
84-
})
85+
}))

0 commit comments

Comments
 (0)