security: fix rate limiting, error message leaks, decrypt crash, key preview

- H1: Add checkRateLimit to OAuth authorize/callback/disconnect + admin AI settings
- H2: Fix decrypt("") crash in provider.ts onTokenRefresh by using decrypt() directly
- H3: Truncate error messages in codex-oauth, codex-client, openai-client to prevent leaking upstream response bodies
- M5: Fix keyPreview to show last 4 chars of decrypted key instead of ciphertext prefix

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.6 committed 2026-04-06 00:47:05 +02:00
1 parent 50911ad801
commit d3149170cd
8 files changed
+33 -20

No files matched your search

+9 -5
View File
@@ -3,7 +3,8 @@ import { apiHandler, requireAdmin, HttpError } from "@/lib/api-handler";
import { prisma } from "@/lib/db";
import { auditLog } from "@/lib/auth/audit";
import { apiSuccess, getClientIp, safeJson } from "@/lib/api-response";
import { encrypt } from "@/lib/crypto";
import { encrypt, decrypt } from "@/lib/crypto";
import { checkRateLimit } from "@/lib/rate-limit";
import { annotate } from "@/lib/logging/context";
export const dynamic = "force-dynamic";
@@ -22,8 +23,8 @@ export const GET = apiHandler(async () => {
return apiSuccess({
hasKey,
keyPreview: hasKey && settings?.adminAiKeyEncrypted
? `${settings.adminAiKeyEncrypted.slice(0, 7)}...`
keyPreview: settings?.adminAiKeyEncrypted
? `...${decrypt(settings.adminAiKeyEncrypted).slice(-4)}`
: null,
model,
baseUrl,
@@ -34,6 +35,9 @@ export const PUT = apiHandler(async (request: NextRequest) => {
const { user } = await requireAdmin();
annotate({ action: { name: "admin.ai-settings.update" } });
const rl = await checkRateLimit("admin-ai-settings", 10, 60_000);
if (!rl.allowed) throw new HttpError(429, "Too many requests");
const { data: body, error: jsonError } = await safeJson(request);
if (jsonError) return jsonError;
@@ -94,8 +98,8 @@ export const PUT = apiHandler(async (request: NextRequest) => {
return apiSuccess({
hasKey,
keyPreview: hasKey && settings.adminAiKeyEncrypted
? `${settings.adminAiKeyEncrypted.slice(0, 7)}...`
keyPreview: settings.adminAiKeyEncrypted
? `...${decrypt(settings.adminAiKeyEncrypted).slice(-4)}`
: null,
model: settings.adminAiModel,
baseUrl: settings.adminAiBaseUrl,