mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-27 07:51:52 +00:00
- Replaced brittle regex-based sanitization with `path.resolve` relative to `packageRoot`. - Added `test/dashboard-path-traversal-fix.test.mjs` to verify robust protection against traversal attacks. - Verified that legitimate access to allowed paths is maintained. - Verified that access to restricted files (like package.json) is blocked. - Updated path handling to correctly handle absolute paths and URL-encoded characters. This change addresses a potential security vulnerability where the previous sanitization logic could be bypassed in certain environments or with specific edge cases. By using standard path resolution and explicitly checking against a canonical root, the implementation is more secure and maintainable. Co-authored-by: PR0M3TH3AN <[email protected]>
96 lines
3.7 KiB
JavaScript
96 lines
3.7 KiB
JavaScript
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import http from 'node:http';
|
|
import { cmdDashboard } from '../src/dashboard.mjs';
|
|
|
|
test('Dashboard Path Traversal Security Fix', async (t) => {
|
|
let testPort = 0;
|
|
let server;
|
|
|
|
t.beforeEach(async () => {
|
|
// Start dashboard on random port
|
|
server = await cmdDashboard(0);
|
|
testPort = server.address().port;
|
|
});
|
|
|
|
t.afterEach((done) => {
|
|
if (server) server.close(done);
|
|
else done();
|
|
});
|
|
|
|
function get(path) {
|
|
return new Promise((resolve, reject) => {
|
|
const req = http.get({
|
|
host: '127.0.0.1',
|
|
port: testPort,
|
|
path: path
|
|
}, (res) => {
|
|
// Consume data to avoid memory leaks
|
|
res.resume();
|
|
resolve(res);
|
|
});
|
|
req.on('error', reject);
|
|
});
|
|
}
|
|
|
|
// --- Valid Access ---
|
|
await t.test('allows access to valid dashboard index', async () => {
|
|
const res = await get('/dashboard/index.html');
|
|
assert.strictEqual(res.statusCode, 200, 'Should allow access to dashboard index');
|
|
});
|
|
|
|
// --- Traversal Attempts ---
|
|
const traversalPayloads = [
|
|
'/../../etc/passwd',
|
|
'/dashboard/../../etc/passwd',
|
|
'/dashboard/../../../etc/passwd',
|
|
'/../package.json',
|
|
'/dashboard/../package.json',
|
|
'/dashboard/%2e%2e/package.json', // URL Encoded ..
|
|
'/dashboard/%2e%2e%2fpackage.json', // URL Encoded ../
|
|
'/..%2fpackage.json',
|
|
'/%2e%2e%2fpackage.json',
|
|
'/dashboard/..%5cpackage.json', // Encoded backslash
|
|
'/dashboard/..%252fpackage.json', // Double encoded
|
|
'/dashboard/....//package.json', // Weird dots
|
|
'/dashboard/.../package.json', // Three dots
|
|
'//etc/passwd', // Double slash
|
|
'/dashboard//../../etc/passwd',
|
|
];
|
|
|
|
for (const payload of traversalPayloads) {
|
|
await t.test(`blocks traversal attempt: ${payload}`, async () => {
|
|
const res = await get(payload);
|
|
// Depending on how it's resolved, it might be 403 (Forbidden) or 404 (Not Found)
|
|
// If it escapes allowed paths, it should be 403.
|
|
// If it stays within allowed paths but file doesn't exist, 404.
|
|
// But we want to ensure it doesn't return 200 for sensitive files if they existed.
|
|
// Since we don't know if /etc/passwd exists on test environment, we assume 403 or 404 is safe.
|
|
// However, for package.json (which exists in root), it MUST be 403 because it's not in allowed paths.
|
|
|
|
// If the payload targets a file outside allowed paths (like package.json), it MUST be 403.
|
|
// If the payload targets a file that doesn't exist but is outside allowed paths, it MUST be 403.
|
|
|
|
// The current logic returns 403 if not in allowedPaths.
|
|
assert.notStrictEqual(res.statusCode, 200, `Should not return 200 for ${payload}`);
|
|
|
|
// If it targets package.json, we expect 403 because it exists but is restricted.
|
|
// EXCEPTION: If the payload uses encoded slashes (%2f) that are NOT decoded by URL parser,
|
|
// it is treated as a filename, not a separator. Thus it looks for a file with %2f in name,
|
|
// which doesn't exist -> 404. This is SAFE.
|
|
// So for package.json payloads, we accept 403 (blocked traversal) or 404 (failed traversal/not found).
|
|
if (payload.includes('package.json')) {
|
|
assert.ok([403, 404].includes(res.statusCode), `Should return 403 or 404 for ${payload}, got ${res.statusCode}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
// --- Allowed Traversal (within allowed scope) ---
|
|
await t.test('allows traversal within allowed paths', async () => {
|
|
// /dashboard/../dashboard/index.html -> /dashboard/index.html (Allowed)
|
|
const res = await get('/dashboard/../dashboard/index.html');
|
|
assert.strictEqual(res.statusCode, 200, 'Should allow traversal that stays within allowed paths');
|
|
});
|
|
});
|