diff --git a/src/actions/admin-badge-upload.ts b/src/actions/admin-badge-upload.ts index b9f03d44..29d3ab41 100644 --- a/src/actions/admin-badge-upload.ts +++ b/src/actions/admin-badge-upload.ts @@ -60,7 +60,6 @@ export async function uploadBadge(formData: FormData): Promise { try { const buffer = Buffer.from(await file.arrayBuffer()); const gif = await toBadgeGif(buffer); - // eslint-disable-next-line security/detect-non-literal-fs-filename await writeFile(target, gif); } catch { back("error", "Could not process or write the badge file"); diff --git a/src/actions/admin-maintenance.ts b/src/actions/admin-maintenance.ts index 25716581..5ac9f646 100644 --- a/src/actions/admin-maintenance.ts +++ b/src/actions/admin-maintenance.ts @@ -32,7 +32,7 @@ async function upsertSetting(key: string, value: string): Promise { .values({ key, value, - // eslint-disable-next-line security/detect-object-injection -- key is one of 3 known const values + // key is one of 3 known const values comment: COMMENTS[key] ?? null, }) .onDuplicateKeyUpdate({ set: { value } }); diff --git a/src/actions/admin-radio-points.ts b/src/actions/admin-radio-points.ts index 44d9feba..5a82ae49 100644 --- a/src/actions/admin-radio-points.ts +++ b/src/actions/admin-radio-points.ts @@ -71,10 +71,10 @@ export async function savePoints(formData: FormData): Promise { POINTS_KEYS.map((key) => db .insert(WebsiteSetting) - // eslint-disable-next-line security/detect-object-injection -- key from POINTS_KEYS const + // key from POINTS_KEYS const .values({ key, value: values[key], comment: "Radio points" }) .onDuplicateKeyUpdate({ - // eslint-disable-next-line security/detect-object-injection -- key from POINTS_KEYS const + // key from POINTS_KEYS const set: { value: values[key] }, }), ), diff --git a/src/actions/admin-theme.ts b/src/actions/admin-theme.ts index 4089f315..7ffa2002 100644 --- a/src/actions/admin-theme.ts +++ b/src/actions/admin-theme.ts @@ -112,7 +112,7 @@ export async function saveTheme(formData: FormData): Promise { export async function applyPreset(formData: FormData): Promise { const staff = await requirePermission(PERMS.SETTINGS_EDIT); const name = String(formData.get("preset") ?? "").normalize("NFC"); - // eslint-disable-next-line security/detect-object-injection -- guarded by null check below + // guarded by null check below const preset = PRESETS[name]; if (!preset) redirect("/admin/theme"); diff --git a/src/actions/save-favicon.ts b/src/actions/save-favicon.ts index ff1c5b27..1e2239ed 100644 --- a/src/actions/save-favicon.ts +++ b/src/actions/save-favicon.ts @@ -31,9 +31,7 @@ export async function saveFavicon( } const buffer = upload.bytes; - // eslint-disable-next-line security/detect-non-literal-fs-filename await mkdir(baseDir, { recursive: true }); - // eslint-disable-next-line security/detect-non-literal-fs-filename await writeFile(filePath, buffer); const url = `/api/media/favicon/${filename}`; @@ -46,7 +44,6 @@ export async function saveFavicon( const oldPath = path.resolve(baseDir, oldName); if (oldPath.startsWith(baseDir + path.sep)) { try { - // eslint-disable-next-line security/detect-non-literal-fs-filename await unlink(oldPath); } catch { /* ignore if file doesn't exist */ @@ -85,7 +82,6 @@ export async function deleteFavicon(): Promise<{ const oldPath = path.resolve(baseDir, oldName); if (oldPath.startsWith(baseDir + path.sep)) { try { - // eslint-disable-next-line security/detect-non-literal-fs-filename await unlink(oldPath); } catch { /* ignore */ diff --git a/src/actions/save-logo.ts b/src/actions/save-logo.ts index 90653dd9..6c6c8395 100644 --- a/src/actions/save-logo.ts +++ b/src/actions/save-logo.ts @@ -29,9 +29,7 @@ export async function saveLogo( } const buffer = upload.bytes; - // eslint-disable-next-line security/detect-non-literal-fs-filename await mkdir(baseDir, { recursive: true }); - // eslint-disable-next-line security/detect-non-literal-fs-filename await writeFile(filePath, buffer); const url = `/api/media/logo/${filename}`; diff --git a/src/actions/theme-manager.ts b/src/actions/theme-manager.ts index 105bd5af..b0c35228 100644 --- a/src/actions/theme-manager.ts +++ b/src/actions/theme-manager.ts @@ -278,14 +278,12 @@ export async function uploadBackground(formData: FormData): Promise { let uploaded = false; try { - // eslint-disable-next-line security/detect-non-literal-fs-filename await mkdir(MEDIA_ROOT, { recursive: true }); const ext = file.name.split(".").pop() ?? "png"; const name = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${ext}`; const bytes = await file.arrayBuffer(); const filePath = resolveMediaPath(name); if (filePath.startsWith(MEDIA_ROOT + path.sep)) { - // eslint-disable-next-line security/detect-non-literal-fs-filename await writeFile(filePath, Buffer.from(bytes)); await writeSetting("background_image", `/api/media/${name}`); await writeSetting( @@ -319,7 +317,7 @@ export async function applyPresetManager(formData: FormData): Promise { const name = String(formData.get("preset") ?? "") .normalize("NFC") .trim(); - // eslint-disable-next-line security/detect-object-injection -- guarded by null check below + // guarded by null check below const preset = PRESETS[name]; if (!preset) redirect(MANAGER_PATH); diff --git a/src/app/(site)/apply/staff/page.tsx b/src/app/(site)/apply/staff/page.tsx index 7aa7df45..e974db01 100644 --- a/src/app/(site)/apply/staff/page.tsx +++ b/src/app/(site)/apply/staff/page.tsx @@ -142,7 +142,6 @@ export default async function ApplyStaffPage({
{team?.badge ? ( - /* eslint-disable-next-line @next/next/no-img-element */ {rankName}
{team.badge ? ( - /* eslint-disable-next-line @next/next/no-img-element */ {team.rankName} - {/* eslint-disable-next-line @next/next/no-img-element */} {badge.badgeName} is correct here. - // eslint-disable-next-line @next/next/no-img-element {guild.badge ? ( - // eslint-disable-next-line @next/next/no-img-element {`${guild.name} {g.badge ? ( - // eslint-disable-next-line @next/next/no-img-element {`${g.name} {imageSrc ? ( - // eslint-disable-next-line @next/next/no-img-element {c.imageUrl ? ( - // eslint-disable-next-line @next/next/no-img-element {article.image ? ( - // eslint-disable-next-line @next/next/no-img-element {badgeSrc ? ( - // eslint-disable-next-line @next/next/no-img-element {`${cat.name} {iconSrc ? ( - // eslint-disable-next-line @next/next/no-img-element
{badgeSrc ? ( - // eslint-disable-next-line @next/next/no-img-element {`${c.name} = { }; function describeState(state: string): { label: string; icon: string } { - // eslint-disable-next-line security/detect-object-injection -- STATE_LABELS has known keys, fallback provided + // STATE_LABELS has known keys, fallback provided return STATE_LABELS[state] ?? { label: state || "Unknown", icon: "🚪" }; } diff --git a/src/app/(site)/shop/page.tsx b/src/app/(site)/shop/page.tsx index 1caa8884..06ad8869 100644 --- a/src/app/(site)/shop/page.tsx +++ b/src/app/(site)/shop/page.tsx @@ -249,7 +249,6 @@ export default async function ShopPage({ }} > {a.iconUrl ? ( - // eslint-disable-next-line @next/next/no-img-element - {/* eslint-disable-next-line @next/next/no-img-element */} ( - {/* eslint-disable-next-line @next/next/no-img-element */} {campaign.image ? ( - // eslint-disable-next-line @next/next/no-img-element {c.image ? ( - // eslint-disable-next-line @next/next/no-img-element {c.name} ) : (
diff --git a/src/app/admin/guilds/[id]/page.tsx b/src/app/admin/guilds/[id]/page.tsx index e28af501..7771d7d1 100644 --- a/src/app/admin/guilds/[id]/page.tsx +++ b/src/app/admin/guilds/[id]/page.tsx @@ -96,7 +96,6 @@ export default async function AdminGuildDetailPage({
{guild.badge ? ( - // eslint-disable-next-line @next/next/no-img-element {g.id} {g.badge ? ( - // eslint-disable-next-line @next/next/no-img-element - {/* eslint-disable-next-line @next/next/no-img-element */} {t("photoAlt", { } function pickString(obj: Record, key: string): string | null { - // eslint-disable-next-line security/detect-object-injection -- only called with hardcoded keys + // only called with hardcoded keys const v = obj[key]; return typeof v === "string" && v.trim() !== "" ? v.trim() : null; } @@ -112,7 +112,7 @@ function findNumberDeep( if (typeof value === "number" && Number.isFinite(value)) return value; if (!isRecord(value)) return null; for (const key of keys) { - // eslint-disable-next-line security/detect-object-injection -- keys from hardcoded array + // keys from hardcoded array const v = value[key]; if (typeof v === "number" && Number.isFinite(v)) return v; if ( diff --git a/src/app/admin/rooms/[id]/furni/furni-client.tsx b/src/app/admin/rooms/[id]/furni/furni-client.tsx index 730bc5d5..55d0ab10 100644 --- a/src/app/admin/rooms/[id]/furni/furni-client.tsx +++ b/src/app/admin/rooms/[id]/furni/furni-client.tsx @@ -577,7 +577,6 @@ export function FurniClient({ room, items = [], canEdit }: Props) { )} {icon ? ( - // eslint-disable-next-line @next/next/no-img-element {item.name} {icon ? ( - // eslint-disable-next-line @next/next/no-img-element {g.base.name} [rk, rarityBoards[i]]), ) as Record; @@ -398,7 +398,7 @@ export async function GET(req: Request) { totalBadgesViewer = tb; achievementViewer = ach; rarityViewer = Object.fromEntries( - // eslint-disable-next-line security/detect-object-injection -- rk from rarityKeys const, i is array index + // rk from rarityKeys const, i is array index rarityKeys.map((rk, i) => [rk, rvs[i]]), ) as Record | undefined>; } @@ -414,7 +414,7 @@ export async function GET(req: Request) { viewerEntry: achievementViewer, }, rarity: Object.fromEntries( - // eslint-disable-next-line security/detect-object-injection -- rk from rarityKeys const + // rk from rarityKeys const rarityKeys.map((rk) => [ rk, { ...global.rarity[rk], viewerEntry: rarityViewer[rk] }, diff --git a/src/app/api/leaderboard/route.ts b/src/app/api/leaderboard/route.ts index ab579ab1..ad557953 100644 --- a/src/app/api/leaderboard/route.ts +++ b/src/app/api/leaderboard/route.ts @@ -110,7 +110,7 @@ export async function GET(req: Request) { async () => type === "credits" ? await loadCreditsRows() - : // eslint-disable-next-line security/detect-object-injection -- type validated to "diamonds"|"duckets" + : // type validated to "diamonds"|"duckets" await loadCurrencyRows(CURRENCY_TYPE[type]), // Rankings are re-sorted on every read; serving the previous order for a // minute keeps a busy leaderboard off the database. diff --git a/src/app/api/media/[...path]/route.ts b/src/app/api/media/[...path]/route.ts index 17ace14a..19ec7daf 100644 --- a/src/app/api/media/[...path]/route.ts +++ b/src/app/api/media/[...path]/route.ts @@ -51,7 +51,6 @@ export async function GET( headers: SECURITY_HEADERS, }); } - // eslint-disable-next-line security/detect-non-literal-fs-filename if (!existsSync(filePath)) { return new NextResponse("Not found", { status: 404, @@ -59,7 +58,6 @@ export async function GET( }); } - // eslint-disable-next-line security/detect-non-literal-fs-filename const bytes = await readFile(filePath); const mime: Record = { ".png": "image/png", @@ -76,7 +74,7 @@ export async function GET( headers: { ...SECURITY_HEADERS, ...(ext === ".svg" ? { "Content-Disposition": "attachment" } : {}), - // eslint-disable-next-line security/detect-object-injection -- ext validated against ALLOWED_EXT + // ext validated against ALLOWED_EXT "Content-Type": mime[ext] ?? "application/octet-stream", "Cache-Control": "public, max-age=3600, must-revalidate", }, diff --git a/src/app/api/media/route.ts b/src/app/api/media/route.ts index fe1fae3f..6818e2bd 100644 --- a/src/app/api/media/route.ts +++ b/src/app/api/media/route.ts @@ -12,11 +12,9 @@ import { PERMS } from "@/lib/permissions"; */ export const GET = withAdmin({ permission: PERMS.PAGES_EDIT }, async () => { const dir = MEDIA_ROOT; - // eslint-disable-next-line security/detect-non-literal-fs-filename if (!existsSync(dir)) { return apiOk({ files: [] }); } - // eslint-disable-next-line security/detect-non-literal-fs-filename const files = readdirSync(dir) .filter((f) => /\.(png|jpg|jpeg|gif|webp|svg|bmp)$/i.test(f)) .map((f) => { diff --git a/src/app/api/radio/listeners/route.ts b/src/app/api/radio/listeners/route.ts index 0defb1a8..455e2f1f 100644 --- a/src/app/api/radio/listeners/route.ts +++ b/src/app/api/radio/listeners/route.ts @@ -35,7 +35,7 @@ function findCount(value: unknown, depth = 0): number | null { "count", ]; for (const key of keys) { - // eslint-disable-next-line security/detect-object-injection -- keys from hardcoded array + // keys from hardcoded array const v = value[key]; if (typeof v === "number" && Number.isFinite(v)) return v; if ( diff --git a/src/components/admin/article-card.tsx b/src/components/admin/article-card.tsx index 464534fe..93694901 100644 --- a/src/components/admin/article-card.tsx +++ b/src/components/admin/article-card.tsx @@ -45,7 +45,6 @@ export function ArticleCard({
{confirmDialog}
- {/* eslint-disable-next-line @next/next/no-img-element */} {title}
{image && !imageError ? ( - // eslint-disable-next-line @next/next/no-img-element {t("imagePreview")} - {/* eslint-disable-next-line @next/next/no-img-element */} {item.itemName} - {/* eslint-disable-next-line @next/next/no-img-element */} {classname} - {/* eslint-disable-next-line @next/next/no-img-element */} {classname}
- {/* eslint-disable-next-line @next/next/no-img-element */} {f.name} - {/* eslint-disable-next-line @next/next/no-img-element */} {f.name} - {/* eslint-disable-next-line @next/next/no-img-element */} {item.itemName} | null>(null); - // eslint-disable-next-line security/detect-object-injection -- openIndex is numeric array index, guarded by null check + // openIndex is numeric array index, guarded by null check const active = openIndex !== null ? photos[openIndex] : null; const close = useCallback(() => { @@ -97,7 +97,6 @@ export function PhotoLightbox({ photos }: { photos: LightboxPhoto[] }) { }} aria-label={p.alt} > - {/* eslint-disable-next-line @next/next/no-img-element */} {p.alt} - {/* eslint-disable-next-line @next/next/no-img-element */} {active.alt} 0) { - // eslint-disable-next-line security/detect-object-injection -- ci increments within CHAR_ORDER bounds + // ci increments within CHAR_ORDER bounds chars.push({ char: CHAR_ORDER[ci], x: start, w }); ci++; } diff --git a/src/components/shared/avatar-image.tsx b/src/components/shared/avatar-image.tsx index 7868ee17..0a677074 100644 --- a/src/components/shared/avatar-image.tsx +++ b/src/components/shared/avatar-image.tsx @@ -31,7 +31,6 @@ export function AvatarImage({ (options?.headOnly ? "l" : max <= 40 ? "s" : max <= 80 ? "m" : "l"); return ( - // eslint-disable-next-line @next/next/no-img-element {alt} = { }; export function currencyLabel(type: CurrencyType): string { - // eslint-disable-next-line security/detect-object-injection -- type is CurrencyType enum + // type is CurrencyType enum return LABELS[type]; } diff --git a/src/lib/services/email.ts b/src/lib/services/email.ts index 877c252c..a128ae30 100644 --- a/src/lib/services/email.ts +++ b/src/lib/services/email.ts @@ -55,12 +55,10 @@ async function writeToFile( ): Promise { try { const logDir = resolve(process.cwd(), "storage", "logs"); - // eslint-disable-next-line security/detect-non-literal-fs-filename await mkdir(logDir, { recursive: true }); const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); const filename = `email-${timestamp}.html`; const content = `\n${html}`; - // eslint-disable-next-line security/detect-non-literal-fs-filename await writeFile(resolve(logDir, filename), content, "utf-8"); logger.info("Email written to file", { module: "email", filename }); return true; diff --git a/src/lib/services/ip-lookup.ts b/src/lib/services/ip-lookup.ts index 472052d0..d7bc12aa 100644 --- a/src/lib/services/ip-lookup.ts +++ b/src/lib/services/ip-lookup.ts @@ -59,7 +59,7 @@ export async function checkVpn(ip: string): Promise { string, { proxy?: string; type?: string } >; - // eslint-disable-next-line security/detect-object-injection -- ip is the API response key from proxycheck + // ip is the API response key from proxycheck const entry = d?.[ip]; if (entry?.proxy === "yes") return { blocked: true, reason: `${entry.type ?? "Proxy"} detected` }; diff --git a/src/lib/services/send-currency.ts b/src/lib/services/send-currency.ts index c567d4e4..a489e204 100644 --- a/src/lib/services/send-currency.ts +++ b/src/lib/services/send-currency.ts @@ -84,7 +84,7 @@ export async function sendCurrency( data: { credits: { increment: amount } }, }); } else { - // eslint-disable-next-line security/detect-object-injection -- type is CurrencyName union, not "credits" + // type is CurrencyName union, not "credits" const t = TYPE_VALUE[type]; await deps.db.usersCurrency.upsert({ where: { userId_type: { userId, type: t } }, diff --git a/src/lib/services/site-settings.ts b/src/lib/services/site-settings.ts index 10341b73..eb743a09 100644 --- a/src/lib/services/site-settings.ts +++ b/src/lib/services/site-settings.ts @@ -128,7 +128,7 @@ class SiteSettings { ): Promise { const map = await this.load(); if (map.has(key)) return map.get(key) as string; - // eslint-disable-next-line security/detect-object-injection -- guarded by `key in DEFAULTS` + // guarded by `key in DEFAULTS` if (key in DEFAULTS) return DEFAULTS[key] as string; return fallback; } diff --git a/src/lib/theme-custom-store.ts b/src/lib/theme-custom-store.ts index cadf562c..995d6b17 100644 --- a/src/lib/theme-custom-store.ts +++ b/src/lib/theme-custom-store.ts @@ -42,7 +42,7 @@ function fallbackFor(key: string): string { const preset = PRESETS["Atom (golden)"]; if (key.endsWith("_dark")) { const base = key.slice(0, -"_dark".length) as ThemeColorKey; - return preset.dark[base] ?? ""; // eslint-disable-line security/detect-object-injection -- key derived from internal THEME_COLOR_KEYS + return preset.dark[base] ?? ""; // key derived from internal THEME_COLOR_KEYS } if (key in preset.light) { return preset.light[key as ThemeColorKey] ?? ""; @@ -63,7 +63,7 @@ function fallbackFor(key: string): string { theme_preset: "Atom (golden)", ...BACKGROUND_DEFAULTS, }; - return extras[key] ?? ""; // eslint-disable-line security/detect-object-injection -- key derived from internal EXTRA_KEYS + return extras[key] ?? ""; // key derived from internal EXTRA_KEYS } export async function listCustomThemes(): Promise { @@ -106,9 +106,9 @@ export async function upsertCustomTheme( if (id) { const idx = themes.findIndex((t) => t.id === id); if (idx >= 0) { - themes[idx] = { ...themes[idx], name: trimmed, settings }; // eslint-disable-line security/detect-object-injection -- idx from findIndex over internal array + themes[idx] = { ...themes[idx], name: trimmed, settings }; // idx from findIndex over internal array await persist(themes); - return themes[idx]; // eslint-disable-line security/detect-object-injection -- idx from findIndex over internal array + return themes[idx]; // idx from findIndex over internal array } } const theme: CustomTheme = {