Files
TORCH/test/dashboard-upload-security.test.mjs
T

106 lines
3.2 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 Upload Security', async (t) => {
let server;
let testPort;
t.beforeEach(async () => {
// Start server on a random port (0 lets OS choose)
server = await cmdDashboard(0);
testPort = server.address().port;
});
t.afterEach((done) => {
if (server) {
server.close(done);
} else {
done();
}
});
function post(path, body = '', headers = {}) {
return new Promise((resolve, reject) => {
const options = {
host: '127.0.0.1',
port: testPort,
path: path,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(body),
...headers,
},
};
const req = http.request(options, (res) => {
// Consume response data to free up memory
res.resume();
resolve({
statusCode: res.statusCode,
headers: res.headers,
});
});
req.on('error', reject);
req.write(body);
req.end();
});
}
await t.test('POST /upload returns 403 (Forbidden) - not in allowlist', async () => {
const res = await post('/upload', 'dummy data');
assert.strictEqual(res.statusCode, 403);
});
await t.test('POST /api/upload returns 403 (Forbidden) - not in allowlist', async () => {
const res = await post('/api/upload', 'dummy data');
assert.strictEqual(res.statusCode, 403);
});
await t.test('POST /dashboard/upload returns 404 (Not Found) - allowed path but file missing', async () => {
const res = await post('/dashboard/upload', 'dummy data');
assert.strictEqual(res.statusCode, 404);
});
await t.test('POST /content returns 403 (Forbidden) - not in allowlist', async () => {
const res = await post('/content', 'dummy data');
assert.strictEqual(res.statusCode, 403);
});
await t.test('POST / returns 302 (Redirect)', async () => {
// The server redirects / to /dashboard/ regardless of method
const res = await post('/', 'dummy data');
assert.strictEqual(res.statusCode, 302);
assert.strictEqual(res.headers.location, '/dashboard/');
});
await t.test('POST with multipart/form-data returns 403', async () => {
const boundary = '--------------------------367946266497677346366835';
const body = [
`--${boundary}`,
'Content-Disposition: form-data; name="file"; filename="test.txt"',
'Content-Type: text/plain',
'',
'This is a test file content.',
`--${boundary}--`,
''
].join('\r\n');
const res = await post('/upload', body, {
'Content-Type': `multipart/form-data; boundary=${boundary}`,
});
assert.strictEqual(res.statusCode, 403);
});
await t.test('POST /torch-config.json (readonly endpoint) returns 200 but ignores body', async () => {
// The handler for /torch-config.json does not check method, so it returns 200 with the config.
// This confirms no upload/write logic exists there.
const res = await post('/torch-config.json', '{"malicious":"config"}');
assert.strictEqual(res.statusCode, 200);
});
});