Skip to content

Commit a194061

Browse files
author
Peter Bengtsson
authored
Handle invalid headers (#49524)
1 parent 99c927c commit a194061

3 files changed

Lines changed: 49 additions & 0 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import statsd from '#src/observability/lib/statsd.js'
2+
import { errorCacheControl } from '#src/frame/middleware/cache-control.js'
3+
4+
const STATSD_KEY = 'middleware.handle_invalid_headers'
5+
6+
const INVALID_HEADER_KEYS = [
7+
// Next.js will pick this up and override the status code.
8+
// We don't want that to happen because `x-invoke-status: 203` can
9+
// trigger the CDN to cache it.
10+
// It can also trigger a 500 error because the header is not used
11+
// correctly.
12+
'x-invoke-status',
13+
]
14+
15+
export default function handleInvalidNextPaths(req, res, next) {
16+
const header = INVALID_HEADER_KEYS.find((key) => req.headers[key])
17+
if (header) {
18+
// This way you can't hammer the backend with invalid requests.
19+
// Since the CDN will cache based on the status code not being one
20+
// of success, we don't have to worry about this being cached when
21+
// the URL is the same but the headers *not invalid*.
22+
errorCacheControl(res)
23+
24+
const tags = [`ip:${req.ip}`, `path:${req.path}`, `header:${header}`]
25+
statsd.increment(STATSD_KEY, 1, tags)
26+
27+
return res.status(400).type('text').send('Invalid request headers')
28+
}
29+
30+
return next()
31+
}

src/shielding/middleware/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import handleInvalidPaths from './handle-invalid-paths.js'
55
import handleOldNextDataPaths from './handle-old-next-data-paths.js'
66
import handleInvalidQuerystringValues from './handle-invalid-query-string-values.js'
77
import handleInvalidNextPaths from './handle-invalid-nextjs-paths.js'
8+
import handleInvalidHeaders from './handle-invalid-headers.js'
89
import rateLimit from './rate-limit.js'
910

1011
const router = express.Router()
@@ -15,5 +16,6 @@ router.use(handleInvalidPaths)
1516
router.use(handleOldNextDataPaths)
1617
router.use(handleInvalidQuerystringValues)
1718
router.use(handleInvalidNextPaths)
19+
router.use(handleInvalidHeaders)
1820

1921
export default router
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { get } from '#src/tests/helpers/e2etest.js'
2+
3+
describe('invalid headers', () => {
4+
test('400 if containing x-invoke-status (instead of redirecting)', async () => {
5+
const res = await get('/', { headers: { 'x-invoke-status': '203' } })
6+
expect(res.statusCode).toBe(400)
7+
expect(res.headers['cache-control']).toMatch('public')
8+
expect(res.headers['cache-control']).toMatch(/max-age=[1-9]/)
9+
})
10+
test('400 if containing x-invoke-status (instead of 200)', async () => {
11+
const res = await get('/en', { headers: { 'x-invoke-status': '203' } })
12+
expect(res.statusCode).toBe(400)
13+
expect(res.headers['cache-control']).toMatch('public')
14+
expect(res.headers['cache-control']).toMatch(/max-age=[1-9]/)
15+
})
16+
})

0 commit comments

Comments
 (0)