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
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:
parent
3e9336ae67
commit
4f4139c47e
2 changed files with 123 additions and 0 deletions
|
|
@ -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"
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -445,6 +445,18 @@ async function handleScreenshotRequest(req: any, res: any) {
|
||||||
return;
|
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
|
// Validate css parameter
|
||||||
if (css && typeof css === 'string' && css.length > 5000) {
|
if (css && typeof css === 'string' && css.length > 5000) {
|
||||||
res.status(400).json({ error: "css: maximum 5000 characters allowed" });
|
res.status(400).json({ error: "css: maximum 5000 characters allowed" });
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue