Skip to content

Conversation

@jp-agenta
Copy link
Member

[STALE] [feature] Add ingestion batching (spans and meters)

Copilot AI review requested due to automatic review settings November 11, 2025 15:26
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


GitHub CI seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This is a stale feature branch that adds ingestion batching capabilities for spans and meters. The changes involve significant refactoring of evaluation entities, removal of legacy application code, updates to database migrations, authentication logic simplification, and entrypoint reorganization.

Key Changes:

  • Renamed evaluation entities from Result/Metrics to Step/Metric (singular forms)
  • Removed legacy application routers, models, and utilities
  • Simplified user authentication by removing PostHog feature flag integration
  • Refactored database migration utilities from async to sync patterns
  • Updated entitlements system with soft-check caching mechanism

Reviewed Changes

Copilot reviewed 114 out of 2332 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
api/oss/src/apis/fastapi/evaluations/models.py Renamed evaluation entities (Result→Step, Metrics→Metric) and updated query models
api/oss/src/apis/fastapi/applications/* Removed legacy application router, models, and utilities
api/oss/src/apis/fastapi/annotations/* Major refactor adding annotation utilities, expanding router with CRUD operations
api/oss/src/__init__.py Simplified email blocking logic by removing PostHog integration
api/oss/docker/Dockerfile.* Cleaned up Docker build steps and removed SDK installation
api/oss/databases/postgres/migrations/utils.py Converted async database operations to synchronous patterns
api/oss/databases/postgres/migrations/tracing/versions/* Fixed migration index creation and enum naming
api/oss/databases/postgres/migrations/core/versions/* Removed several stale migration files
api/entrypoint.py Reorganized service initialization and router mounting order
api/ee/src/utils/entitlements.py Added soft-check caching mode for entitlements
api/ee/src/services/* Updated service methods to use correct database managers and fixed parameter naming
Comments suppressed due to low confidence (1)

api/oss/databases/postgres/migrations/runner.py:1

  • Entire migration runner file was deleted. This removes the ability to run database migrations programmatically, which may be needed for deployment or setup scripts.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +102 to +103
span_type_enum = sa.Enum(SpanType, name="tracetype")
trace_type_enum = sa.Enum(TraceType, name="spantype")
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enum names are swapped in the downgrade function. span_type_enum should use name='spantype' and trace_type_enum should use name='tracetype' to properly reverse the upgrade operation.

Suggested change
span_type_enum = sa.Enum(SpanType, name="tracetype")
trace_type_enum = sa.Enum(TraceType, name="spantype")
span_type_enum = sa.Enum(SpanType, name="spantype")
trace_type_enum = sa.Enum(TraceType, name="tracetype")

Copilot uses AI. Check for mistakes.
).scalar()

if (count or 0) > 0:
if count > 0:
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition if count > 0 is missing a null check. The previous code had if (count or 0) > 0 which safely handles None values. If count is None, this will raise a TypeError.

Suggested change
if count > 0:
if (count or 0) > 0:

Copilot uses AI. Check for mistakes.
"Click the link below to accept the invitation:</p><br>"
f'<a href="{invite_link}">Accept Invitation</a>'
),
call_to_action=f'Click the link below to accept the invitation:</p><br><a href="{env.AGENTA_WEB_URL}/auth?token={token}&email={email}&org_id={organization.id}&workspace_id={workspace.id}&project_id={project_id}">Accept Invitation</a>',
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URL parameters are no longer being URL-encoded. The previous code used quote() for token, email, and IDs. Without encoding, special characters in these values could break the URL or create security vulnerabilities.

Copilot uses AI. Check for mistakes.
Comment on lines 4 to 7
from typing import Callable, Coroutine


async def run_in_separate_thread(func: Callable, *args, **kwargs) -> Coroutine:
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return type annotation is incorrect. The function returns the result of loop.run_in_executor(), which is a Future/Task, not a Coroutine. The return type should be the actual return type of func or Any.

Suggested change
from typing import Callable, Coroutine
async def run_in_separate_thread(func: Callable, *args, **kwargs) -> Coroutine:
from typing import Callable, Coroutine, Any
async def run_in_separate_thread(func: Callable, *args, **kwargs) -> Any:

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +40
VIEW_APPLICATION = "view_application"
EDIT_APPLICATION = "edit_application"
CREATE_APPLICATION = "create_application"
DELETE_APPLICATION = "delete_application"
Copy link

Copilot AI Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Permission naming changed from plural (VIEW_APPLICATIONS) to singular forms. This is a breaking change that will affect existing permission checks throughout the codebase if not updated consistently.

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +84
const runRes = await axios.get(
`/preview/evaluations/runs/${evaluationTableId}?project_id=${projectId}`,
)

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI 2 months ago

The best way to fix this is to validate and constrain the user-provided evaluationTableId before using it to construct an internal API URL. Ideally, restrict the value to a string that matches an expected pattern—most likely a UUID (or whatever the allowed set of IDs is). This prevents attackers from injecting special characters or performing path traversal. We can implement this validation at the point where useEvaluationRunData is called, or at the beginning of the hook itself (preferable for defense-in-depth, and because we cannot assume calling code changes). Should the value fail validation, early-return or throw an error, or use a known safe value (e.g., null) for the request. We'll add a helper isValidEvaluationId function to do this check and only proceed if it passes; otherwise, we return early or handle the error gracefully.

Suggested changeset 1
web/ee/src/lib/hooks/useEvaluationRunData/index.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/web/ee/src/lib/hooks/useEvaluationRunData/index.ts b/web/ee/src/lib/hooks/useEvaluationRunData/index.ts
--- a/web/ee/src/lib/hooks/useEvaluationRunData/index.ts
+++ b/web/ee/src/lib/hooks/useEvaluationRunData/index.ts
@@ -25,6 +25,15 @@
 import {evalAtomStore, evaluationRunStateAtom, loadingStateAtom} from "./assets/atoms"
 import {buildRunIndex} from "./assets/helpers/buildRunIndex"
 
+// Accept UUID or simple non-empty alphanumeric/hyphen/underscore string as safe
+function isValidEvaluationId(id: unknown): id is string {
+    if (typeof id !== 'string') return false;
+    // Strict UUID v4 pattern, loosen if other forms are allowed
+    const uuidV4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
+    const allowedSimple = /^[A-Za-z0-9_-]+$/;
+    return uuidV4.test(id) || allowedSimple.test(id);
+}
+
 const fetchLegacyScenariosData = async (
     evaluationId: string,
     evaluationObj: Evaluation,
@@ -75,6 +84,21 @@
 
     // New fetcher for preview runs that fetches and enriches with testsetData
     const fetchAndEnrichPreviewRun = useCallback(async () => {
+        // Validate evaluationTableId before making API call
+        if (!isValidEvaluationId(evaluationTableId)) {
+            console.error("[useEvaluationRunData] Invalid evaluationTableId received, aborting fetch.");
+            evalAtomStore().set(loadingStateAtom, (draft) => {
+                draft.isLoadingEvaluation = false
+                draft.activeStep = null
+            })
+            evalAtomStore().set(evaluationRunStateAtom, (draft) => {
+                draft.isPreview = false;
+                draft.rawRun = null;
+                draft.enrichedRun = null;
+                draft.runIndex = null;
+            })
+            return null;
+        }
         evalAtomStore().set(loadingStateAtom, (draft) => {
             draft.isLoadingEvaluation = true
             draft.activeStep = "eval-run"
EOF
@@ -25,6 +25,15 @@
import {evalAtomStore, evaluationRunStateAtom, loadingStateAtom} from "./assets/atoms"
import {buildRunIndex} from "./assets/helpers/buildRunIndex"

// Accept UUID or simple non-empty alphanumeric/hyphen/underscore string as safe
function isValidEvaluationId(id: unknown): id is string {
if (typeof id !== 'string') return false;
// Strict UUID v4 pattern, loosen if other forms are allowed
const uuidV4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const allowedSimple = /^[A-Za-z0-9_-]+$/;
return uuidV4.test(id) || allowedSimple.test(id);
}

const fetchLegacyScenariosData = async (
evaluationId: string,
evaluationObj: Evaluation,
@@ -75,6 +84,21 @@

// New fetcher for preview runs that fetches and enriches with testsetData
const fetchAndEnrichPreviewRun = useCallback(async () => {
// Validate evaluationTableId before making API call
if (!isValidEvaluationId(evaluationTableId)) {
console.error("[useEvaluationRunData] Invalid evaluationTableId received, aborting fetch.");
evalAtomStore().set(loadingStateAtom, (draft) => {
draft.isLoadingEvaluation = false
draft.activeStep = null
})
evalAtomStore().set(evaluationRunStateAtom, (draft) => {
draft.isPreview = false;
draft.rawRun = null;
draft.enrichedRun = null;
draft.runIndex = null;
})
return null;
}
evalAtomStore().set(loadingStateAtom, (draft) => {
draft.isLoadingEvaluation = true
draft.activeStep = "eval-run"
Copilot is powered by AI and may make mistakes. Always verify output.
export const fetchEvaluation = async (evaluationId: string) => {
const {projectId} = getCurrentProject()

const response = await axios.get(`/evaluations/${evaluationId}?project_id=${projectId}`)

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.
The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI 2 months ago

The recommended fix is to validate and sanitize the evaluationId parameter before incorporating it into the URL for outgoing requests. Specifically, since evaluation IDs are likely opaque tokens (such as UUIDs), the code should check that each supplied ID matches an expected format before sending the request. This can be implemented by adding a utility function that verifies whether a string is a valid UUID (or whatever structure is expected for evaluation IDs). If validation fails, the code should either throw an error or avoid sending the request.

Edits should occur in web/ee/src/services/evaluations/api/index.ts, within the relevant functions (fetchEvaluation, fetchEvaluationStatus, fetchAllEvaluationScenarios, and the mapping inside fetchAllComparisonResults). Add a UUID validation function and call it before requests are made with user-supplied evaluation IDs. If an ID fails validation, throw an error instead of interpolating it into the URL.

A well-known library for UUID validation is validator, but you can use a simple regex or the built-in validate method from the uuid package (since uuid is already imported).

Suggested changeset 1
web/ee/src/services/evaluations/api/index.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/web/ee/src/services/evaluations/api/index.ts b/web/ee/src/services/evaluations/api/index.ts
--- a/web/ee/src/services/evaluations/api/index.ts
+++ b/web/ee/src/services/evaluations/api/index.ts
@@ -1,5 +1,5 @@
 import uniqBy from "lodash/uniqBy"
-import {v4 as uuidv4} from "uuid"
+import {v4 as uuidv4, validate as uuidValidate} from "uuid"
 
 import {getCurrentProject} from "@/oss/contexts/project.context"
 import axios from "@/oss/lib/api/assets/axiosConfig"
@@ -146,14 +146,18 @@
 
 export const fetchEvaluation = async (evaluationId: string) => {
     const {projectId} = getCurrentProject()
-
+    if (!uuidValidate(evaluationId)) {
+        throw new Error("Invalid evaluationId format")
+    }
     const response = await axios.get(`/evaluations/${evaluationId}?project_id=${projectId}`)
     return evaluationTransformer(response.data) as _Evaluation
 }
 
 export const fetchEvaluationStatus = async (evaluationId: string) => {
     const {projectId} = getCurrentProject()
-
+    if (!uuidValidate(evaluationId)) {
+        throw new Error("Invalid evaluationId format")
+    }
     const response = await axios.get(`/evaluations/${evaluationId}/status?project_id=${projectId}`)
     return response.data as {status: _Evaluation["status"]}
 }
@@ -198,7 +196,9 @@
 // Evaluation Scenarios
 export const fetchAllEvaluationScenarios = async (evaluationId: string) => {
     const {projectId} = getCurrentProject()
-
+    if (!uuidValidate(evaluationId)) {
+        throw new Error("Invalid evaluationId format")
+    }
     const [{data: evaluationScenarios}, evaluation] = await Promise.all([
         axios.get(`/evaluations/${evaluationId}/evaluation_scenarios?project_id=${projectId}`),
         fetchEvaluation(evaluationId),
@@ -225,6 +225,10 @@
 
 // Comparison
 export const fetchAllComparisonResults = async (evaluationIds: string[]) => {
+    // Validate all evaluationIds before making any requests
+    if (!Array.isArray(evaluationIds) || evaluationIds.some((id) => !uuidValidate(id))) {
+        throw new Error("One or more evaluationIds are invalid")
+    }
     const scenarioGroups = await Promise.all(evaluationIds.map(fetchAllEvaluationScenarios))
     const testset: TestSet = await fetchTestset(scenarioGroups[0][0].evaluation?.testset?.id)
 
EOF
@@ -1,5 +1,5 @@
import uniqBy from "lodash/uniqBy"
import {v4 as uuidv4} from "uuid"
import {v4 as uuidv4, validate as uuidValidate} from "uuid"

import {getCurrentProject} from "@/oss/contexts/project.context"
import axios from "@/oss/lib/api/assets/axiosConfig"
@@ -146,14 +146,18 @@

export const fetchEvaluation = async (evaluationId: string) => {
const {projectId} = getCurrentProject()

if (!uuidValidate(evaluationId)) {
throw new Error("Invalid evaluationId format")
}
const response = await axios.get(`/evaluations/${evaluationId}?project_id=${projectId}`)
return evaluationTransformer(response.data) as _Evaluation
}

export const fetchEvaluationStatus = async (evaluationId: string) => {
const {projectId} = getCurrentProject()

if (!uuidValidate(evaluationId)) {
throw new Error("Invalid evaluationId format")
}
const response = await axios.get(`/evaluations/${evaluationId}/status?project_id=${projectId}`)
return response.data as {status: _Evaluation["status"]}
}
@@ -198,7 +196,9 @@
// Evaluation Scenarios
export const fetchAllEvaluationScenarios = async (evaluationId: string) => {
const {projectId} = getCurrentProject()

if (!uuidValidate(evaluationId)) {
throw new Error("Invalid evaluationId format")
}
const [{data: evaluationScenarios}, evaluation] = await Promise.all([
axios.get(`/evaluations/${evaluationId}/evaluation_scenarios?project_id=${projectId}`),
fetchEvaluation(evaluationId),
@@ -225,6 +225,10 @@

// Comparison
export const fetchAllComparisonResults = async (evaluationIds: string[]) => {
// Validate all evaluationIds before making any requests
if (!Array.isArray(evaluationIds) || evaluationIds.some((id) => !uuidValidate(id))) {
throw new Error("One or more evaluationIds are invalid")
}
const scenarioGroups = await Promise.all(evaluationIds.map(fetchAllEvaluationScenarios))
const testset: TestSet = await fetchTestset(scenarioGroups[0][0].evaluation?.testset?.id)

Copilot is powered by AI and may make mistakes. Always verify output.
const {projectId} = getCurrentProject()

const [{data: evaluationScenarios}, evaluation] = await Promise.all([
axios.get(`/evaluations/${evaluationId}/evaluation_scenarios?project_id=${projectId}`),

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.
The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI 2 months ago

To fix this SSRF risk, we must ensure that the user-controlled values interpolated into the outgoing request URL (specifically, the evaluationId in /evaluations/${evaluationId}/evaluation_scenarios) are strictly validated or constrained to allow only legitimate, expected values such as UUIDs. The fix should be implemented in the frontend code before making the Axios request. The most robust approach is to run a validation (e.g., a regex match) that accepts only valid UUIDs or expected ID formats before passing them into the request, and to ignore or reject any IDs that do not match. This should be done in the fetchAllComparisonResults and fetchAllEvaluationScenarios functions. Add a helper for validation (e.g., isValidUUID) and apply it to filter IDs. If an ID does not validate, it should not be used in any request.

Required changes:

  • Add a UUID validation method (can use a utility function or simple regex).
  • In fetchAllComparisonResults, filter the incoming evaluationIds array for valid IDs only before proceeding.
  • In fetchAllEvaluationScenarios, validate evaluationId before making any requests; throw an error or skip if invalid.

Suggested changeset 1
web/ee/src/services/evaluations/api/index.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/web/ee/src/services/evaluations/api/index.ts b/web/ee/src/services/evaluations/api/index.ts
--- a/web/ee/src/services/evaluations/api/index.ts
+++ b/web/ee/src/services/evaluations/api/index.ts
@@ -197,6 +197,9 @@
 
 // Evaluation Scenarios
 export const fetchAllEvaluationScenarios = async (evaluationId: string) => {
+    if (!isValidUUID(evaluationId)) {
+        throw new Error("Invalid evaluationId format");
+    }
     const {projectId} = getCurrentProject()
 
     const [{data: evaluationScenarios}, evaluation] = await Promise.all([
@@ -225,7 +228,11 @@
 
 // Comparison
 export const fetchAllComparisonResults = async (evaluationIds: string[]) => {
-    const scenarioGroups = await Promise.all(evaluationIds.map(fetchAllEvaluationScenarios))
+    const filteredIds = evaluationIds.filter(isValidUUID);
+    if (filteredIds.length === 0) {
+        throw new Error("No valid evaluation IDs provided.");
+    }
+    const scenarioGroups = await Promise.all(filteredIds.map(fetchAllEvaluationScenarios))
     const testset: TestSet = await fetchTestset(scenarioGroups[0][0].evaluation?.testset?.id)
 
     const inputsNameSet = new Set<string>()
EOF
@@ -197,6 +197,9 @@

// Evaluation Scenarios
export const fetchAllEvaluationScenarios = async (evaluationId: string) => {
if (!isValidUUID(evaluationId)) {
throw new Error("Invalid evaluationId format");
}
const {projectId} = getCurrentProject()

const [{data: evaluationScenarios}, evaluation] = await Promise.all([
@@ -225,7 +228,11 @@

// Comparison
export const fetchAllComparisonResults = async (evaluationIds: string[]) => {
const scenarioGroups = await Promise.all(evaluationIds.map(fetchAllEvaluationScenarios))
const filteredIds = evaluationIds.filter(isValidUUID);
if (filteredIds.length === 0) {
throw new Error("No valid evaluation IDs provided.");
}
const scenarioGroups = await Promise.all(filteredIds.map(fetchAllEvaluationScenarios))
const testset: TestSet = await fetchTestset(scenarioGroups[0][0].evaluation?.testset?.id)

const inputsNameSet = new Set<string>()
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines 18 to 23
const res = await fetch(
`${apiUrl}/preview/evaluations/scenarios/${scenarioId}?project_id=${projectId}`,
{
headers: {Authorization: `Bearer ${jwt}`},
},
)

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI 2 months ago

The best way to fix the SSRF issue is to validate or sanitize the scenarioId before including it as a path segment in the outgoing request URL. As a scenario ID should be a UUID, the fix is to ensure the input matches a valid UUID pattern before any outgoing request is made. If the validation fails, the code should avoid issuing the request (and possibly show an error or fallback accordingly). The changes should happen in the function updateScenarioStatusRemote in web/ee/src/services/evaluations/workerUtils.ts where scenarioId is used, and anywhere else in the request chain where scenario IDs enter the system from user input, although the main risk is at the HTTP request boundary. To implement, add a simple UUID validation method (e.g., using regex), and early-return/safeguard if the validation fails.

Specifically:

  • Add a isValidUUID helper to validate scenarioId.
  • At the beginning of updateScenarioStatusRemote, check that scenarioId is a valid UUID; if not, throw or early-return (do not send fetch request).
  • Ensure imports (no new external dependencies needed, use regex).
  • This change is all within the file web/ee/src/services/evaluations/workerUtils.ts.

Suggested changeset 1
web/ee/src/services/evaluations/workerUtils.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/web/ee/src/services/evaluations/workerUtils.ts b/web/ee/src/services/evaluations/workerUtils.ts
--- a/web/ee/src/services/evaluations/workerUtils.ts
+++ b/web/ee/src/services/evaluations/workerUtils.ts
@@ -3,6 +3,11 @@
 import {EvaluationStatus} from "@/oss/lib/Types"
 import {BaseResponse} from "@/oss/lib/Types"
 
+// Helper to validate UUID v4 (standard pattern)
+function isValidUUID(uuid: string): boolean {
+    return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(uuid)
+}
+
 /**
  * Update scenario status from a WebWorker / non-axios context.
  */
@@ -13,6 +18,11 @@
     status: EvaluationStatus,
     projectId: string,
 ): Promise<void> {
+    // Validate scenarioId
+    if (!isValidUUID(scenarioId)) {
+        // Optionally log or handle error
+        throw new Error("Invalid scenarioId format");
+    }
     try {
         // 1. fetch full scenario (backend requires full object on PATCH)
         const res = await fetch(
EOF
@@ -3,6 +3,11 @@
import {EvaluationStatus} from "@/oss/lib/Types"
import {BaseResponse} from "@/oss/lib/Types"

// Helper to validate UUID v4 (standard pattern)
function isValidUUID(uuid: string): boolean {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(uuid)
}

/**
* Update scenario status from a WebWorker / non-axios context.
*/
@@ -13,6 +18,11 @@
status: EvaluationStatus,
projectId: string,
): Promise<void> {
// Validate scenarioId
if (!isValidUUID(scenarioId)) {
// Optionally log or handle error
throw new Error("Invalid scenarioId format");
}
try {
// 1. fetch full scenario (backend requires full object on PATCH)
const res = await fetch(
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines 87 to 88
return await axios
.get(`${getAgentaApiUrl()}/human-evaluations/${evaluationId}?project_id=${projectId}`)

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.
The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI 2 months ago

To fix this problem, validate or sanitize the evaluationId value before using it in the outbound HTTP request URL. The recommended approach is to ensure it matches the expected format for an evaluation ID (such as a MongoDB ObjectID, a UUID, or an allowed-string regular expression).
Modify the function fetchLoadEvaluation in web/ee/src/services/human-evaluations/api/index.ts so that before the API call, it checks that evaluationId consists only of permitted characters (for example, /^[a-zA-Z0-9_-]{1,36}$/ or a stricter pattern, depending on the ID system in use).
If validation fails, log the error and return null or throw a harmless error.
To implement this, you may need to add an in-file helper function for validation, such as isSafeEvaluationId.

Edits will be made only to the fetchLoadEvaluation function in web/ee/src/services/human-evaluations/api/index.ts. No changes to other files are needed.


Suggested changeset 1
web/ee/src/services/human-evaluations/api/index.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/web/ee/src/services/human-evaluations/api/index.ts b/web/ee/src/services/human-evaluations/api/index.ts
--- a/web/ee/src/services/human-evaluations/api/index.ts
+++ b/web/ee/src/services/human-evaluations/api/index.ts
@@ -81,8 +81,18 @@
     return results
 }
 
+// Validate evaluationId to prevent SSRF attacks
+function isSafeEvaluationId(id: string): boolean {
+    // Accept only alphanumeric, hyphens, underscores, and max 36 chars (adjust as needed)
+    return /^[a-zA-Z0-9_-]{1,36}$/.test(id)
+}
+
 export const fetchLoadEvaluation = async (evaluationId: string) => {
     const {projectId} = getCurrentProject()
+    if (!isSafeEvaluationId(evaluationId)) {
+        console.error(`Rejected unsafe evaluationId: ${evaluationId}`)
+        return null
+    }
     try {
         return await axios
             .get(`${getAgentaApiUrl()}/human-evaluations/${evaluationId}?project_id=${projectId}`)
EOF
@@ -81,8 +81,18 @@
return results
}

// Validate evaluationId to prevent SSRF attacks
function isSafeEvaluationId(id: string): boolean {
// Accept only alphanumeric, hyphens, underscores, and max 36 chars (adjust as needed)
return /^[a-zA-Z0-9_-]{1,36}$/.test(id)
}

export const fetchLoadEvaluation = async (evaluationId: string) => {
const {projectId} = getCurrentProject()
if (!isSafeEvaluationId(evaluationId)) {
console.error(`Rejected unsafe evaluationId: ${evaluationId}`)
return null
}
try {
return await axios
.get(`${getAgentaApiUrl()}/human-evaluations/${evaluationId}?project_id=${projectId}`)
Copilot is powered by AI and may make mistakes. Always verify output.
return fromEvaluationResponseToEvaluation(responseData.data)
})
} catch (error) {
console.error(`Error fetching evaluation ${evaluationId}:`, error)

Check failure

Code scanning / CodeQL

Use of externally-controlled format string High

Format string depends on a
user-provided value
.
Format string depends on a
user-provided value
.

Copilot Autofix

AI about 2 months ago

The best way to fix this is to avoid injecting untrusted input directly into the format string passed to console.error. Instead, use a static format string, and pass user data (such as evaluationId) as a variable argument. For this case, change:

console.error(`Error fetching evaluation ${evaluationId}:`, error)

to:

console.error("Error fetching evaluation %s:", evaluationId, error)

This way, even if evaluationId includes format characters (like %d), they're treated as string data, not format specifiers. No new imports are needed, and the change is entirely in web/ee/src/services/human-evaluations/api/index.ts, line 93.


Suggested changeset 1
web/ee/src/services/human-evaluations/api/index.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/web/ee/src/services/human-evaluations/api/index.ts b/web/ee/src/services/human-evaluations/api/index.ts
--- a/web/ee/src/services/human-evaluations/api/index.ts
+++ b/web/ee/src/services/human-evaluations/api/index.ts
@@ -90,7 +90,7 @@
                 return fromEvaluationResponseToEvaluation(responseData.data)
             })
     } catch (error) {
-        console.error(`Error fetching evaluation ${evaluationId}:`, error)
+        console.error("Error fetching evaluation %s:", evaluationId, error)
         return null
     }
 }
EOF
@@ -90,7 +90,7 @@
return fromEvaluationResponseToEvaluation(responseData.data)
})
} catch (error) {
console.error(`Error fetching evaluation ${evaluationId}:`, error)
console.error("Error fetching evaluation %s:", evaluationId, error)
return null
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines 115 to 118
return await axios
.get(
`${getAgentaApiUrl()}/human-evaluations/${evaluationTableId}/evaluation_scenarios?project_id=${projectId}`,
)

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.
The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI 2 months ago

To fix this SSRF risk, restrict the format and allowed values of evaluationTableId before it is used to construct the outgoing HTTP request path. The best way is to validate that evaluationTableId conforms to the allowed pattern (such as a UUID or a database ObjectId, if that's the case), and reject or return an error if not. This ensures that client-provided input cannot cause a request to an unintended endpoint. We can implement a function to validate the format (e.g., a UUID validator) and call it before making the outgoing request. If evaluationTableId does not match the allowed pattern, throw an error or reject the request early.

This fix affects fetchAllLoadEvaluationsScenarios in web/ee/src/services/human-evaluations/api/index.ts, adding a validation step on evaluationTableId before using it in the URL.


Suggested changeset 1
web/ee/src/services/human-evaluations/api/index.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/web/ee/src/services/human-evaluations/api/index.ts b/web/ee/src/services/human-evaluations/api/index.ts
--- a/web/ee/src/services/human-evaluations/api/index.ts
+++ b/web/ee/src/services/human-evaluations/api/index.ts
@@ -106,12 +106,22 @@
     return response.data
 }
 
+// Helper to validate UUID v4 – adjust the pattern if you use a different id format
+function isValidUUID(id: string): boolean {
+    const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
+    return uuidV4Regex.test(id)
+}
+
 export const fetchAllLoadEvaluationsScenarios = async (
     evaluationTableId: string,
     evaluation: Evaluation,
 ) => {
     const {projectId} = getCurrentProject()
 
+    if (!isValidUUID(evaluationTableId)) {
+        throw new Error("Invalid evaluationTableId")
+    }
+
     return await axios
         .get(
             `${getAgentaApiUrl()}/human-evaluations/${evaluationTableId}/evaluation_scenarios?project_id=${projectId}`,
EOF
@@ -106,12 +106,22 @@
return response.data
}

// Helper to validate UUID v4 – adjust the pattern if you use a different id format
function isValidUUID(id: string): boolean {
const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
return uuidV4Regex.test(id)
}

export const fetchAllLoadEvaluationsScenarios = async (
evaluationTableId: string,
evaluation: Evaluation,
) => {
const {projectId} = getCurrentProject()

if (!isValidUUID(evaluationTableId)) {
throw new Error("Invalid evaluationTableId")
}

return await axios
.get(
`${getAgentaApiUrl()}/human-evaluations/${evaluationTableId}/evaluation_scenarios?project_id=${projectId}`,
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines 194 to 197
const response = await axios.put(
`${getAgentaApiUrl()}/human-evaluations/${evaluationTableId}/evaluation_scenario/${evaluationScenarioId}/${evaluationType}?project_id=${projectId}`,
data,
)

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI 2 months ago

To fix this issue, we need to ensure that the evaluationScenarioId parameter used in the API endpoint URL is strictly validated or sanitized before use, so that only legitimate values (e.g., UUIDs, fixed-format IDs, etc.) are allowed. In general, the fix involves limiting path or query parameters inserted into outbound request URLs to known-safe, validated input values. The best course is to validate evaluationScenarioId (and possibly evaluationTableId) before making the API call with them: ensure they match the expected pattern (e.g., a UUID using regex, or that they appear in a known, safe list of scenario IDs for that evaluation). This validation should be added immediately prior to calling axios.put in updateEvaluationScenario in web/ee/src/services/human-evaluations/api/index.ts.

To implement this, add a simple validation—using a regex for UUIDs (assuming that's the allowed format, which is exceedingly common for such IDs in typical API design) or restricting to a known-safe list. If the validation fails, throw an error or return early, preventing the potentially unsafe API call.


Suggested changeset 1
web/ee/src/services/human-evaluations/api/index.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/web/ee/src/services/human-evaluations/api/index.ts b/web/ee/src/services/human-evaluations/api/index.ts
--- a/web/ee/src/services/human-evaluations/api/index.ts
+++ b/web/ee/src/services/human-evaluations/api/index.ts
@@ -189,6 +189,14 @@
     data: GenericObject,
     evaluationType: EvaluationType,
 ) => {
+    // Only allow scenario and table IDs that are UUIDs.
+    const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
+    if (
+        !uuidRegex.test(evaluationTableId) ||
+        !uuidRegex.test(evaluationScenarioId)
+    ) {
+        throw new Error("Invalid evaluationTableId or evaluationScenarioId");
+    }
     const {projectId} = getCurrentProject()
 
     const response = await axios.put(
EOF
@@ -189,6 +189,14 @@
data: GenericObject,
evaluationType: EvaluationType,
) => {
// Only allow scenario and table IDs that are UUIDs.
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (
!uuidRegex.test(evaluationTableId) ||
!uuidRegex.test(evaluationScenarioId)
) {
throw new Error("Invalid evaluationTableId or evaluationScenarioId");
}
const {projectId} = getCurrentProject()

const response = await axios.put(
Copilot is powered by AI and may make mistakes. Always verify output.
@junaway junaway changed the title [STALE] [feature] Add ingestion batching (spans and meters) [stale] [feature] Add ingestion batching (spans and meters) Nov 14, 2025
@junaway junaway changed the base branch from main to chore/unify-redis November 20, 2025 18:00
@junaway junaway changed the base branch from chore/unify-redis to release/v0.61.0 November 20, 2025 18:02
@junaway junaway closed this Dec 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants