CVE-2024-5128 (TypeScript, published 2024-06-06T19:16Z)
Description: value: An Insecure Direct Object Reference (IDOR) vulnerability was identified in lunary-ai/lunary, affecting versions up to and including 1.2.2. This vulnerability allows unauthorized users to view, update, or delete any dataset_prompt or dataset_prompt_variation within any dataset or project. The issue stems from improper access control checks in the dataset management endpoints, where direct references to object IDs are not adequately secured against unauthorized access. This vulnerability was fixed in version 1.2.25.
Severity: nan (CVSS v3 base score: )
Weakness: CWE-284 \u2014 Improper Access Control
The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
Repository: https://github.com/lunary-ai/lunary
Files changed:
packages/backend/src/api/v1/datasets/index.ts
Commit message: fix: api security (#330)



Vulnerable code:
// Can be public route
datasets.get(
  "/:identifier",
  // checkAccess("datasets", "read"),
  async (ctx: Context) => {
    const { projectId } = ctx.state
    const { identifier } = ctx.params
    const isUUID = validateUUID(identifier)
    if (isUUID) {
      // For frontend
      const datasetId = identifier
      const dataset = await getDatasetById(datasetId, projectId)
      ctx.body = dataset
      return
    } else {
      // For SDK
      const slug = identifier
      const dataset = await getDatasetBySlug(slug, projectId)
      ctx.body = dataset
      return
    }
  },
)
  // insert 1 prompt and 1 variation
    const { id } = ctx.params
    await sql`delete from dataset where id = ${id} and project_id = ${projectId}`
// Create prompt
    // insert 1 variation
    await sql`insert into dataset_prompt_variation
    ${sql({
      promptId: prompt.id,
      variables: {},
      idealOutput: idealOutput ? lastMsg(idealOutput) : "",
    })}
    returning *
  `
// return array of prompts and variations
    select * from dataset_prompt where id = ${id} order by created_at asc
    select * from dataset_prompt_variation where prompt_id = ${id} order by created_at asc
// add route ot to delete dataset_prompt and dataset_prompt_variation
    const { id } = ctx.params
    await sql`delete from dataset_prompt where id = ${id}`
    await sql`delete from dataset_prompt_variation where prompt_id = ${id}`
// Update prompt
    const { id } = ctx.params
    await sql`delete from dataset_prompt_variation where id = ${id}`
// Update variation
// Create variation
  ${sql(
    clearUndefined({
      promptId,
      variables,
      idealOutput,
    }),
  )}
  returning *
  `

Fixed code:
datasets.get("/:identifier", async (ctx: Context) => {
  const { projectId } = ctx.state
  const { identifier } = ctx.params
  const isUUID = validateUUID(identifier)
  if (isUUID) {
    // For frontend
    const datasetId = identifier
    const dataset = await getDatasetById(datasetId, projectId)
    ctx.body = dataset
    return
  } else {
    // For SDK
    const slug = identifier
    const dataset = await getDatasetBySlug(slug, projectId)
    ctx.body = dataset
    return
  }
})
    const { id: datasetId } = ctx.params
    await sql`delete from dataset where id = ${datasetId} and project_id = ${projectId}`
    await sql`
      insert into dataset_prompt_variation
        ${sql({
          promptId: prompt.id,
          variables: {},
          idealOutput: idealOutput ? lastMsg(idealOutput) : "",
        })}
      returning *
    `
      select * from dataset_prompt where id = ${id} order by created_at asc
      select * from dataset_prompt_variation where prompt_id = ${id} order by created_at asc
    const { id: promptId } = ctx.params

    const [datasetPrompt] = await sql`
      select
        *
      from
        dataset_prompt dp
        left join dataset d on dp.dataset_id = d.id
        left join project p on d.project_id = p.id
      where
        p.org_id = ${ctx.state.orgId} 
        and dp.id = ${promptId}
    `
    if (!datasetPrompt) {
      ctx.throw(401, "You do not have access to this ressource.")
    }

    await sql`delete from dataset_prompt where id = ${promptId}`
    await sql`delete from dataset_prompt_variation where prompt_id = ${promptId}`
    const { id: variationId } = ctx.params

    const [promptVariation] = await sql`
      select
        *
      from
        dataset_prompt_variation dpv
        left join dataset_prompt dp on dpv.prompt_id = dp.id
        left join dataset d on dp.dataset_id = d.id
        left join project p on d.project_id = p.id
      where
        p.org_id = ${ctx.state.orgId} 
        and dpv.id = ${variationId}
    `
    if (!promptVariation) {
      ctx.throw(401, "You do not have access to this ressource.")
    }
    await sql`delete from dataset_prompt_variation where id = ${variationId}`
      ${sql(
        clearUndefined({
          promptId,
          variables,
          idealOutput,
        }),
      )}
      returning *
    `