CVE-2024-1666 (published 2024-04-16T00:15Z)

In lunary-ai/lunary version 1.0.0, an authorization flaw exists that allows unauthorized radar creation. The vulnerability stems from the lack of server-side checks to verify if a user is on a free account during the radar creation process, which is only enforced in the web UI. As a result, attackers can bypass the intended account upgrade requirement by directly sending crafted requests to the server, enabling the creation of an unlimited number of radars without payment.


Weakness information:
cwe_id: CWE-770
cwe_name: Allocation of Resources Without Limits or Throttling
description: The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
extended_description: Code frequently has to work with limited resources, so programmers must be careful to ensure that resources are not consumed too quickly, or too easily.  Without use of quotas, resource limits, or other protection mechanisms, it can be easy for an attacker to consume many resources by rapidly making many requests, or causing larger resources to be used than is needed. When too many resources are allocated, or if a single resource is too large, then it can prevent the code from working correctly, possibly leading to a denial of service.
         

Fix information:
repo_url: https://github.com/lunary-ai/lunary
commit_details: repo_url: https://github.com/lunary-ai/lunary
author: Hugues Chocart
author_date: 2024-04-08 23:35:58-03:00
msg: fix: security patches (#192)
repository: repo_url: https://github.com/lunary-ai/lunary
repo_name: lunary-ai/lunary
description: The production toolkit for LLMs. Observability, prompt management and evaluations. 
repo_language: TypeScript
owner: lunary-ai
file_changes: filename: index.ts
change_type: ModificationType.MODIFY
diff: @@ -1,15 +1,14 @@
-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" })
 
@@ -20,7 +19,7 @@ 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)
@@ -37,6 +36,12 @@ evaluations.post(
       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()}`,

programming_language: TypeScript
filename: index.ts
change_type: ModificationType.MODIFY
diff: @@ -121,7 +121,12 @@ projects.post(
     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"]),
     })

programming_language: TypeScript
filename: radars.ts
change_type: ModificationType.MODIFY
diff: @@ -218,7 +218,7 @@ radars.get("/:radarId/chart", async (ctx) => {
 })
 
 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[]
@@ -227,6 +227,12 @@ radars.post("/", checkAccess("radars", "create"), async (ctx) => {
     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,

programming_language: JavaScript