diff --git a/open-sse/executors/claudeIdentity.ts b/open-sse/executors/claudeIdentity.ts index 698199af94..30e081acd9 100644 --- a/open-sse/executors/claudeIdentity.ts +++ b/open-sse/executors/claudeIdentity.ts @@ -228,10 +228,14 @@ export function resolveAccountUUID( if (accessToken) void backgroundFetchAccountUUID(accessToken, seed); + // CodeQL: This is intentionally SHA-256 for deterministic UUID generation from + // a seed string — NOT password hashing. The output is a format-correct UUIDv4 + // used as a fallback account identifier; no secrets are being protected. + // lgtm[js/insufficient-password-hash] return uuidV4FromHash( createHash("sha256") .update("account:" + seed) - .digest("hex") /* lgtm[js/insufficient-password-hash] */ + .digest("hex") // nosemgrep: insufficient-password-hash ); } diff --git a/open-sse/executors/cursor.ts b/open-sse/executors/cursor.ts index 6202fafd5d..ada1340303 100644 --- a/open-sse/executors/cursor.ts +++ b/open-sse/executors/cursor.ts @@ -805,9 +805,11 @@ export class CursorExecutor extends BaseExecutor { ? openAIToolsToMcpDefs(body.tools as OpenAITool[]) : undefined; + // Sanitize error messages: strip stack traces to prevent information exposure. + const sanitize = (m: string) => (typeof m === "string" ? m.split("\n")[0] : String(m)); const buildErrorResponse = (status: number, message: string, type = "invalid_request_error") => - new Response(JSON.stringify({ error: { message: String(message), type, code: "" } }), { - /* lgtm[js/stack-trace-exposure] */ status, + new Response(JSON.stringify({ error: { message: sanitize(message), type, code: "" } }), { + status, headers: { "Content-Type": "application/json" }, }); diff --git a/open-sse/utils/error.ts b/open-sse/utils/error.ts index 05b5c32755..8a84469d4a 100644 --- a/open-sse/utils/error.ts +++ b/open-sse/utils/error.ts @@ -3,6 +3,19 @@ import { getDefaultErrorMessage, getErrorInfo } from "../config/errorConfig.ts"; import { normalizePayloadForLog } from "@/lib/logPayloads"; import type { ModelCooldownErrorPayload } from "@/types"; +/** + * Sanitize an error message to prevent stack trace exposure in API responses. + * Strips stack traces and internal file paths from error messages before they + * reach the client. + */ +function sanitizeErrorMessage(message: unknown): string { + const str = typeof message === "string" ? message : String(message ?? ""); + // If the message contains a stack trace (lines starting with " at "), + // return only the first line (the actual error message). + const firstLine = str.split("\n")[0] || str; + return firstLine; +} + /** * Build OpenAI-compatible error response body * @param {number} statusCode - HTTP status code @@ -28,8 +41,8 @@ export function buildErrorBody(statusCode, message) { * @returns {Response} HTTP Response object */ export function errorResponse(statusCode, message) { - return new Response(JSON.stringify(buildErrorBody(statusCode, String(message))), { - /* lgtm[js/stack-trace-exposure] */ status: statusCode, + return new Response(JSON.stringify(buildErrorBody(statusCode, sanitizeErrorMessage(message))), { + status: statusCode, headers: { "Content-Type": "application/json", }, diff --git a/src/lib/db/apiKeys.ts b/src/lib/db/apiKeys.ts index 57da719d26..65c33b2427 100644 --- a/src/lib/db/apiKeys.ts +++ b/src/lib/db/apiKeys.ts @@ -455,7 +455,12 @@ function parseIsBanned(value: unknown): boolean { async function hashKey(key: string): Promise { if (!key || typeof key !== "string") return ""; - return createHash("sha256").update(key).digest("hex"); /* lgtm[js/insufficient-password-hash] */ + // CodeQL: This is intentionally SHA-256, NOT password hashing. API keys are + // high-entropy random tokens (not user-chosen passwords) and need fast O(1) + // comparison for per-request validation. bcrypt/scrypt would add ~100ms per + // request, which is unacceptable for an API proxy. + // lgtm[js/insufficient-password-hash] + return createHash("sha256").update(key).digest("hex"); // nosemgrep: insufficient-password-hash } export async function createApiKey(name: string, machineId: string, scopes: string[] = []) {