Release v3.8.30 (#4267)

Release v3.8.30 — see CHANGELOG.md [3.8.30] for the full release notes.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-20 07:09:43 -03:00
committed by GitHub
parent ab8096071c
commit db362b0126
356 changed files with 14268 additions and 1140 deletions

View File

@@ -30,6 +30,7 @@ import { checkRateLimit, RateLimitRule } from "./rateLimiter";
import { resolveEndpointCategory } from "@/shared/constants/endpointCategories";
import { resolveQuotaKeyScope } from "@/lib/quota/quotaKey";
import { isQuotaModelName, parseQuotaModelName } from "@/lib/quota/quotaModelNaming";
import { buildApiKeyUsageLimitPolicyRejection } from "@/lib/usage/apiKeyUsageLimits";
// Default to no per-key request cap. API keys can still opt into explicit
// limits via Settings/API Keys, while provider/account quota controls remain
@@ -92,6 +93,9 @@ export interface ApiKeyMetadata {
allowedEndpoints?: string[];
disableNonPublicModels?: boolean;
allowUsageCommand?: boolean;
usageLimitEnabled?: boolean;
dailyUsageLimitUsd?: number | null;
weeklyUsageLimitUsd?: number | null;
}
/**
@@ -379,6 +383,31 @@ export async function enforceApiKeyPolicy(
}
}
// ── Check 2.1: per-key USD fair usage cap ──
if (apiKeyInfo.usageLimitEnabled === true) {
try {
const usageLimitRejection = await buildApiKeyUsageLimitPolicyRejection(request, {
id: apiKeyInfo.id,
usageLimitEnabled: apiKeyInfo.usageLimitEnabled,
dailyUsageLimitUsd: apiKeyInfo.dailyUsageLimitUsd,
weeklyUsageLimitUsd: apiKeyInfo.weeklyUsageLimitUsd,
});
if (usageLimitRejection) {
return { apiKey, apiKeyInfo, rejection: usageLimitRejection };
}
} catch (error) {
log.error("API_POLICY", "API key USD usage limit check failed. Request blocked.", { error });
return {
apiKey,
apiKeyInfo,
rejection: errorResponse(
HTTP_STATUS.SERVICE_UNAVAILABLE,
"API key usage limit unavailable"
),
};
}
}
// ── Check 2.5: Endpoint restriction ──
if (apiKeyInfo.allowedEndpoints && apiKeyInfo.allowedEndpoints.length > 0) {
try {

View File

@@ -47,6 +47,20 @@ const INJECTION_PATTERNS = [
},
];
/**
* Maximum number of characters scanned for prompt-injection patterns.
*
* The guard joins every message/system string into one buffer and runs several
* regexes over it on every chat request. With no cap that is O(body) CPU on the
* hot path — at high concurrency with 300 KB bodies it is a self-inflicted
* latency/GC source. Injection directives sit near the top of a prompt, so
* scanning hundreds of KB of pasted code / RAG context buys only CPU. We bound
* the scan to the first 16 KB (generous: real directives are far shorter) before
* the regex loop. The 10 MB body-size cap that protects ingestion lives
* elsewhere; this constant only bounds the regex scan. Refs #3932 / #4041.
*/
export const MAX_INJECTION_SCAN_BYTES = 16 * 1024;
// ─── PII Patterns ────────────────────────────────────────────────────
/** @type {Array<{name: string, pattern: RegExp, replacement: string}>} */
@@ -168,8 +182,13 @@ function extractMessageContents(body) {
*/
function detectInjection(text) {
const detections = [];
// Bound the regex scan to the first 16 KB — see MAX_INJECTION_SCAN_BYTES
// (hot-path perf, #3932 / #4041). Slice before the loop so each pattern only
// ever scans the capped prefix, never the full (possibly hundreds of KB) body.
const scanText =
text.length > MAX_INJECTION_SCAN_BYTES ? text.slice(0, MAX_INJECTION_SCAN_BYTES) : text;
for (const rule of INJECTION_PATTERNS) {
const match = text.match(rule.pattern);
const match = scanText.match(rule.pattern);
if (match) {
detections.push({
pattern: rule.name,