diff --git a/app/admin/community/page.tsx b/app/admin/community/page.tsx index e78a129..04d4bc8 100644 --- a/app/admin/community/page.tsx +++ b/app/admin/community/page.tsx @@ -14,7 +14,7 @@ import { useEffect, useState } from "react"; import { AdminGuard } from "@/app/admin/events/AdminGuard"; import type { SharedLinkView } from "@/app/feed/types"; -import { sanitizeExternalUrl } from "@/lib/url-safety"; +import { sanitizeExternalUrl, sanitizeMediaUrl } from "@/lib/url-safety"; import { approveLink, listPendingLinks, rejectLink } from "./lib"; export default function AdminCommunityPage() { @@ -134,19 +134,23 @@ function AdminCommunityInner() { 图床防盗链会检查 Referer,非本站来源返回"未经允许"裂图。 next/image 的 remotePatterns 限制外站域名也一并规避。 */}
- {link.ogCover ? ( - // eslint-disable-next-line @next/next/no-img-element - {link.ogTitle - ) : ( - - {link.host[0]?.toUpperCase() ?? "?"} - - )} + {(() => { + // defense-in-depth:过 sanitizeMediaUrl 拦 javascript:/data: 协议 + const safeCover = sanitizeMediaUrl(link.ogCover); + return safeCover ? ( + // eslint-disable-next-line @next/next/no-img-element + {link.ogTitle + ) : ( + + {link.host[0]?.toUpperCase() ?? "?"} + + ); + })()}
{/* 中:元信息 */} diff --git a/app/components/Hero.tsx b/app/components/Hero.tsx index fa1fa2d..41506bf 100644 --- a/app/components/Hero.tsx +++ b/app/components/Hero.tsx @@ -94,29 +94,26 @@ export async function Hero() {

{t("join.body")}

- {/* 双阅读入口:严肃文档 + 社区随手分享,视觉同构;投稿动作已在 Hero 左侧 Contribute/ShareLink */} + {/* 双阅读入口:严肃文档 + 社区随手分享,视觉同构;投稿动作已在 Hero 左侧 Contribute/ShareLink。 + 直接把 渲染成按钮样式,避免 + {t("cta.access")} - + {t("cta.feed")} diff --git a/app/feed/components/LinkCard.tsx b/app/feed/components/LinkCard.tsx index 7a71bcc..cd1c639 100644 --- a/app/feed/components/LinkCard.tsx +++ b/app/feed/components/LinkCard.tsx @@ -10,6 +10,7 @@ import { useTranslations } from "next-intl"; import type { SharedLinkView } from "@/app/feed/types"; import { ReportButton } from "@/app/feed/components/ReportButton"; import { Badge } from "@/components/ui/badge"; +import { sanitizeMediaUrl } from "@/lib/url-safety"; interface LinkCardProps { link: SharedLinkView; @@ -27,6 +28,8 @@ function getHostInitial(host: string): string { export function LinkCard({ link, categoryLabel, isLoggedIn }: LinkCardProps) { const t = useTranslations("feed.card"); + // defense-in-depth:过白名单协议拦 javascript:/data:,后端 UrlNormalizer 是第一道,这里是第二道 + const safeOgCover = sanitizeMediaUrl(link.ogCover); return (
  • @@ -39,14 +42,14 @@ export function LinkCard({ link, categoryLabel, isLoggedIn }: LinkCardProps) { aria-label={link.ogTitle ?? link.url} > {/* OG 封面 / 占位块 */} - {link.ogCover && !link.ogFetchFailed ? ( + {safeOgCover && !link.ogFetchFailed ? ( // next/image 全站 unoptimized:true,用 img 即可(与 events 页一致)。 // referrerPolicy="no-referrer":微信 mmbiz.qpic.cn 防盗链会检查 Referer, // 非 mp.weixin.qq.com 来源直接返回"未经允许使用"裂图;不发 Referer 时 // 反而放行(微信客户端内打开文章浏览器也不发 Referer)。 // eslint-disable-next-line @next/next/no-img-element