diff --git a/apps/studio/components/interfaces/Home/SecurityStatus.tsx b/apps/studio/components/interfaces/Home/SecurityStatus.tsx
index be4dc9b4425..7bf2277aef9 100644
--- a/apps/studio/components/interfaces/Home/SecurityStatus.tsx
+++ b/apps/studio/components/interfaces/Home/SecurityStatus.tsx
@@ -1,9 +1,10 @@
-import { CheckCircle2, ChevronRight, Loader2 } from 'lucide-react'
+import { CheckCircle2, ChevronRight, InfoIcon, Loader2 } from 'lucide-react'
import Link from 'next/link'
import { Fragment, useState } from 'react'
import { useParams } from 'common'
import { useProjectLintsQuery } from 'data/lint/lint-query'
+import { useServiceRoleKeyLeakQuery } from 'data/lint/service-role-key-leak-query'
import {
Button,
PopoverContent_Shadcn_,
@@ -27,6 +28,10 @@ export const SecurityStatus = () => {
const totalLints = errorLints.length + warnLints.length + infoLints.length
const noIssuesFound = totalLints === 0
+ const { data: hasServiceRoleKeyLeak } = useServiceRoleKeyLeakQuery({
+ projectRef: ref,
+ })
+
return (
@@ -73,6 +78,31 @@ export const SecurityStatus = () => {
{totalLints} issue{totalLints > 1 ? 's' : ''} have been identified
+ {hasServiceRoleKeyLeak && (
+
+
+
+
+
+
+
+
+
Service Role Key Leak
+
+ Your service role key may be compromised. Rotate this key immediately.
+
+
+
+
+
+
+
+
+ )}
+
{[
{ lints: errorLints, level: LINTER_LEVELS.ERROR },
{ lints: warnLints, level: LINTER_LEVELS.WARN },
diff --git a/apps/studio/components/interfaces/Settings/API/JWTSettings.tsx b/apps/studio/components/interfaces/Settings/API/JWTSettings.tsx
index deafd573c5e..29ec63b7c9e 100644
--- a/apps/studio/components/interfaces/Settings/API/JWTSettings.tsx
+++ b/apps/studio/components/interfaces/Settings/API/JWTSettings.tsx
@@ -226,6 +226,11 @@ const JWTSettings = () => {
A random secret will be created, or you can create your own.
+
+ This will invalidate all existing API keys, including your{' '}
+ service_role and{' '}
+ anon keys.
+
{isUpdatingJwtSecret ? (
@@ -244,13 +249,13 @@ const JWTSettings = () => {
},
}}
>
- Generate a new secret
+ Generate new API Keys
) : (
}>
- Generate a new secret
+ Generate new API Keys
@@ -259,7 +264,7 @@ const JWTSettings = () => {
onClick={() => setIsGeneratingKey(true)}
>
- Generate a random secret
+ Generate new API Keys
+ {hasServiceRoleKeyLeak && x.tags === 'service_role' && (
+
+
+ Urgent: Service Role key leak detected
+
+
+ Your service role key may be compromised. Rotate it immediately and ensure
+ it's used only on a private server. We detected that it is likely being used
+ on the client-side, in a browser or mobile app.{' '}
+ Generate new API keys below in JWT Settings
+ to rotate this key.
+
+
+
+ Read{' '}
+
+ Understanding API Keys
+ {' '}
+ to learn more.
+
+
+
+ )}
))
)}
@@ -161,7 +191,7 @@ const DisplayApiSettings = ({
) : (
['projects', projectRef, 'lint'] as const,
+ serviceRoleKeyLeak: (projectRef: string | undefined) =>
+ ['projects', projectRef, 'service-role-key-leak'] as const,
}
diff --git a/apps/studio/data/lint/service-role-key-leak-query.ts b/apps/studio/data/lint/service-role-key-leak-query.ts
new file mode 100644
index 00000000000..2e1bb463753
--- /dev/null
+++ b/apps/studio/data/lint/service-role-key-leak-query.ts
@@ -0,0 +1,56 @@
+import { UseQueryOptions, useQuery } from '@tanstack/react-query'
+
+import { useSelectedProject } from 'hooks/misc/useSelectedProject'
+import { BASE_PATH, PROJECT_STATUS } from 'lib/constants'
+import { ResponseError } from 'types'
+import { lintKeys } from './keys'
+
+export type ServiceRoleKeyLeakVariables = {
+ projectRef?: string
+}
+
+export type ServiceRoleKeyLeakResponse = boolean
+
+export async function checkServiceRoleKeyLeak(
+ { projectRef }: ServiceRoleKeyLeakVariables,
+ signal?: AbortSignal
+): Promise {
+ if (!projectRef) throw new Error('Project ref is required')
+
+ const res = await fetch(`${BASE_PATH}/api/check-service-role-key-leak`, {
+ headers: {
+ 'x-project': projectRef,
+ },
+ signal,
+ })
+
+ if (!res.ok) {
+ const error = await res.json()
+ throw new Error(error.error?.message || 'Failed to fetch service role key leak data')
+ }
+
+ return res.json()
+}
+
+export type ServiceRoleKeyLeakData = Awaited>
+export type ServiceRoleKeyLeakError = ResponseError
+
+export const useServiceRoleKeyLeakQuery = (
+ { projectRef }: ServiceRoleKeyLeakVariables,
+ {
+ enabled = true,
+ ...options
+ }: UseQueryOptions = {}
+) => {
+ const project = useSelectedProject()
+ const isActive = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY
+
+ return useQuery(
+ lintKeys.serviceRoleKeyLeak(projectRef),
+ ({ signal }) => checkServiceRoleKeyLeak({ projectRef }, signal),
+ {
+ enabled: enabled && typeof projectRef !== 'undefined' && isActive,
+ ...options,
+ }
+ )
+}
diff --git a/apps/studio/middleware.ts b/apps/studio/middleware.ts
index 8256a9563cd..dd9f0d62f43 100644
--- a/apps/studio/middleware.ts
+++ b/apps/studio/middleware.ts
@@ -20,6 +20,7 @@ const HOSTED_SUPPORTED_API_URLS = [
'/get-ip-address',
'/get-utc-time',
'/edge-functions/test',
+ '/check-service-role-key-leak',
]
export function middleware(request: NextRequest) {
diff --git a/apps/studio/pages/api/check-service-role-key-leak.ts b/apps/studio/pages/api/check-service-role-key-leak.ts
new file mode 100644
index 00000000000..22d3632532f
--- /dev/null
+++ b/apps/studio/pages/api/check-service-role-key-leak.ts
@@ -0,0 +1,54 @@
+import apiWrapper from 'lib/api/apiWrapper'
+import { NextApiRequest, NextApiResponse } from 'next'
+
+async function handler(req: NextApiRequest, res: NextApiResponse) {
+ const { method } = req
+
+ switch (method) {
+ case 'GET':
+ return handleGet(req, res)
+ default:
+ res.setHeader('Allow', ['GET'])
+ res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } })
+ }
+}
+
+async function handleGet(req: NextApiRequest, res: NextApiResponse) {
+ const projectRef = req.headers['x-project'] as string
+ console.log('the ref', projectRef)
+
+ if (!projectRef) {
+ return res.status(400).json({ error: 'Project reference is required' })
+ }
+
+ try {
+ const response = await fetch(
+ `https://api.logflare.app/api/endpoints/query/exposed%20service_role%20advisor%20hack?project=${projectRef}`,
+ {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8',
+ 'X-API-KEY': process.env.LOGFLARE_API_KEY!,
+ },
+ }
+ )
+
+ if (!response.ok) {
+ throw new Error(`Logflare API responded with status ${response.status}`)
+ }
+
+ const data = await response.json()
+
+ return res.json(data.result.length > 0)
+ } catch (error) {
+ console.error('Error fetching service role key leak data:', error)
+ return res.status(500).json({
+ error:
+ 'There was an unknown error fetching the service role key leak data. Please try again.',
+ })
+ }
+}
+
+const wrapper = (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler)
+
+export default wrapper