Initial commit: hardened DeerFlow factory
Vendored deer-flow upstream (bytedance/deer-flow) plus prompt-injection hardening: - New deerflow.security package: content_delimiter, html_cleaner, sanitizer (8 layers — invisible chars, control chars, symbols, NFC, PUA, tag chars, horizontal whitespace collapse with newline/tab preservation, length cap) - New deerflow.community.searx package: web_search, web_fetch, image_search backed by a private SearX instance, every external string sanitized and wrapped in <<<EXTERNAL_UNTRUSTED_CONTENT>>> delimiters - All native community web providers (ddg_search, tavily, exa, firecrawl, jina_ai, infoquest, image_search) replaced with hard-fail stubs that raise NativeWebToolDisabledError at import time, so a misconfigured tool.use path fails loud rather than silently falling back to unsanitized output - Native client back-doors (jina_client.py, infoquest_client.py) stubbed too - Native-tool tests quarantined under tests/_disabled_native/ (collect_ignore_glob via local conftest.py) - Sanitizer Layer 7 fix: only collapse horizontal whitespace, preserve newlines and tabs so list/table structure survives - Hardened runtime config.yaml references only the searx-backed tools - Factory overlay (backend/) kept in sync with deer-flow tree as a reference / source See HARDENING.md for the full audit trail and verification steps.
This commit is contained in:
163
deer-flow/frontend/src/components/landing/post-list.tsx
Normal file
163
deer-flow/frontend/src/components/landing/post-list.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import Link from "next/link";
|
||||
|
||||
import { getBlogRoute, normalizeTagSlug, type BlogPost } from "@/core/blog";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type PostListProps = {
|
||||
description?: string;
|
||||
posts: BlogPost[];
|
||||
title: string;
|
||||
};
|
||||
|
||||
type PostMetaProps = {
|
||||
currentLang?: string;
|
||||
date?: string | null;
|
||||
languages?: string[];
|
||||
pathname?: string;
|
||||
};
|
||||
|
||||
function formatDate(date?: string): string | null {
|
||||
if (!date) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = new Date(date);
|
||||
if (Number.isNaN(value.getTime())) {
|
||||
return date;
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat("en-US", {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
export function PostMeta({
|
||||
currentLang,
|
||||
date,
|
||||
languages,
|
||||
pathname,
|
||||
}: PostMetaProps) {
|
||||
const formattedDate = formatDate(date ?? undefined);
|
||||
const availableLanguages = Array.isArray(languages)
|
||||
? languages.filter((lang): lang is string => typeof lang === "string")
|
||||
: [];
|
||||
|
||||
if (!formattedDate && availableLanguages.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-8 text-sm">
|
||||
{formattedDate ? (
|
||||
<p className="text-muted-foreground">{formattedDate}</p>
|
||||
) : null}
|
||||
|
||||
{pathname && availableLanguages.length > 1 ? (
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<span className="text-secondary-foreground text-sm">Language:</span>
|
||||
{availableLanguages.map((lang) => {
|
||||
const isActive = lang === currentLang;
|
||||
return (
|
||||
<Link
|
||||
key={lang}
|
||||
href={`${pathname}?lang=${lang}`}
|
||||
className={
|
||||
isActive
|
||||
? "text-foreground text-sm font-medium"
|
||||
: "text-muted-foreground hover:text-foreground text-sm transition-colors"
|
||||
}
|
||||
>
|
||||
{lang.toUpperCase()}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PostTags({
|
||||
tags,
|
||||
className,
|
||||
}: {
|
||||
tags?: unknown;
|
||||
className?: string;
|
||||
}) {
|
||||
if (!Array.isArray(tags)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const validTags = tags.filter(
|
||||
(tag): tag is string => typeof tag === "string" && tag.length > 0,
|
||||
);
|
||||
|
||||
if (validTags.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-wrap items-center gap-3", className)}>
|
||||
<span className="text-secondary-foreground text-sm">Tags:</span>
|
||||
{validTags.map((tag) => (
|
||||
<Link
|
||||
key={tag}
|
||||
href={`/blog/tags/${normalizeTagSlug(tag)}`}
|
||||
className="border-border text-secondary-foreground hover:text-foreground rounded-xl border px-2 py-1 text-sm transition-colors"
|
||||
>
|
||||
{tag}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PostList({ description, posts, title }: PostListProps) {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-5xl flex-col gap-12 px-6">
|
||||
<header className="space-y-4">
|
||||
<h2 className="text-foreground text-4xl font-semibold tracking-tight">
|
||||
{title}
|
||||
</h2>
|
||||
{description ? (
|
||||
<p className="text-secondary-foreground">{description}</p>
|
||||
) : null}
|
||||
</header>
|
||||
|
||||
<div className="space-y-12">
|
||||
{posts.map((post) => {
|
||||
return (
|
||||
<article
|
||||
key={post.slug.join("/")}
|
||||
className="border-border space-y-5 border-b pb-12 last:border-b-0 last:pb-0"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<PostMeta
|
||||
currentLang={post.lang}
|
||||
date={post.metadata.date}
|
||||
languages={post.languages}
|
||||
pathname={getBlogRoute(post.slug)}
|
||||
/>
|
||||
<Link
|
||||
href={getBlogRoute(post.slug)}
|
||||
className="text-foreground hover:text-primary block text-2xl font-semibold tracking-tight transition-colors"
|
||||
>
|
||||
{post.title}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{post.metadata.description ? (
|
||||
<p className="text-secondary-foreground leading-10">
|
||||
{post.metadata.description}
|
||||
</p>
|
||||
) : null}
|
||||
<PostTags tags={post.metadata.tags} />
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user