import * as React from "react";
import { ArrowDown, ArrowRight, Cloud, KeyRound, Lock, LockOpen, Monitor, ScrollText, ShieldCheck } from "lucide-react";
import { cn } from "@/lib/utils";
import { Reveal } from "./reveal";
/* ------------------------------------------------------------------------------------------------
* Browser → PolyLLM encrypted server layer → Provider
* ---------------------------------------------------------------------------------------------- */
function Node({ icon, title, sub, items, tone = "default", className }: { icon: React.ReactNode; title: string; sub: string; items: string[]; tone?: "default" | "accent"; className?: string }) {
return (
{items.map((it) => (
-
{it}
))}
);
}
function Arrow({ label }: { label: string }) {
return (
);
}
export function SecurityFlow() {
return (
} title="Your browser" sub="never holds a provider key" items={["Sends the key once, over TLS, when you connect a provider", "Receives only a hint like sk-••••••••9A2K afterwards", "Session in an HttpOnly cookie — not readable by scripts"]} />
}
title="PolyLLM server"
sub="encrypted key layer"
tone="accent"
items={["Encrypts the key with AES-256-GCM (HKDF-derived data key, per-user AAD) before it is stored", "Decrypts in memory only when it is about to call the provider", "Never logs, returns or serializes the key; logs pass through a secret redactor"]}
/>
} title="Provider API" sub="the one you selected" items={["Receives your prompt and your key directly from PolyLLM's server", "Bills your own account at list price — no markup, no reseller", "Nothing is routed through third-party inference brokers"]} />
);
}
/* ------------------------------------------------------------------------------------------------
* API key lifecycle
* ---------------------------------------------------------------------------------------------- */
const LIFECYCLE: { icon: React.ReactNode; title: string; body: React.ReactNode; code?: string }[] = [
{
icon: ,
title: "1 · You paste a key",
body: "Sent once over TLS to PUT /api/providers. Checked for shape (8–512 characters, no whitespace) and validated against the provider with a real request, so you know it works before it is saved.",
},
{
icon: ,
title: "2 · Encrypted with AES-256-GCM",
body: (
<>
The data key is derived from API_KEY_ENCRYPTION_SECRET with HKDF-SHA256 (info polyllm:provider-key:v1), so the raw secret never touches the cipher. A random 12-byte IV and a 16-byte authentication tag protect every envelope. The additional authenticated data is userId|provider: a ciphertext copied to another user or provider cannot be decrypted.
>
),
code: "v1... (base64url)",
},
{
icon: ,
title: "3 · Stored with a hint and a fingerprint",
body: (
<>
The database row holds the envelope, a display hint (recognizable prefix + last 4 characters) and a SHA-256 fingerprint used only to detect “same key” on rotation. PublicConnection, the shape the browser sees, has no key field at all.
>
),
},
{
icon: ,
title: "4 · Decrypted only for the request",
body: (
<>
getDecryptedKey() runs immediately before a provider call — chat, Arena, key validation, model sync — and the plaintext lives in memory only for that call. It is never written to logs, error messages or exports.
>
),
},
{
icon: ,
title: "5 · Audited, rotatable, deletable",
body: (
<>
Adding, replacing and removing a key writes an audit event (provider.key_added, provider.key_replaced, provider.key_deleted) with the requesting IP. Removing a key deletes the encrypted row immediately; revoking it in the provider console makes the stored copy useless.
>
),
},
];
export function KeyLifecycle() {
return (
{LIFECYCLE.map((step, i) => (
{step.icon}
{step.title}
{step.body}
{step.code ?
{step.code} : null}
))}
);
}