CVE-2024-1740: Incorrect Authorization (CWE-863)
value: In lunary-ai/lunary version 1.0.1, a vulnerability exists where a user removed from an organization can still read, create, modify, and delete logs by re-using an old authorization token. The lunary web application communicates with the server using an 'Authorization' token in the browser, which does not properly invalidate upon the user's removal from the organization. This allows the removed user to perform unauthorized actions on logs and access project and external user details without valid permissions.
CWE description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
Published: 2024-04-10T17:15Z
Repository: https://github.com/lunary-ai/lunary
Language: TypeScript
Files changed: packages/backend/src/api/v1/evaluations/index.ts, packages/backend/src/api/v1/projects/index.ts, packages/backend/src/api/v1/radars.ts
Commit date: 2024-04-08 23:35:58 -0300
Commit message: fix: security patches (#192)


Diff: diff --git a/packages/backend/src/api/v1/evaluations/index.ts b/packages/backend/src/api/v1/evaluations/index.ts
index 9b0a5ff4..bf9fc112 100644
--- a/packages/backend/src/api/v1/evaluations/index.ts
+++ b/packages/backend/src/api/v1/evaluations/index.ts
@@ -1,28 +1,27 @@
-import Router from "koa-router"
+import { runChecksOnRun } from "@/src/checks/runChecks"
+import { checkAccess } from "@/src/utils/authorization"
+import { calcRunCost } from "@/src/utils/calcCost"
+import { getReadableDateTime } from "@/src/utils/date"
 import sql from "@/src/utils/db"
 import Context from "@/src/utils/koa"
-import { getReadableDateTime } from "@/src/utils/date"
-import { runEval } from "./utils"
-import { getEvaluation } from "./utils"
-import { calcRunCost } from "@/src/utils/calcCost"
-import { runChecksOnRun } from "@/src/checks/runChecks"
+import Router from "koa-router"
+import { RunEvent } from "lunary/types"
 import PQueue from "p-queue"
 import { PassThrough } from "stream"
-import { checkAccess } from "@/src/utils/authorization"
-import { RunEvent } from "lunary/types"
+import { runEval } from "./utils"
 
 const evaluations = new Router({ prefix: "/evaluations" })
 
 const MAX_PARALLEL_EVALS = 4
 
 evaluations.post(
   "/",
   checkAccess("evaluations", "create"),
   async (ctx: Context) => {
     const { name, datasetId, checklistId, providers } = ctx.request.body as any
-    const { userId, projectId } = ctx.state
+    const { userId, projectId, orgId } = ctx.state
 
     ctx.request.socket.setTimeout(0)
     ctx.request.socket.setNoDelay(true)
     ctx.request.socket.setKeepAlive(true)
 
@@ -35,10 +34,16 @@ evaluations.post(
     const queue = new PQueue({
       concurrency: MAX_PARALLEL_EVALS,
       timeout: 10000,
     })
 
+    const [{ plan }] =
+      await sql`select plan, eval_allowance from org where id = ${orgId}`
+    if (plan === "free") {
+      ctx.throw(403, "You can't create evaluations on the free plan.")
+    }
+
     // TODO: transactions, but not working with because of nesting
     const evaluationToInsert = {
       name: name ? name : `Evaluation of ${getReadableDateTime()}`,
       ownerId: userId,
       projectId,
diff --git a/packages/backend/src/api/v1/projects/index.ts b/packages/backend/src/api/v1/projects/index.ts
index 3745798b..99c8f88c 100644
--- a/packages/backend/src/api/v1/projects/index.ts
+++ b/packages/backend/src/api/v1/projects/index.ts
@@ -119,11 +119,16 @@ projects.post(
   checkAccess("projects", "update"),
   async (ctx: Context) => {
     const { projectId } = ctx.params
     const { userId } = ctx.state
 
-    // Define the schema for request body validation using Zod
+    const [hasAccess] =
+      await sql`select * from account_project where project_id = ${projectId} and account_id = ${userId}`
+    if (!hasAccess) {
+      ctx.throw(401, "Not allowed")
+    }
+
     const requestBodySchema = z.object({
       type: z.enum(["private", "public"]),
     })
     const { type } = requestBodySchema.parse(ctx.request.body)
 
diff --git a/packages/backend/src/api/v1/radars.ts b/packages/backend/src/api/v1/radars.ts
index 68eca74a..8ff2babf 100644
--- a/packages/backend/src/api/v1/radars.ts
+++ b/packages/backend/src/api/v1/radars.ts
@@ -216,19 +216,25 @@ radars.get("/:radarId/chart", async (ctx) => {
     failed: Number(row.failed),
   }))
 })
 
 radars.post("/", checkAccess("radars", "create"), async (ctx) => {
-  const { projectId, userId } = ctx.state
+  const { projectId, userId, orgId } = ctx.state
   const { description, view, checks, alerts, negative } = ctx.request.body as {
     description: string
     view: any[]
     checks: any[]
     alerts: any[]
     negative: boolean
   }
 
+  const [{ plan }] =
+    await sql`select plan, eval_allowance from org where id = ${orgId}`
+  if (plan === "free") {
+    ctx.throw(403, "You can't create evaluations on the free plan.")
+  }
+
   const [row] = await sql`
     insert into radar ${sql({
       description,
       view: sql.json(view),
       checks: sql.json(checks),

Vulnerable code: import Router from "koa-router"
import { getReadableDateTime } from "@/src/utils/date"
import { runEval } from "./utils"
import { getEvaluation } from "./utils"
import { calcRunCost } from "@/src/utils/calcCost"
import { runChecksOnRun } from "@/src/checks/runChecks"
import { checkAccess } from "@/src/utils/authorization"
import { RunEvent } from "lunary/types"
    const { userId, projectId } = ctx.state
    // Define the schema for request body validation using Zod
  const { projectId, userId } = ctx.state
Fixed code: import { runChecksOnRun } from "@/src/checks/runChecks"
import { checkAccess } from "@/src/utils/authorization"
import { calcRunCost } from "@/src/utils/calcCost"
import { getReadableDateTime } from "@/src/utils/date"
import Router from "koa-router"
import { RunEvent } from "lunary/types"
import { runEval } from "./utils"
    const { userId, projectId, orgId } = ctx.state
    const [{ plan }] =
      await sql`select plan, eval_allowance from org where id = ${orgId}`
    if (plan === "free") {
      ctx.throw(403, "You can't create evaluations on the free plan.")
    }

    const [hasAccess] =
      await sql`select * from account_project where project_id = ${projectId} and account_id = ${userId}`
    if (!hasAccess) {
      ctx.throw(401, "Not allowed")
    }

  const { projectId, userId, orgId } = ctx.state
  const [{ plan }] =
    await sql`select plan, eval_allowance from org where id = ${orgId}`
  if (plan === "free") {
    ctx.throw(403, "You can't create evaluations on the free plan.")
  }