Validate file IDs for correctness (#7067)

* Validate file IDs for correctness

* Add release notes
This commit is contained in:
Julian Dominguez-Schatz
2026-02-24 10:32:50 -05:00
committed by GitHub
parent a1e0b3f45d
commit 18072e1d8b
3 changed files with 42 additions and 0 deletions

View File

@@ -366,6 +366,19 @@ describe('/upload-user-file', () => {
expect(res.text).toBe('fileId is required');
});
it('returns 400 for invalid fileId format', async () => {
const res = await request(app)
.post('/upload-user-file')
.set('Content-Type', 'application/encrypted-file')
.set('x-actual-token', 'valid-token')
.set('x-actual-name', 'test-file')
.set('x-actual-file-id', 'budget@2026')
.send(Buffer.from('file content'));
expect(res.statusCode).toEqual(400);
expect(res.text).toBe('invalid fileId');
});
it('uploads a new file successfully', async () => {
const fileId = crypto.randomBytes(16).toString('hex');
const fileName = 'test-file.txt';
@@ -670,6 +683,16 @@ describe('/download-user-file', () => {
expect(res.text).toBe('User or file not found');
});
it('returns 400 for invalid fileId format', async () => {
const res = await request(app)
.get('/download-user-file')
.set('x-actual-token', 'valid-token')
.set('x-actual-file-id', 'budget@2026');
expect(res.statusCode).toEqual(400);
expect(res.text).toBe('invalid fileId');
});
it('returns 500 error if the file does not exist on the filesystem', async () => {
getAccountDb().mutate(
'INSERT INTO files (id, deleted) VALUES (?, FALSE)',

View File

@@ -49,11 +49,16 @@ app.use(express.json({ limit: `${config.get('upload.fileSizeLimitMB')}mb` }));
export { app as handlers };
const OK_RESPONSE = { status: 'ok' };
const FILE_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
function boolToInt(deleted) {
return deleted ? 1 : 0;
}
function isValidFileId(fileId: unknown): fileId is string {
return typeof fileId === 'string' && FILE_ID_PATTERN.test(fileId);
}
const verifyFileExists = (fileId, filesService, res, errorObject) => {
try {
return filesService.get(fileId);
@@ -256,6 +261,10 @@ app.post('/upload-user-file', async (req, res) => {
res.status(400).send('fileId is required');
return;
}
if (!isValidFileId(fileId)) {
res.status(400).send('invalid fileId');
return;
}
let groupId = req.headers['x-actual-group-id'] || null;
const encryptMeta = req.headers['x-actual-encrypt-meta'] || null;
@@ -352,6 +361,10 @@ app.get('/download-user-file', async (req, res) => {
res.status(400).send('Single file ID is required');
return;
}
if (!isValidFileId(fileId)) {
res.status(400).send('invalid fileId');
return;
}
const filesService = new FilesService(getAccountDb());
const file = verifyFileExists(