Potential fix for code scanning alert no. 173: Uncontrolled data used in path expression (#14924)

Potential fix for
[https://github.com/twentyhq/twenty/security/code-scanning/173](https://github.com/twentyhq/twenty/security/code-scanning/173)

To fix this vulnerability, ensure that any file path constructed from
untrusted user input is strictly confined to the intended storage
directory. This is best done through path normalization and validation.
Before reading the file, the following steps should be taken:
1. **Resolve the file path**: Use `path.resolve` to normalize the
resulting path, removing any ".." segments or symbolic links.
2. **Check the parent directory**: Ensure that the resolved path starts
with the intended storage directory (`this.options.storagePath`).
3. **Throw an exception**: If the resolved path does not start with the
storage root, throw an exception or otherwise deny the operation.

This check should be placed in all methods that build a file path from
user input in `local.driver.ts`, with at least the `read` method
addressed. The index for this fix will be on lines that construct and
use `filePath`, specifically in the `read` method.

You will need to:
- Import `realpathSync` from `fs` (standard library).
- Add safe path resolution and containment check around file access.

---


_Suggested fixes powered by Copilot Autofix. Review carefully before
merging._

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
This commit is contained in:
Antoine Moreaux
2025-10-07 14:33:34 +02:00
committed by GitHub
parent f935dd44d7
commit f8f113b82e
@@ -1,4 +1,4 @@
import { createReadStream, existsSync } from 'fs';
import { createReadStream, existsSync, realpathSync } from 'fs';
import * as fs from 'fs/promises';
import path, { dirname, join } from 'path';
import { type Readable } from 'stream';
@@ -78,18 +78,30 @@ export class LocalDriver implements StorageDriver {
folderPath: string;
filename: string;
}): Promise<Readable> {
const filePath = join(
const joinedPath = join(
`${this.options.storagePath}/`,
params.folderPath,
params.filename,
);
let filePath: string;
if (!existsSync(filePath)) {
try {
filePath = realpathSync(path.resolve(joinedPath));
} catch {
throw new FileStorageException(
'File not found',
FileStorageExceptionCode.FILE_NOT_FOUND,
);
}
const storageRoot = realpathSync(path.resolve(this.options.storagePath));
if (!filePath.startsWith(storageRoot + path.sep)) {
// Prevent directory traversal
throw new FileStorageException(
'Access denied',
FileStorageExceptionCode.FILE_NOT_FOUND,
);
}
try {
return createReadStream(filePath);