security: fix code scanning alerts — sanitize error messages and suppress false-positive hash warnings

- Sanitize error messages in errorResponse() and cursor buildErrorResponse() to strip stack traces before sending to client (fixes js/stack-trace-exposure)
- Add explicit CodeQL suppression comments for intentional SHA-256 usage in API key hashing (fast O(1) lookup, not password storage) and deterministic UUID generation (fixes js/insufficient-password-hash false positives)
This commit is contained in:
diegosouzapw
2026-05-10 10:41:16 -03:00
parent 58e5ce3900
commit 12b254097b
4 changed files with 30 additions and 6 deletions

View File

@@ -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
);
}

View File

@@ -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" },
});

View File

@@ -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",
},

View File

@@ -455,7 +455,12 @@ function parseIsBanned(value: unknown): boolean {
async function hashKey(key: string): Promise<string> {
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[] = []) {