diff --git a/workflow/packages/backend/api/src/app/flags/flag.module.ts b/workflow/packages/backend/api/src/app/flags/flag.module.ts index 83e7ccd5..8391ea77 100644 --- a/workflow/packages/backend/api/src/app/flags/flag.module.ts +++ b/workflow/packages/backend/api/src/app/flags/flag.module.ts @@ -18,16 +18,51 @@ export const flagController: FastifyPluginAsyncTypebox = async (app) => { logLevel: 'silent', }, async (request: FastifyRequest) => { + // Security fix: Require authentication + if (!request.principal) { + return app.httpErrors.unauthorized('Authentication required') + } + + // Security fix: Require admin role for sensitive configuration + if (request.principal.type !== 'ADMIN') { + return app.httpErrors.forbidden('Admin access required') + } + const flags = await flagService.getAll() const flagsMap: Record> = flags.reduce( (map, flag) => ({ ...map, [flag.id as string]: flag.value }), {}, ) + + // Security fix: Filter sensitive configuration data + const safeFlags = filterSensitiveFlags(flagsMap) + return flagHooks.get().modify({ - flags: flagsMap, + flags: safeFlags, request, }) }, ) } + +// Security fix: Filter sensitive configuration data +function filterSensitiveFlags(flags: Record>): Record> { + const sensitiveKeys = [ + 'AUTH0_DOMAIN', + 'AUTH0_APP_CLIENT_ID', + 'SAML_AUTH_ACS_URL', + 'WEBHOOK_URL_PREFIX', + 'THIRD_PARTY_AUTH_PROVIDER_REDIRECT_URL', + 'SUPPORTED_APP_WEBHOOKS' + ] + + const safeFlags = { ...flags } + + // Remove sensitive keys + sensitiveKeys.forEach(key => { + delete safeFlags[key] + }) + + return safeFlags +} diff --git a/workflow/packages/backend/api/src/app/server.ts b/workflow/packages/backend/api/src/app/server.ts index 848ced9d..24dd37db 100644 --- a/workflow/packages/backend/api/src/app/server.ts +++ b/workflow/packages/backend/api/src/app/server.ts @@ -74,10 +74,29 @@ async function setupBaseApp(): Promise { await app.register(formBody, { parser: (str) => qs.parse(str) }) app.setErrorHandler(errorHandler) + // Security fix: CORS configuration to prevent wildcard origin with credentials await app.register(cors, { - origin: '*', + origin: (origin, callback) => { + // Allow specific AIxBlock domains only + const allowedOrigins = [ + 'https://workflow.aixblock.io', + 'https://app.aixblock.io', + 'https://api.aixblock.io', + 'https://workflow-live.aixblock.io' + ] + + // Allow requests with no origin (mobile apps, Postman, etc.) + if (!origin) return callback(null, true) + + if (allowedOrigins.includes(origin)) { + callback(null, true) + } else { + callback(new Error('Not allowed by CORS'), false) + } + }, + credentials: true, exposedHeaders: ['*'], - methods: ['*'], + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], }) // SurveyMonkey app.addContentTypeParser(