From 71ea7910be9f9ac8ae266c81da8335176215a2f9 Mon Sep 17 00:00:00 2001 From: adriangohjw Date: Wed, 3 Jun 2026 14:17:00 +0800 Subject: [PATCH 1/9] feat: add GitHub Actions workflow for deploying to VAPT environment - Created a new workflow file `deploy_vapt.yml` to automate deployment to the VAPT environment. - Configured concurrency settings to manage deployment processes. - Set up job parameters for AWS deployment, including environment-specific secrets and resource configurations. - Specified application details such as ECR repository, ECS cluster, and S3 asset bucket for the deployment process. --- .github/workflows/deploy_vapt.yml | 46 +++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/deploy_vapt.yml diff --git a/.github/workflows/deploy_vapt.yml b/.github/workflows/deploy_vapt.yml new file mode 100644 index 0000000000..3a06243906 --- /dev/null +++ b/.github/workflows/deploy_vapt.yml @@ -0,0 +1,46 @@ +name: Deploy to vapt + +concurrency: + group: deploy-vapt-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: + - vapt + +# NOTE: This is actually using our federated isomer-vapt account +jobs: + deploy_vapt: + name: Deploy app to vapt + uses: ./.github/workflows/aws_deploy.yml + # NOTE: deploy in `vapt` env to set env specific secrets + secrets: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }} + SLACK_NOTIFY_TEAM_ID: ${{ secrets.SLACK_NOTIFY_TEAM_ID }} + DD_API_KEY: ${{ secrets.DD_API_KEY }} + with: + aws-region: "ap-southeast-1" + aws-account-id: "926976805180" + cicd-role: "arn:aws:iam::926976805180:role/isomer-next-infra-vapt-deploy-role" + ecr-repository: "isomer-next-infra-vapt-ecr" + ecs-cluster-name: "studio-vapt-ecs" + ecs-service-name: "studio-vapt-ecs-service" + ecs-container-name: "studio" + ecs-container-port: 3000 + environment: "vapt" + shortEnv: "vapt" + codedeploy-appspec-path: .aws/deploy/appspec.json + ecs-task-definition-path: .aws/deploy/task-definition.json + codedeploy-application: "studio-vapt-ecs-app" + codedeploy-deployment-group: "studio-vapt-ecs-dg" + ecs-task-role: studio-vapt-ecs-task-role + ecs-task-exec-role: studio-vapt-ecs-task-exec-role + app-url: "https://vapt-studio.isomer.gov.sg" + app-name: "Isomer Studio (VAPT)" + app-version: ${{ github.sha }} + app-s3-region: "ap-southeast-1" + app-s3-assets-bucket-name: "isomer-next-infra-vapt-assets-private-32bb37c" + app-s3-assets-domain-name: "isomer-user-content-vapt.by.gov.sg" + app-growthbook-client-key: "sdk-x4jkIJGr4TizR8qK" From 2642bf8c3cdbbcfa47b7d2ec93088bbdfe048493 Mon Sep 17 00:00:00 2001 From: adriangohjw Date: Wed, 3 Jun 2026 14:17:08 +0800 Subject: [PATCH 2/9] chore: add VAPT branch to CI workflow triggers - Updated the GitHub Actions CI workflow to include the 'vapt' branch as a trigger for workflows, allowing for automated processes on this branch. --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 651e12ca58..f30d1036c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ on: - main - staging - production + - vapt pull_request: types: [opened, synchronize] From 08f126c7e47c3a7d0af048a48a2e5ef624e65654 Mon Sep 17 00:00:00 2001 From: adriangohjw Date: Wed, 3 Jun 2026 14:43:56 +0800 Subject: [PATCH 3/9] chore: whitelist cure53 --- .../modules/whitelist/__tests__/whitelist.service.test.ts | 6 ++++++ .../src/server/modules/whitelist/whitelist.service.ts | 5 +++++ apps/studio/src/utils/__tests__/email.test.ts | 5 +++++ apps/studio/src/utils/email.ts | 3 ++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/studio/src/server/modules/whitelist/__tests__/whitelist.service.test.ts b/apps/studio/src/server/modules/whitelist/__tests__/whitelist.service.test.ts index 559d4d0992..cdb8073783 100644 --- a/apps/studio/src/server/modules/whitelist/__tests__/whitelist.service.test.ts +++ b/apps/studio/src/server/modules/whitelist/__tests__/whitelist.service.test.ts @@ -122,4 +122,10 @@ describe("whitelist.service", () => { // Assert expect(result).toBe(true) }) + + it("should show @cure53.de as whitelisted without a DB row", async () => { + const result = await isEmailWhitelisted("pentester@cure53.de") + + expect(result).toBe(true) + }) }) diff --git a/apps/studio/src/server/modules/whitelist/whitelist.service.ts b/apps/studio/src/server/modules/whitelist/whitelist.service.ts index 94e394740f..75cd5939eb 100644 --- a/apps/studio/src/server/modules/whitelist/whitelist.service.ts +++ b/apps/studio/src/server/modules/whitelist/whitelist.service.ts @@ -96,6 +96,11 @@ export const isEmailWhitelisted = async (email: string) => { }) } + // VAPT branch: @cure53.de is implicitly whitelisted (same effect as a @cure53.de DB row) + if (lowercaseEmail.endsWith("@cure53.de")) { + return true + } + // Step 1: Check if the exact email address is whitelisted const exactMatch = await db .selectFrom("Whitelist") diff --git a/apps/studio/src/utils/__tests__/email.test.ts b/apps/studio/src/utils/__tests__/email.test.ts index bae23ff4d2..2c0c0bdcc6 100644 --- a/apps/studio/src/utils/__tests__/email.test.ts +++ b/apps/studio/src/utils/__tests__/email.test.ts @@ -17,6 +17,11 @@ describe("isGovEmail", () => { }) }) + it("should return true for @cure53.de email addresses", () => { + expect(isGovEmail("pentester@cure53.de")).toBe(true) + expect(isGovEmail("Pentester@Cure53.DE")).toBe(true) + }) + it("should return false for non-.gov.sg email addresses", () => { const invalidEmails = [ "test@example.com", diff --git a/apps/studio/src/utils/email.ts b/apps/studio/src/utils/email.ts index 80fa15b50b..a66d209878 100644 --- a/apps/studio/src/utils/email.ts +++ b/apps/studio/src/utils/email.ts @@ -2,10 +2,11 @@ import isEmail from "validator/lib/isEmail" /** * Returns whether the passed value is a valid government email. + * On the VAPT branch, @cure53.de (pentest vendor) is treated the same as .gov.sg. */ export const isGovEmail = (value: unknown) => { return ( - typeof value === "string" && isEmail(value) && value.endsWith(".gov.sg") + typeof value === "string" && isEmail(value) && (value.endsWith(".gov.sg") || value.endsWith("@cure53.de")) ) } From cccc09296c137d56b6b11d8542d41c341277e7bf Mon Sep 17 00:00:00 2001 From: adriangohjw Date: Wed, 3 Jun 2026 15:21:21 +0800 Subject: [PATCH 4/9] fix: failing test --- apps/studio/src/utils/email.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/studio/src/utils/email.ts b/apps/studio/src/utils/email.ts index a66d209878..19a9b1c3b4 100644 --- a/apps/studio/src/utils/email.ts +++ b/apps/studio/src/utils/email.ts @@ -6,7 +6,9 @@ import isEmail from "validator/lib/isEmail" */ export const isGovEmail = (value: unknown) => { return ( - typeof value === "string" && isEmail(value) && (value.endsWith(".gov.sg") || value.endsWith("@cure53.de")) + typeof value === "string" && + isEmail(value) && + (value.endsWith(".gov.sg") || value.toLowerCase().endsWith("@cure53.de")) ) } From 291f51b66361e516d9e5ee499a32b4d0ec36019b Mon Sep 17 00:00:00 2001 From: adriangohjw Date: Wed, 3 Jun 2026 17:48:25 +0800 Subject: [PATCH 5/9] chore: add VAPT migration command to package.json - Introduced a new script "migrate:vapt" in package.json to facilitate database migrations for the VAPT environment, enhancing deployment flexibility. --- apps/studio/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/studio/package.json b/apps/studio/package.json index 56f1b22df1..63523f7252 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -33,6 +33,7 @@ "migrate:dev": "prisma migrate dev", "migrate:staging": "source .ssh/.env.staging && pnpm exec prisma migrate deploy", "migrate:uat": "source .ssh/.env.uat && pnpm exec prisma migrate deploy", + "migrate:vapt": "source .ssh/.env.vapt && pnpm exec prisma migrate deploy", "predev": "run-s migrate", "prestart": "pnpm run migrate", "services:setup": "docker compose -f \"docker-compose.yml\" up -d --wait", From 05cfe84611a3b7dfe7f7e04bb5a8a218baf6801f Mon Sep 17 00:00:00 2001 From: adriangohjw Date: Mon, 8 Jun 2026 22:57:14 +0800 Subject: [PATCH 6/9] chore: update S3 assets bucket name in deploy workflow Changed the S3 assets bucket name in the GitHub Actions deployment workflow for VAPT to reflect the new naming convention. --- .github/workflows/deploy_vapt.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_vapt.yml b/.github/workflows/deploy_vapt.yml index 3a06243906..3b7e912928 100644 --- a/.github/workflows/deploy_vapt.yml +++ b/.github/workflows/deploy_vapt.yml @@ -41,6 +41,6 @@ jobs: app-name: "Isomer Studio (VAPT)" app-version: ${{ github.sha }} app-s3-region: "ap-southeast-1" - app-s3-assets-bucket-name: "isomer-next-infra-vapt-assets-private-32bb37c" + app-s3-assets-bucket-name: "isomer-next-infra-vapt-assets-private-14544ea" app-s3-assets-domain-name: "isomer-user-content-vapt.by.gov.sg" app-growthbook-client-key: "sdk-x4jkIJGr4TizR8qK" From 946128f1e780ed45c28e383f18853a0462ab778f Mon Sep 17 00:00:00 2001 From: adriangohjw Date: Mon, 8 Jun 2026 22:57:35 +0800 Subject: [PATCH 7/9] fix: disable allErrors in AJV configuration Updated the AJV configuration to set allErrors to false, improving error handling behavior in validation processes. --- apps/studio/src/utils/ajv.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/studio/src/utils/ajv.ts b/apps/studio/src/utils/ajv.ts index 7aa4d5c6c5..1bba79d97a 100644 --- a/apps/studio/src/utils/ajv.ts +++ b/apps/studio/src/utils/ajv.ts @@ -3,7 +3,7 @@ import addErrors from "ajv-errors" export const ajv = new Ajv({ useDefaults: true, - allErrors: true, + allErrors: false, strict: false, logger: false, discriminator: true, From 537b749a83d83e48418266b88ff138ab88dc6d45 Mon Sep 17 00:00:00 2001 From: adriangohjw Date: Mon, 8 Jun 2026 23:13:19 +0800 Subject: [PATCH 8/9] Revert "fix: disable allErrors in AJV configuration" This reverts commit 946128f1e780ed45c28e383f18853a0462ab778f. --- apps/studio/src/utils/ajv.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/studio/src/utils/ajv.ts b/apps/studio/src/utils/ajv.ts index 1bba79d97a..7aa4d5c6c5 100644 --- a/apps/studio/src/utils/ajv.ts +++ b/apps/studio/src/utils/ajv.ts @@ -3,7 +3,7 @@ import addErrors from "ajv-errors" export const ajv = new Ajv({ useDefaults: true, - allErrors: false, + allErrors: true, strict: false, logger: false, discriminator: true, From af9e93000d35433d4d9160091bc8ffc2e84dde71 Mon Sep 17 00:00:00 2001 From: adriangohjw Date: Tue, 9 Jun 2026 12:40:34 +0800 Subject: [PATCH 9/9] fix: enhance folder title validation in baseEditFolderSchema - Updated the title field in baseEditFolderSchema to enforce a maximum length constraint, ensuring folder titles do not exceed ${MAX_FOLDER_TITLE_LENGTH} characters. This improves user experience by providing clearer feedback on title requirements. --- apps/studio/src/schemas/folder.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/studio/src/schemas/folder.ts b/apps/studio/src/schemas/folder.ts index 6a0aba1133..c0c4c54a0a 100644 --- a/apps/studio/src/schemas/folder.ts +++ b/apps/studio/src/schemas/folder.ts @@ -39,7 +39,12 @@ const baseFolderSchema = z.object({ export const baseEditFolderSchema = baseFolderSchema.extend({ permalink: permalinkSchema, - title: z.string().min(1, { message: "Enter a title for this folder" }), + title: z + .string() + .min(1, { message: "Enter a title for this folder" }) + .max(MAX_FOLDER_TITLE_LENGTH, { + message: `Folder title should be shorter than ${MAX_FOLDER_TITLE_LENGTH} characters.`, + }), }) export const editFolderSchema = baseEditFolderSchema.superRefine(