fix: add userAgent validation (max 500 chars, no newlines) + add userAgent tests
Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Has been cancelled

- Route-level validation for userAgent length and newline injection
- 6 new userAgent tests (validation, passthrough, GET support)
- Fixes missing validation from previous commit
- TDD: tests verify both rejection (400) and acceptance paths
- Test suite: 425 → 431 tests
This commit is contained in:
SnapAPI Developer 2026-03-05 15:19:08 +01:00
parent 3e9336ae67
commit 4f4139c47e
2 changed files with 123 additions and 0 deletions

View file

@ -1086,4 +1086,115 @@ describe('Screenshot Route', () => {
)
})
})
describe('userAgent parameter', () => {
it('should return 400 when userAgent exceeds 500 characters', async () => {
const req = createMockRequest({
url: "https://example.com",
userAgent: "A".repeat(501)
})
const res = createMockResponse()
const handler = screenshotRouter.stack.find(layer =>
layer.route?.methods.post
)?.route.stack[0].handle
await handler(req, res, vi.fn())
expect(res.status).toHaveBeenCalledWith(400)
expect(res.json).toHaveBeenCalledWith({
error: "userAgent: maximum 500 characters allowed"
})
})
it('should return 400 when userAgent contains newline (\\n)', async () => {
const req = createMockRequest({
url: "https://example.com",
userAgent: "Mozilla/5.0\nX-Injected: true"
})
const res = createMockResponse()
const handler = screenshotRouter.stack.find(layer =>
layer.route?.methods.post
)?.route.stack[0].handle
await handler(req, res, vi.fn())
expect(res.status).toHaveBeenCalledWith(400)
expect(res.json).toHaveBeenCalledWith({
error: "userAgent: newlines are not allowed (HTTP header injection prevention)"
})
})
it('should return 400 when userAgent contains carriage return (\\r)', async () => {
const req = createMockRequest({
url: "https://example.com",
userAgent: "Mozilla/5.0\rX-Injected: true"
})
const res = createMockResponse()
const handler = screenshotRouter.stack.find(layer =>
layer.route?.methods.post
)?.route.stack[0].handle
await handler(req, res, vi.fn())
expect(res.status).toHaveBeenCalledWith(400)
expect(res.json).toHaveBeenCalledWith({
error: "userAgent: newlines are not allowed (HTTP header injection prevention)"
})
})
it('should accept valid userAgent at exactly 500 characters', async () => {
const req = createMockRequest({
url: "https://example.com",
userAgent: "A".repeat(500)
})
const res = createMockResponse()
const handler = screenshotRouter.stack.find(layer =>
layer.route?.methods.post
)?.route.stack[0].handle
await handler(req, res, vi.fn())
expect(res.status).not.toHaveBeenCalledWith(400)
})
it('should pass userAgent to takeScreenshot service', async () => {
const req = createMockRequest({
url: "https://example.com",
userAgent: "Mozilla/5.0 (compatible; SnapAPI/1.0)"
})
const res = createMockResponse()
const handler = screenshotRouter.stack.find(layer =>
layer.route?.methods.post
)?.route.stack[0].handle
await handler(req, res, vi.fn())
expect(mockTakeScreenshot).toHaveBeenCalledWith(
expect.objectContaining({
userAgent: "Mozilla/5.0 (compatible; SnapAPI/1.0)"
})
)
})
it('should accept userAgent via GET query parameter', async () => {
const req = createMockRequest({
url: "https://example.com",
userAgent: "CustomBot/2.0"
})
req.method = "GET"
req.query = req.body
const res = createMockResponse()
const handler = screenshotRouter.stack.find(layer =>
layer.route?.methods.get
)?.route.stack[0].handle
await handler(req, res, vi.fn())
expect(mockTakeScreenshot).toHaveBeenCalledWith(
expect.objectContaining({
userAgent: "CustomBot/2.0"
})
)
})
})
})

View file

@ -445,6 +445,18 @@ async function handleScreenshotRequest(req: any, res: any) {
return;
}
// Validate userAgent parameter
if (userAgent && typeof userAgent === 'string') {
if (userAgent.length > 500) {
res.status(400).json({ error: "userAgent: maximum 500 characters allowed" });
return;
}
if (/[\r\n]/.test(userAgent)) {
res.status(400).json({ error: "userAgent: newlines are not allowed (HTTP header injection prevention)" });
return;
}
}
// Validate css parameter
if (css && typeof css === 'string' && css.length > 5000) {
res.status(400).json({ error: "css: maximum 5000 characters allowed" });