import request from 'supertest';
import { cleanupApplicationAndAppRegistration } from 'test/integration/metadata/suites/application/utils/cleanup-application-and-app-registration.util';
import { setupApplicationForSync } from 'test/integration/metadata/suites/application/utils/setup-application-for-sync.util';
import { uploadApplicationFile } from 'test/integration/metadata/suites/application/utils/upload-application-file.util';
import { expectOneNotInternalServerErrorHttpResponseSnapshot } from 'test/integration/utils/expect-one-not-internal-server-error-http-response-snapshot.util';
import {
type EachTestingContext,
eachTestingContextFilter,
} from 'twenty-shared/testing';
import { v4 as uuidv4 } from 'uuid';
import { SEED_APPLE_WORKSPACE_ID } from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant';
const TEST_APP_UID = uuidv4();
const TEST_WORKSPACE_ID = SEED_APPLE_WORKSPACE_ID;
const UNKNOWN_WORKSPACE_ID = uuidv4();
const UNKNOWN_APPLICATION_ID = uuidv4();
const PUBLIC_ASSET_PATH = 'assets/logo.svg';
const PUBLIC_ASSET_CONTENT = '';
const PERSISTED_PUBLIC_ASSET_CONTENT = '';
const PUBLIC_ASSET_CONTENT_TYPE = 'image/svg+xml';
type FailingCase = {
buildUrl: (validApplicationId: string) => string;
};
const FAILING_CASES: EachTestingContext[] = [
{
title: 'when path contains a leading parent-directory segment (..)',
context: {
buildUrl: (applicationId) =>
`/public-assets/${TEST_WORKSPACE_ID}/${applicationId}/..`,
},
},
{
title: 'when path contains multiple upward traversal segments (../../)',
context: {
buildUrl: (applicationId) =>
`/public-assets/${TEST_WORKSPACE_ID}/${applicationId}/../../sensitive-file`,
},
},
{
title: 'when path attempts to escape into another file folder',
context: {
buildUrl: (applicationId) =>
`/public-assets/${TEST_WORKSPACE_ID}/${applicationId}/../workflow/secret.json`,
},
},
{
title: 'when path contains URL-encoded backslash traversal',
context: {
buildUrl: (applicationId) =>
`/public-assets/${TEST_WORKSPACE_ID}/${applicationId}/..%5C..%5Cetc%5Cpasswd`,
},
},
{
title: 'when the requested asset does not exist',
context: {
buildUrl: (applicationId) =>
`/public-assets/${TEST_WORKSPACE_ID}/${applicationId}/assets/does-not-exist.svg`,
},
},
{
title: 'when the workspaceId does not match any workspace',
context: {
buildUrl: (applicationId) =>
`/public-assets/${UNKNOWN_WORKSPACE_ID}/${applicationId}/${PUBLIC_ASSET_PATH}`,
},
},
{
title: 'when the applicationId does not match any application',
context: {
buildUrl: () =>
`/public-assets/${TEST_WORKSPACE_ID}/${UNKNOWN_APPLICATION_ID}/${PUBLIC_ASSET_PATH}`,
},
},
];
describe('Public assets controller download should fail', () => {
let applicationId: string;
beforeAll(async () => {
await setupApplicationForSync({
applicationUniversalIdentifier: TEST_APP_UID,
name: 'Test Public Assets Download Failure App',
description: 'App for testing failing public-assets controller downloads',
sourcePath: 'test-public-assets-download-failure',
});
const [{ id }] = await globalThis.testDataSource.query(
`SELECT id FROM core."application" WHERE "universalIdentifier" = $1`,
[TEST_APP_UID],
);
applicationId = id;
jest.useRealTimers();
await uploadApplicationFile({
applicationUniversalIdentifier: TEST_APP_UID,
fileFolder: 'PublicAsset',
filePath: PUBLIC_ASSET_PATH,
fileBuffer: Buffer.from(PUBLIC_ASSET_CONTENT),
filename: 'logo.svg',
contentType: PUBLIC_ASSET_CONTENT_TYPE,
expectToFail: false,
});
jest.useFakeTimers();
}, 60000);
afterAll(async () => {
await cleanupApplicationAndAppRegistration({
applicationUniversalIdentifier: TEST_APP_UID,
});
});
it.each(eachTestingContextFilter(FAILING_CASES))(
'$title',
async ({ context }) => {
jest.useRealTimers();
const response = await request(global.app.getHttpServer()).get(
context.buildUrl(applicationId),
);
jest.useFakeTimers();
// The legitimate asset content must never leak through a failure path.
expect(response.text).not.toContain(PERSISTED_PUBLIC_ASSET_CONTENT);
expectOneNotInternalServerErrorHttpResponseSnapshot({
status: response.status,
body: response.body,
});
},
30000,
);
});