TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1import * as React from "react";2import { ArrowDown, ArrowRight, Cloud, KeyRound, Lock, LockOpen, Monitor, ScrollText, ShieldCheck } from "lucide-react";3import { cn } from "@/lib/utils";4import { Reveal } from "./reveal";56/* ------------------------------------------------------------------------------------------------7 * Browser → PolyLLM encrypted server layer → Provider8 * ---------------------------------------------------------------------------------------------- */9function Node({ icon, title, sub, items, tone = "default", className }: { icon: React.ReactNode; title: string; sub: string; items: string[]; tone?: "default" | "accent"; className?: string }) {10 return (11 <div className={cn("relative flex min-w-0 flex-1 flex-col rounded-2xl p-4 sm:p-5", tone === "accent" ? "bg-accent-soft/60 ring-1 ring-accent/30" : "panel", className)}>12 <div className="flex items-center gap-2.5">13 <span className={cn("flex size-9 shrink-0 items-center justify-center rounded-lg [&_svg]:size-4.5", tone === "accent" ? "bg-accent text-accent-fg" : "bg-bg-elevated text-fg-muted")}>{icon}</span>14 <div className="min-w-0">15 <p className="text-[15px] font-semibold tracking-tight">{title}</p>16 <p className="truncate text-[12.5px] text-fg-muted">{sub}</p>17 </div>18 </div>19 <ul className="mt-3.5 space-y-1.5 text-[13px] leading-5 text-fg-muted">20 {items.map((it) => (21 <li key={it} className="flex gap-2">22 <span className="mt-[7px] size-1.5 shrink-0 rounded-full bg-border-strong" aria-hidden />23 <span>{it}</span>24 </li>25 ))}26 </ul>27 </div>28 );29}3031function Arrow({ label }: { label: string }) {32 return (33 <div className="flex items-center justify-center lg:flex-col" aria-hidden>34 <span className="hidden font-mono text-[10.5px] uppercase tracking-[0.1em] text-fg-subtle lg:block">{label}</span>35 <ArrowDown className="size-4 text-fg-subtle lg:hidden" />36 <ArrowRight className="hidden size-4 text-fg-subtle lg:block" />37 </div>38 );39}4041export function SecurityFlow() {42 return (43 <Reveal className="grid gap-3 lg:grid-cols-[1fr_auto_1.2fr_auto_1fr] lg:items-stretch lg:gap-4">44 <Node icon={<Monitor />} 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"]} />45 <Arrow label="TLS" />46 <Node47 icon={<Lock />}48 title="PolyLLM server"49 sub="encrypted key layer"50 tone="accent"51 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"]}52 />53 <Arrow label="TLS" />54 <Node icon={<Cloud />} 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"]} />55 </Reveal>56 );57}5859/* ------------------------------------------------------------------------------------------------60 * API key lifecycle61 * ---------------------------------------------------------------------------------------------- */62const LIFECYCLE: { icon: React.ReactNode; title: string; body: React.ReactNode; code?: string }[] = [63 {64 icon: <KeyRound />,65 title: "1 · You paste a key",66 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.",67 },68 {69 icon: <Lock />,70 title: "2 · Encrypted with AES-256-GCM",71 body: (72 <>73 The data key is derived from <code>API_KEY_ENCRYPTION_SECRET</code> with HKDF-SHA256 (info <code>polyllm:provider-key:v1</code>), 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 <code>userId|provider</code>: a ciphertext copied to another user or provider cannot be decrypted.74 </>75 ),76 code: "v1.<iv 12B>.<authTag 16B>.<ciphertext> (base64url)",77 },78 {79 icon: <ShieldCheck />,80 title: "3 · Stored with a hint and a fingerprint",81 body: (82 <>83 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. <code>PublicConnection</code>, the shape the browser sees, has no key field at all.84 </>85 ),86 },87 {88 icon: <LockOpen />,89 title: "4 · Decrypted only for the request",90 body: (91 <>92 <code>getDecryptedKey()</code> 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.93 </>94 ),95 },96 {97 icon: <ScrollText />,98 title: "5 · Audited, rotatable, deletable",99 body: (100 <>101 Adding, replacing and removing a key writes an audit event (<code>provider.key_added</code>, <code>provider.key_replaced</code>, <code>provider.key_deleted</code>) with the requesting IP. Removing a key deletes the encrypted row immediately; revoking it in the provider console makes the stored copy useless.102 </>103 ),104 },105];106107export function KeyLifecycle() {108 return (109 <ol className="relative space-y-6 border-l border-border pl-6 sm:space-y-7 sm:pl-8">110 {LIFECYCLE.map((step, i) => (111 <Reveal key={step.title} as="li" delay={0.04 * i} className="relative">112 <span className="absolute -left-[calc(1.5rem+9px)] top-1 flex size-[18px] items-center justify-center rounded-full bg-bg ring-1 ring-border sm:-left-[calc(2rem+9px)]" aria-hidden>113 <span className="size-2 rounded-full bg-accent" />114 </span>115 <div className="flex items-start gap-3">116 <span className="hidden size-8 shrink-0 items-center justify-center rounded-lg bg-accent-soft text-accent sm:flex [&_svg]:size-4">{step.icon}</span>117 <div className="min-w-0">118 <h3 className="text-[15px] font-semibold tracking-tight">{step.title}</h3>119 <p className="mt-1 text-[14px] leading-6 text-fg-muted [&_code]:rounded [&_code]:bg-bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[12px] [&_code]:text-fg">{step.body}</p>120 {step.code ? <pre className="mt-2 overflow-x-auto rounded-lg bg-bg-inset px-3 py-2 font-mono text-[12px] text-fg-muted">{step.code}</pre> : null}121 </div>122 </div>123 </Reveal>124 ))}125 </ol>126 );127}128