Öffentliche Landingpage mit Erklärung & Prüfregelwerk (Task #29)

Baut die Einstiegsseite (/) zu einer öffentlichen Landingpage um und verschiebt
das bisherige Dashboard auf eine eigene Route (/dashboard).

Änderungen:
- Neues schlankes Public-Layout (components/public-layout.tsx): Kopfbereich mit
  SkillGuard-Logo (Link -> /) und CTAs ("Zum Dashboard" -> /dashboard,
  "Skill prüfen" -> /pruefen), Inhaltsbereich, Footer mit Impressum/
  Haftungsausschluss-Links (aus Task #27 wiederverwendet).
- Neue Landingpage (pages/landing.tsx):
  - Hero mit Wertaussage und primären CTAs.
  - Abschnitt "Worin liegt das Risiko?" mit 6 verständlichen Problem-Karten
    (nicht vertrauenswürdiger Code, versteckte Anweisungen, Prompt-Injektion,
    Datenabfluss, Zugriff auf Geheimnisse, unkontrollierte Installation).
  - Abschnitt "Das Prüfregelwerk": lädt aktive Regeln live via useListRules,
    filtert enabled, rendert zwei getrennte Gruppen (Datenschutz / IT-Sicherheit)
    mit Achsen-/Schweregrad-Badge, "Was geprüft wird" (Regelbeschreibung) und
    "Warum das ein Risiko ist" (kuratierte Texte je ruleId mit Rückfall auf die
    Regelbeschreibung). Lade-/Fehler-/Leerzustände abgedeckt.
- App.tsx: Routing aufgeteilt – "/" nutzt PublicLayout+Landing, alle übrigen
  Routen bleiben im AppLayout (Sidebar); Dashboard nun unter /dashboard.
- layout.tsx: Sidebar-Logo verlinkt auf /, Dashboard-Link auf /dashboard mit
  angepasster Aktiv-Logik (startsWith("/dashboard")).

Hinweise:
- Keine Backend-/Schema-Änderungen; kuratierte Risikotexte leben im Frontend.
- Vorhandene TS-Fehler in admin/scan-* Seiten stammen aus anderen offenen Tasks
  (fehlende generierte API-Member) und sind nicht Teil dieser Änderung.
- Verifiziert via Screenshots (Landing + /dashboard) und tsc (eigene Dateien
  fehlerfrei).

Replit-Task-Id: 83cee1ab-a1fc-4106-be3c-52e1cebcc838
This commit is contained in:
amertensreplit 2026-06-11 01:26:49 +00:00
parent 9415e184dc
commit fc4d0d9d28
4 changed files with 345 additions and 18 deletions

View file

@ -3,8 +3,10 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "@/components/ui/toaster";
import { TooltipProvider } from "@/components/ui/tooltip";
import { AppLayout } from "@/components/layout";
import { PublicLayout } from "@/components/public-layout";
import NotFound from "@/pages/not-found";
import Landing from "@/pages/landing";
import Dashboard from "@/pages/dashboard";
import ScanForm from "@/pages/scan-form";
import ScanReport from "@/pages/scan-report";
@ -18,19 +20,28 @@ const queryClient = new QueryClient();
function Router() {
return (
<AppLayout>
<Switch>
<Route path="/" component={Dashboard} />
<Route path="/pruefen" component={ScanForm} />
<Route path="/berichte/:id" component={ScanReport} />
<Route path="/vergleich/:id/:otherId" component={ScanCompare} />
<Route path="/verlauf" component={ScanHistory} />
<Route path="/admin" component={Admin} />
<Route path="/impressum" component={Impressum} />
<Route path="/haftungsausschluss" component={Haftungsausschluss} />
<Route component={NotFound} />
</Switch>
</AppLayout>
<Switch>
<Route path="/">
<PublicLayout>
<Landing />
</PublicLayout>
</Route>
<Route>
<AppLayout>
<Switch>
<Route path="/dashboard" component={Dashboard} />
<Route path="/pruefen" component={ScanForm} />
<Route path="/berichte/:id" component={ScanReport} />
<Route path="/vergleich/:id/:otherId" component={ScanCompare} />
<Route path="/verlauf" component={ScanHistory} />
<Route path="/admin" component={Admin} />
<Route path="/impressum" component={Impressum} />
<Route path="/haftungsausschluss" component={Haftungsausschluss} />
<Route component={NotFound} />
</Switch>
</AppLayout>
</Route>
</Switch>
);
}

View file

@ -9,9 +9,11 @@ export function AppLayout({ children }: { children: React.ReactNode }) {
<SidebarProvider>
<div className="flex min-h-screen w-full bg-background text-foreground">
<Sidebar className="border-r border-sidebar-border bg-sidebar text-sidebar-foreground">
<SidebarHeader className="p-4 flex flex-row items-center gap-2">
<Shield className="w-6 h-6 text-sidebar-primary" />
<span className="font-bold text-lg tracking-tight">SkillGuard</span>
<SidebarHeader className="p-4">
<Link href="/" className="flex flex-row items-center gap-2">
<Shield className="w-6 h-6 text-sidebar-primary" />
<span className="font-bold text-lg tracking-tight">SkillGuard</span>
</Link>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
@ -19,8 +21,8 @@ export function AppLayout({ children }: { children: React.ReactNode }) {
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild isActive={location === "/"}>
<Link href="/">
<SidebarMenuButton asChild isActive={location.startsWith("/dashboard")}>
<Link href="/dashboard">
<LayoutDashboard className="w-4 h-4 mr-2" />
<span>Dashboard</span>
</Link>

View file

@ -0,0 +1,49 @@
import { Link } from "wouter";
import { Shield, Search, LayoutDashboard } from "lucide-react";
import { Button } from "@/components/ui/button";
export function PublicLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex min-h-screen w-full flex-col bg-background text-foreground">
<header className="sticky top-0 z-30 border-b border-border bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-3 sm:px-6">
<Link href="/" className="flex items-center gap-2">
<Shield className="h-6 w-6 text-sidebar-primary" />
<span className="text-lg font-bold tracking-tight">SkillGuard</span>
</Link>
<nav className="flex items-center gap-2">
<Button asChild variant="ghost" size="sm">
<Link href="/dashboard">
<LayoutDashboard className="mr-2 h-4 w-4" />
<span className="hidden sm:inline">Zum Dashboard</span>
<span className="sm:hidden">Dashboard</span>
</Link>
</Button>
<Button asChild size="sm">
<Link href="/pruefen">
<Search className="mr-2 h-4 w-4" />
Skill prüfen
</Link>
</Button>
</nav>
</div>
</header>
<main className="flex-1">{children}</main>
<footer className="border-t border-border">
<div className="mx-auto flex max-w-6xl flex-col items-center justify-between gap-2 px-4 py-6 text-xs text-muted-foreground sm:flex-row sm:px-6">
<span>© 2026 avameo GmbH</span>
<nav className="flex items-center gap-4">
<Link href="/impressum" className="transition-colors hover:text-foreground">
Impressum
</Link>
<Link href="/haftungsausschluss" className="transition-colors hover:text-foreground">
Haftungsausschluss
</Link>
</nav>
</div>
</footer>
</div>
);
}

View file

@ -0,0 +1,265 @@
import { useListRules, type Rule } from "@workspace/api-client-react";
import { Link } from "wouter";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { Button } from "@/components/ui/button";
import { AxisBadge, SeverityBadge } from "@/components/ui-helpers";
import {
Shield,
ShieldCheck,
Search,
LayoutDashboard,
EyeOff,
Syringe,
Upload,
KeyRound,
FileWarning,
Lock,
ShieldAlert,
} from "lucide-react";
const RISK_EXPLANATIONS: Record<string, string> = {
"SEC-REVERSE-SHELL":
"Eine Reverse-Shell öffnet Angreifern eine Fernsteuerung Ihres Rechners sie könnten dann beliebige Befehle ausführen, als säßen sie selbst davor.",
"SEC-REMOTE-EXEC":
"Wird Code direkt aus dem Netz ausgeführt, weiß niemand vorher, was wirklich läuft schädlicher Fremdcode kann jederzeit unbemerkt nachgeladen werden.",
"SEC-DESTRUCTIVE":
"Solche Befehle können in Sekunden ganze Verzeichnisse, Festplatten oder Backups unwiderruflich löschen oder das System lahmlegen.",
"SEC-PRIV-ESC":
"Mit erhöhten Rechten kann ein Skill Schutzmechanismen aushebeln und tief ins System eingreifen, weit über das hinaus, was es eigentlich bräuchte.",
"SEC-PERSISTENCE":
"Dauerhafte Hintertüren sorgen dafür, dass Schadcode auch nach einem Neustart aktiv bleibt und sich kaum noch entfernen lässt.",
"SEC-OBFUSCATION":
"Verschleierter Code versteckt seine wahre Funktion absichtlich das ist ein typisches Merkmal, um Schadhandlungen vor der Prüfung zu verbergen.",
"SEC-SUPPLY-CHAIN":
"Pakete aus unkontrollierten Quellen können manipuliert sein und Schadcode einschleusen, noch bevor das Skill überhaupt etwas tut.",
"SEC-NETWORK":
"Ausgehende Verbindungen sind nicht automatisch bösartig, können aber Daten nach außen tragen oder Befehle empfangen sie gehören kontrolliert.",
"PRIV-SECRET-ACCESS":
"Greift ein Skill auf Passwörter, Schlüssel oder Zugangsdaten zu, können Angreifer damit Ihre Konten und Cloud-Dienste übernehmen.",
"PRIV-EXFILTRATION":
"Werden lokale Daten an fremde Server gesendet, verlassen vertrauliche Informationen unbemerkt Ihren Rechner besonders gefährlich zusammen mit Zugriff auf Geheimnisse.",
"PRIV-PROMPT-INJECTION":
"Manipulative Anweisungen bringen den KI-Agenten dazu, Sicherheitsregeln zu ignorieren oder Sie zu täuschen Sie verlieren die Kontrolle über sein Verhalten.",
"PRIV-HIDDEN-INSTRUCTIONS":
"Unsichtbare Zeichen oder versteckte Kommentare enthalten Anweisungen, die ein Mensch nie zu sehen bekommt, der KI-Agent aber sehr wohl befolgt.",
"PRIV-PII":
"Werden personenbezogene Daten erfasst, drohen DSGVO-Verstöße und der Missbrauch sensibler Informationen wie Ausweis-, Bank- oder Gesundheitsdaten.",
"PRIV-AGENT-TAMPERING":
"Verändert ein Skill den Agenten, dessen Gedächtnis oder andere Skills, kann es Schutzregeln dauerhaft aushebeln und sich selbst tarnen.",
"PRIV-OVERREACH":
"Wer mehr Rechte verlangt als nötig, schafft unnötige Angriffsfläche im Schadensfall steht dem Skill dann viel zu viel offen.",
"AI-PROMPT-INJECTION":
"Subtile Manipulationsversuche umgehen oft die starren Mustererkennungen die KI-Analyse erkennt auch verdeckte Angriffe auf das Agentenverhalten.",
"AI-MALICIOUS-INTENT":
"Schädliche Absicht ist nicht immer ein bekanntes Muster die KI-Analyse bewertet den Sinn des Codes und findet getarnte Funktionen.",
"AI-DATA-PRIVACY":
"Datenschutzrisiken stecken oft im Kontext, nicht in einzelnen Schlüsselwörtern die KI-Analyse erkennt möglichen Datenabfluss auch ohne klare Signatur.",
};
const PROBLEM_POINTS = [
{
icon: Shield,
title: "Nicht vertrauenswürdiger Code",
text: "Ein fremdes Skill kann beliebige Befehle auf Ihrem Rechner ausführen. Wer es installiert, vertraut blind dem, was darin steckt oft ohne es je gelesen zu haben.",
},
{
icon: EyeOff,
title: "Versteckte & unsichtbare Anweisungen",
text: "Anweisungen können in unsichtbaren Zeichen oder versteckten Kommentaren stecken. Für Menschen unsichtbar, vom KI-Agenten aber befolgt.",
},
{
icon: Syringe,
title: "Prompt-Injektion",
text: "Manipulative Texte bringen den KI-Agenten dazu, frühere Anweisungen zu ignorieren, Sicherheitsregeln zu umgehen oder Sie zu täuschen.",
},
{
icon: Upload,
title: "Datenabfluss",
text: "Vertrauliche Daten können unbemerkt an fremde Server gesendet werden von Dateien über Zwischenergebnisse bis zu ganzen Verzeichnissen.",
},
{
icon: KeyRound,
title: "Zugriff auf Geheimnisse",
text: "Passwörter, API-Schlüssel und Zugangsdaten liegen an bekannten Orten. Ein bösartiges Skill weiß genau, wo es danach suchen muss.",
},
{
icon: FileWarning,
title: "Unkontrollierte Installation",
text: "Wird ein Skill ungeprüft eingebunden, fehlt jede Kontrolle darüber, was es darf und tut ein erhebliches Sicherheits- und Datenschutzrisiko.",
},
];
function riskText(rule: Rule): string {
return RISK_EXPLANATIONS[rule.ruleId] ?? rule.description;
}
function RuleCard({ rule }: { rule: Rule }) {
return (
<Card className="h-full">
<CardHeader className="pb-3">
<div className="flex flex-wrap items-center gap-2">
<AxisBadge axis={rule.axis} className="text-[10px] px-1.5 py-0" />
<SeverityBadge severity={rule.severity} className="text-[10px] px-1.5 py-0" />
</div>
<CardTitle className="pt-1 text-base">{rule.title}</CardTitle>
</CardHeader>
<CardContent className="space-y-3 text-sm">
<div className="space-y-1">
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Was geprüft wird</p>
<p className="leading-relaxed text-foreground/90">{rule.description}</p>
</div>
<div className="space-y-1 rounded-md border border-border bg-muted/40 p-3">
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Warum das ein Risiko ist</p>
<p className="leading-relaxed text-foreground/90">{riskText(rule)}</p>
</div>
</CardContent>
</Card>
);
}
function RuleGroup({
rules,
axis,
icon: Icon,
title,
intro,
}: {
rules: Rule[];
axis: "security" | "privacy";
icon: typeof Shield;
title: string;
intro: string;
}) {
const items = rules.filter((r) => r.axis === axis);
if (items.length === 0) return null;
return (
<div className="space-y-4">
<div className="flex flex-col gap-2">
<div className="flex items-center gap-3">
<Icon className="h-6 w-6 text-sidebar-primary" />
<h3 className="text-2xl font-bold tracking-tight">{title}</h3>
<AxisBadge axis={axis} />
</div>
<p className="max-w-3xl text-muted-foreground">{intro}</p>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{items.map((rule) => (
<RuleCard key={rule.id} rule={rule} />
))}
</div>
</div>
);
}
export default function Landing() {
const { data, isLoading, error } = useListRules();
const activeRules = (data ?? []).filter((r) => r.enabled);
return (
<div className="mx-auto max-w-6xl px-4 py-12 sm:px-6">
<section className="flex flex-col items-start gap-6 pb-16">
<div className="inline-flex items-center gap-2 rounded-full border border-border bg-muted/50 px-3 py-1 text-xs font-medium text-muted-foreground">
<ShieldCheck className="h-3.5 w-3.5 text-sidebar-primary" />
Sicherheits- & Datenschutzprüfung für KI-Agent-Skills
</div>
<h1 className="max-w-3xl text-4xl font-bold leading-tight tracking-tight sm:text-5xl">
Prüfen Sie fremde Skills, bevor Sie ihnen vertrauen.
</h1>
<p className="max-w-2xl text-lg leading-relaxed text-muted-foreground">
SkillGuard untersucht öffentliche und fremde KI-Agent-Skills auf versteckte Anweisungen, Prompt-Injektion,
Datenabfluss und gefährlichen Code und erklärt verständlich, wo das Risiko liegt. So entscheiden Sie auf
einer fundierten Grundlage, statt blind zu vertrauen.
</p>
<div className="flex flex-wrap items-center gap-3">
<Button asChild size="lg">
<Link href="/pruefen">
<Search className="mr-2 h-4 w-4" />
Skill prüfen
</Link>
</Button>
<Button asChild size="lg" variant="outline">
<Link href="/dashboard">
<LayoutDashboard className="mr-2 h-4 w-4" />
Zum Dashboard
</Link>
</Button>
</div>
</section>
<section className="space-y-6 pb-16">
<div className="flex flex-col gap-2">
<div className="flex items-center gap-3">
<ShieldAlert className="h-6 w-6 text-sidebar-primary" />
<h2 className="text-3xl font-bold tracking-tight">Worin liegt das Risiko?</h2>
</div>
<p className="max-w-3xl text-muted-foreground">
Ein Skill ist mehr als nur eine Anleitung: Es kann Code ausführen, Daten lesen und das Verhalten Ihres
KI-Agenten steuern. Ein unkontrolliert installiertes Skill aus fremder Quelle ist deshalb ein echtes
Sicherheits- und Datenschutzrisiko hier die wichtigsten Gefahren in Alltagssprache.
</p>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{PROBLEM_POINTS.map((p) => (
<Card key={p.title} className="h-full">
<CardHeader className="pb-2">
<p.icon className="h-7 w-7 text-sidebar-primary" />
<CardTitle className="pt-2 text-lg">{p.title}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm leading-relaxed text-muted-foreground">{p.text}</p>
</CardContent>
</Card>
))}
</div>
</section>
<section className="space-y-10">
<div className="flex flex-col gap-2">
<h2 className="text-3xl font-bold tracking-tight">Das Prüfregelwerk</h2>
<p className="max-w-3xl text-muted-foreground">
Jeder geprüfte Skill wird gegen die folgenden Prüfpunkte gehalten aufgeteilt nach Datenschutz und
IT-Sicherheit. Die Liste wird live aus dem System geladen und zeigt nur die aktuell aktiven Prüfpunkte.
</p>
</div>
{isLoading ? (
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{[1, 2, 3, 4, 5, 6].map((i) => (
<Skeleton key={i} className="h-56 w-full" />
))}
</div>
) : error ? (
<Card>
<CardContent className="py-10 text-center text-muted-foreground">
Das Prüfregelwerk konnte gerade nicht geladen werden. Bitte versuchen Sie es später erneut.
</CardContent>
</Card>
) : activeRules.length === 0 ? (
<Card>
<CardContent className="py-10 text-center text-muted-foreground">
Aktuell sind keine Prüfpunkte aktiviert.
</CardContent>
</Card>
) : (
<div className="space-y-12">
<RuleGroup
rules={activeRules}
axis="privacy"
icon={Lock}
title="Datenschutz"
intro="Diese Prüfpunkte schützen Ihre Daten und die Kontrolle über den KI-Agenten: Sie erkennen Datenabfluss, Zugriff auf Geheimnisse, versteckte oder manipulative Anweisungen und den Umgang mit personenbezogenen Daten."
/>
<RuleGroup
rules={activeRules}
axis="security"
icon={Shield}
title="IT-Sicherheit"
intro="Diese Prüfpunkte schützen Ihr System vor schädlichem Code: Sie erkennen gefährliche Befehle, Rechteausweitung, Persistenz-Mechanismen, Verschleierung und unsichere Quellen."
/>
</div>
)}
</section>
</div>
);
}