diff --git a/e2e/session.test.ts b/e2e/session-launcher.test.ts similarity index 51% rename from e2e/session.test.ts rename to e2e/session-launcher.test.ts index 66d54954c..92c4c695c 100644 --- a/e2e/session.test.ts +++ b/e2e/session-launcher.test.ts @@ -7,22 +7,61 @@ import { } from './test-util'; import { test, expect } from '@playwright/test'; -test.describe('Sessions ', () => { +test.describe('NEO Sessions Launcher', () => { + test.beforeEach(async ({ page }) => { + await loginAsUser(page); + }); + const sessionName = 'e2e-test-session'; test('User can create session in NEO', async ({ page }) => { - await loginAsUser(page); await createSession(page, sessionName); await deleteSession(page, sessionName); }); + + test('Sensitive environment variables are cleared when the browser is reloaded.', async ({ + page, + }) => { + await navigateTo(page, 'session/start'); + await page + .getByRole('button', { name: '2 Environments & Resource' }) + .click(); + await page + .getByRole('button', { name: 'plus Add environment variables' }) + .click(); + await page.getByPlaceholder('Variable').fill('abc'); + await page.getByPlaceholder('Variable').press('Tab'); + await page.getByPlaceholder('Value').fill('123'); + await page + .getByRole('button', { name: 'plus Add environment variables' }) + .click(); + await page.locator('#envvars_1_variable').fill('password'); + await page.locator('#envvars_1_variable').press('Tab'); + await page.locator('#envvars_1_value').fill('hello'); + await page + .getByRole('button', { name: 'plus Add environment variables' }) + .click(); + await page.locator('#envvars_2_variable').fill('api_key'); + await page.locator('#envvars_2_variable').press('Tab'); + await page.locator('#envvars_2_value').fill('secret'); + await page.waitForTimeout(1000); // Wait for the form state to be saved as query param. + await page.reload(); + await expect( + page.locator('#envvars_1_value_help').getByText('Please enter a value.'), + ).toBeVisible(); + await expect( + page.locator('#envvars_2_value_help').getByText('Please enter a value.'), + ).toBeVisible(); + }); }); test.describe('Restrict resource policy and see resource warning message', () => { - test('superadmin to modify keypair resource policy', async ({ page }) => { + // TODO: fix this test + test.skip('superadmin to modify keypair resource policy', async ({ + page, + }) => { await loginAsAdmin(page); - // go to resource policy page await navigateTo(page, 'resource-policy'); - // modify resource limit (cpu, memory) to zero await page .getByRole('table') @@ -40,10 +79,8 @@ test.describe('Restrict resource policy and see resource warning message', () => await page.getByLabel('Memory(optional)').click(); await page.getByLabel('Memory(optional)').fill('0'); await page.getByRole('button', { name: 'OK' }).click(); - // go back to session page and see message in resource allocation section await navigateTo(page, 'session/start'); - await page.getByRole('button', { name: 'Next right' }).click(); const notEnoughCPUResourceMsg = await page .locator('#resource_cpu_help') @@ -53,7 +90,6 @@ test.describe('Restrict resource policy and see resource warning message', () => .getByText('Allocatable resources falls') .nth(1) .textContent(); - expect(notEnoughCPUResourceMsg).toEqual(notEnoughRAMResourceMsg); }); }); diff --git a/react/src/components/EnvVarFormList.test.tsx b/react/src/components/EnvVarFormList.test.tsx new file mode 100644 index 000000000..408dbe2d1 --- /dev/null +++ b/react/src/components/EnvVarFormList.test.tsx @@ -0,0 +1,35 @@ +// EnvVarFormList.test.tsx +import { sanitizeSensitiveEnv } from './EnvVarFormList'; + +describe('emptySensitiveEnv', () => { + it('should empty the value of sensitive environment variables', () => { + const envs = [ + { variable: 'SECRET_KEY', value: '12345' }, + { variable: 'API_KEY', value: 'abcdef' }, + { variable: 'NON_SENSITIVE', value: 'value' }, + ]; + + const result = sanitizeSensitiveEnv(envs); + + expect(result).toEqual([ + { variable: 'SECRET_KEY', value: '' }, + { variable: 'API_KEY', value: '' }, + { variable: 'NON_SENSITIVE', value: 'value' }, + ]); + }); + + it('should not change non-sensitive environment variables', () => { + const envs = [{ variable: 'NON_SENSITIVE', value: 'value' }]; + const result = sanitizeSensitiveEnv(envs); + + expect(result).toEqual([{ variable: 'NON_SENSITIVE', value: 'value' }]); + }); + + it('should handle an empty array', () => { + const envs: any[] = []; + + const result = sanitizeSensitiveEnv(envs); + + expect(result).toEqual([]); + }); +}); diff --git a/react/src/components/EnvVarFormList.tsx b/react/src/components/EnvVarFormList.tsx index e651a9a54..0286a3f8c 100644 --- a/react/src/components/EnvVarFormList.tsx +++ b/react/src/components/EnvVarFormList.tsx @@ -137,4 +137,40 @@ const EnvVarFormList: React.FC = ({ ); }; +const sensitivePatterns = [ + /AUTH/i, + /ACCESS/i, + /SECRET/i, + /_KEY/i, + /PASSWORD/i, + /PASSWD/i, + /PWD/i, + /TOKEN/i, + /PRIVATE/i, + /CREDENTIAL/i, + /JWT/i, + /KEYPAIR/i, + /CERTIFICATE/i, + /SSH/i, + /ENCRYPT/i, + /SIGNATURE/i, + /SALT/i, + /PIN/i, + /PASSPHRASE/i, + /OAUTH/i, +]; + +export function isSensitiveEnv(key: string) { + return sensitivePatterns.some((pattern) => pattern.test(key)); +} + +export function sanitizeSensitiveEnv(envs: EnvVarFormListValue[]) { + return envs.map((env) => { + if (env && isSensitiveEnv(env.variable)) { + return { ...env, value: '' }; + } + return env; + }); +} + export default EnvVarFormList; diff --git a/react/src/components/VFolderTableFormItem.tsx b/react/src/components/VFolderTableFormItem.tsx index 488bb169c..56cb3060f 100644 --- a/react/src/components/VFolderTableFormItem.tsx +++ b/react/src/components/VFolderTableFormItem.tsx @@ -19,6 +19,7 @@ interface VFolderTableFormItemProps extends Omit { export interface VFolderTableFormValues { mounts: string[]; vfoldersAliasMap: AliasMap; + autoMountedFolderNames?: string[]; } const VFolderTableFormItem: React.FC = ({ diff --git a/react/src/pages/SessionLauncherPage.tsx b/react/src/pages/SessionLauncherPage.tsx index 16fb23b1c..f6e1385be 100644 --- a/react/src/pages/SessionLauncherPage.tsx +++ b/react/src/pages/SessionLauncherPage.tsx @@ -3,6 +3,7 @@ import BAIIntervalText from '../components/BAIIntervalText'; import DatePickerISO from '../components/DatePickerISO'; import DoubleTag from '../components/DoubleTag'; import EnvVarFormList, { + sanitizeSensitiveEnv, EnvVarFormListValue, } from '../components/EnvVarFormList'; import Flex from '../components/Flex'; @@ -236,15 +237,22 @@ const SessionLauncherPage = () => { // console.log('syncFormToURLWithDebounce', form.getFieldsValue()); // To sync the latest form values to URL, // 'trailing' is set to true, and get the form values here." + const currentValue = form.getFieldsValue(); setQuery( { // formValues: form.getFieldsValue(), - formValues: _.omit( - form.getFieldsValue(), - ['environments.image'], - ['environments.customizedTag'], - ['autoMountedFolderNames'], - ['owner'], + formValues: _.extend( + _.omit( + form.getFieldsValue(), + ['environments.image'], + ['environments.customizedTag'], + ['autoMountedFolderNames'], + ['owner'], + ['envvars'], + ), + { + envvars: sanitizeSensitiveEnv(currentValue.envvars), + }, ), }, 'replaceIn',