Files
zeekay 6c65e3e941 @hanzo/app 0.2.0: the app SDK, renamed from @hanzo/rooms
Every import, the catalog pin and the docs name @hanzo/app and
github.com/hanzoai/app. lib/rooms.ts is lib/app.ts and
components/rooms.tsx is components/app.tsx.
2026-10-22 19:07:20 -07:00

531 lines
22 KiB
TypeScript

'use client'
/**
* /security — verified security architecture and mechanisms.
*
* NO TAILWIND AND NO STYLE OBJECTS. The five section frames are
* `components/marketing/band`'s `Band` and `Head`; @hanzo/ui's marketing
* components carry the type and the controls (`Display`, `Lede`, `Title`,
* `Chip`, `Action`, `More`, `tap`); everything left is gui props over
* @hanzo/design's tokens.
*
* Four things the swap moves, each the shared shape winning over this page's
* own copy of it:
*
* - the section headings take `Title quiet` and the `h1` takes
* `Display`, so this page's ladder is the site's.
* - every control is `Action` at one of its two weights, and the three quiet
* links are `More`, whose `:hover` to white is what each of them was
* spelling out. `tap` stays on those three because a standalone link IS a
* tap target and `More` states no floor; the buttons drop it, because
* `Action` already carries the 44px minimum.
* - the two side-by-side sections were a `lg:grid-cols-2` with `order-*`
* classes swapping the halves at 1024. They are a stack that becomes a row —
* `row` where the picture is on the right, `row-reverse` where it is on the
* left — so the DOM order is the reading order at every width and no class
* is needed to say it twice.
* - `flex-1` is spelt `grow={1} shrink={1} flexBasis="0%"`, because gui's
* `flex={1}` writes `flex-basis: 0px` and collapses the pane.
*/
import Link from "next/link";
import { motion } from "@/components/motion";
import type { ReactNode } from "react";
import {
Shield,
Lock,
CheckCircle,
Server,
Eye,
Key,
FileCheck,
Globe,
ArrowRight,
ExternalLink,
Fingerprint,
Database,
Network,
Clock,
Users,
Bug
} from "lucide-react";
import { Box, Text, View, XStack, YStack } from '@hanzo/ui'
import { Grid } from '@hanzo/ui/grid'
import { Band, Head } from '@/components/marketing/band'
import { Glow } from '@/components/shared/Glow'
import { Action, Chip, Display, Lede, More, Title, Line } from '@hanzo/ui/marketing'
import { mix } from '@hanzo/app/lib/mix'
/*
* There was a badge row here: four names — SOC 2 Type II, GDPR, CCPA, HIPAA —
* each rendered with a green checkmark. The qualifiers that made them honest
* ("assessment planned", "BAA available") were carried in a `description` field
* the markup never rendered, so what shipped was four certifications with a tick
* beside them and nothing anywhere saying we hold none of them.
*
* The certification question now has exactly one answer, at /trust: continual
* internal audits, the report on request, and a scoping conversation for a
* reviewer who runs on a named framework. This page describes mechanisms; that
* one holds the position. Two pages answering it is how the qualifier got lost
* the first time.
*/
/** The inks this page draws in. */
const INK = 'var(--foreground)'
const DIM = 'var(--muted-foreground)'
const SOFT = '$color/80'
/** The entrance a block makes as it scrolls in. */
const rise = (delay?: number) => (
<motion.div initial={{ opacity: 0, y: 30 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.5, delay }} />
)
/** The badge above a heading, in the tint this page has always drawn it. */
const BADGE = { mb: 24, px: 12, py: 4, fontSize: "$1", lineHeight: "1.3333", fontWeight: "500", borderColor: "transparent", bg: "color-mix(in srgb, var(--primary) 15%, transparent)", color: "var(--primary)" } as const
/** A pane of a two-up section: `flex-1`, spelt so gui does not collapse it. */
const PANE = { grow: 1, shrink: 1, flexBasis: '0%' } as const
/** The disc a mark sits on. */
const DISC = { width: 48, height: 48, items: 'center', justify: 'center', rounded: 'var(--radius-lg)' } as const
/** A heading inside a panel, and the grey paragraph under it. */
const PanelTitle = ({ mb = 16, children }: { mb?: number; children: ReactNode }) => (
<Line render="h3" display="block" mb={mb} fontSize="$6" fontWeight="700" color={INK} lineHeight="1.4">
{children}
</Line>
)
const Body = ({ mb, children }: { mb?: number; children: ReactNode }) => (
<Text render="p" display="block" mb={mb} color={DIM}>
{children}
</Text>
)
/** A small line, on the leading its utility paired with `text-sm`. */
const SMALL = { fontSize: '$2', lineHeight: '1.4286' } as const
const SECURITY_FEATURES = [
{
icon: Lock,
title: "Encryption at Rest and in Transit",
description:
"At rest, each organization is its own file on Hanzo Base, and where a master key is configured each file opens under its own key, derived through HKDF-SHA256. In transit the edge speaks TLS 1.3 and offers hybrid ML-KEM-768 key exchange to clients that support it, so their traffic captured today cannot be read by a quantum computer later.",
},
{
icon: Fingerprint,
title: "Second Factors",
description:
"An authenticator app, or a code by SMS or email, with recovery codes. Enrolment hands out the material and demands it back before it writes anything, and adding or dropping a factor signs the account out of every other browser — a stolen session that outlives the change is the same as not having the factor.",
},
{
icon: Key,
title: "Keys and Tokens",
description:
"A key is told apart from a token by its prefix, so nothing has to guess what it was handed. Tokens are refused outright if they carry an HMAC signature or no algorithm at all, and a refresh token is single-use — presenting a spent one revokes its whole family rather than the one token.",
},
{
icon: Users,
title: "Who May Reach What",
description:
"Access is a grant at a place: an organization, a workspace under it, a project under that. A check asks whether some grant the caller holds covers the path and admits the verb. One rule, no special cases, and the decision is a function call rather than a service that can be down.",
},
{
icon: Network,
title: "The Edge Throws Identity Away",
description:
"Anything that arrives claiming to be an org, a user or an email is deleted at the gateway before a single handler reads it. Identity is written back only from a verified token. Inside the cluster, services reach each other over a binary protocol rather than the public internet.",
},
{
icon: Database,
title: "Where It Sits",
description:
"An organization is a tenancy boundary, and on Hanzo Base it is a physical one — a different organization is a different database file, so no query can reach across two. Dedicated and self-hosted deployments pin that file to a region you choose.",
},
];
const ENTERPRISE_FEATURES = [
"Single sign-on through Hanzo IAM",
"Federation to your own OpenID Connect issuer",
"SCIM 2.0 provisioning from your directory",
"A named engineer who knows your deployment",
"Priority incident response",
"Retention set by you, not by us",
"Audit records exported where you want them",
"Security questionnaire support",
];
const EDGE_FEATURES = [
{ name: "Run it where you like", description: "Our cloud, your cluster, your rack" },
{ name: "Scales to nothing", description: "Idle should cost what idle is worth" },
{ name: "Your region", description: "Pin the data and keep it there" },
];
const Security = () => {
return (
<Box minH="100vh" $platform-web={{ color: 'var(--foreground)' }}>
<main>
{/* Hero Section */}
<Box
render="section"
position="relative"
overflow="hidden"
pt={96}
pb={80}
px={16}
$md={{ px: 32 }}
$lg={{ px: 48 }}
>
{/* Background gradient */}
<Box
aria-hidden="true"
position="absolute"
t={0}
l="50%"
x="-50%"
width={800}
height={420}
background="radial-gradient(circle, color-mix(in srgb, var(--primary) 12%, transparent) 0%, transparent 70%)"
filter="blur(80px)"
pointerEvents="none"
/>
<Box position="relative" z="var(--z-raised)" width="100%" maxW={1024} mx="auto">
<Box $platform-web={{ textAlign: 'center' }}>
{/* Badge */}
<Chip
render={<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4 }} />}
{...BADGE}
>
<Shield width={14} height={14} />
Security Architecture
</Chip>
<Display
render={<motion.h1 initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, delay: 0.05 }} />}
mb={24}
>
What protects your data,
<br />
and how.
</Display>
<Lede
render={<motion.p initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, delay: 0.1 }} />}
mb={40}
width="100%"
maxW={768}
mx="auto"
>
Clear security mechanisms: which key encrypts your data, where tenancy boundaries are
enforced, and how every request is verified.
</Lede>
{/* CTAs */}
<XStack
render={<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, delay: 0.15 }} />}
flexWrap="wrap"
items="center"
justify="center"
gap={16}
mb={48}
>
<Action
href="/contact"
fill
>
Talk to Security Team
<ArrowRight width={16} height={16} />
</Action>
<Action
href="https://docs.hanzo.ai/docs/cli/security"
target="_blank"
rel="noreferrer noopener"
>
Security Documentation
<ExternalLink width={16} height={16} />
</Action>
</XStack>
<Line
render={<motion.p initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4, delay: 0.2 }} />}
display="block"
{...SMALL}
color={DIM}
>
Continual internal audits, with the report on request.{" "}
<Line render={<Link href="/trust" />} color={INK} whiteSpace="normal" textDecorationLine="underline" $platform-web={{ textUnderlineOffset: '4px' }}>
The controls, and how to check them
</Line>
.
</Line>
</Box>
</Box>
</Box>
{/* Security Features Grid */}
<Band rule={false} pad="$12" measure={1152} ground="linear-gradient(to bottom in oklab, var(--background), color-mix(in oklab, var(--neutral-900) 50%, transparent))">
<Head
title="The Controls, Named"
lede="Each of these is a decision in code you can point at, not a posture."
/>
<Grid columns={{ min: 300, max: 3 }} gap={24}>
{SECURITY_FEATURES.map((feature, index) => {
const Icon = feature.icon;
return (
<Box
key={feature.title}
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ delay: index * 0.1 }} />}
p={24}
rounded="var(--radius-xl)"
borderWidth={1}
borderColor="var(--border)"
bg={mix('var(--secondary)', 80)}
>
<XStack {...DISC} mb={16} bg={mix('var(--primary)', 15, 'srgb')}>
<Icon width={24} height={24} />
</XStack>
<Line render="h3" display="block" mb={8} fontSize="$4" fontWeight="600" color={INK} lineHeight="1.5556">
{feature.title}
</Line>
<Line render="p" display="block" {...SMALL} color={DIM}>{feature.description}</Line>
</Box>
);
})}
</Grid>
</Band>
{/* Infrastructure Security */}
<Band rule={false} pad="$12" measure={1152}>
<YStack gap={64} $lg={{ flexDirection: 'row', items: 'flex-start' }}>
<Box render={rise()} {...PANE}>
{/* A plain span, not a Box: `Chip` decides the display, and a
gui element states one of its own that the class would have
to out-rank. */}
<Chip {...BADGE}>
Infrastructure
</Chip>
<Title quiet mb={24}>
What runs underneath
</Title>
<Body mb={32}>
Workloads sit in hardware-virtualised machines on managed Kubernetes, in data centres with
physical access control and redundant power. Secrets never reach a manifest — they are fetched
from Hanzo KMS at boot, and outside development a service refuses to start without a verified
issuer, audience and key set rather than falling back to running open.
</Body>
<YStack gap={16}>
{[
{ icon: Server, text: "Hardware-virtualised machines, one tenant to a boundary" },
{ icon: Eye, text: "Errors, traces and release health watched around the clock" },
{ icon: Clock, text: "Replicated, with failover that does not need a person" },
{ icon: FileCheck, text: "Every image pinned to a version, every deploy reconciled" },
].map((item, index) => {
const Icon = item.icon;
return (
<XStack key={index} items="center" gap={12}>
<Icon width={20} height={20} color={DIM} />
<Text color={SOFT}>{item.text}</Text>
</XStack>
);
})}
</YStack>
</Box>
<Box
render={rise(0.2)}
{...PANE}
p={32}
rounded="var(--radius-xl)"
borderWidth={1}
borderColor="var(--border)"
background="linear-gradient(to bottom right in oklab, var(--neutral-900), color-mix(in oklab, var(--neutral-900) 50%, transparent))"
>
<View render={<Globe width={40} height={40} color={DIM} />} display="inline" mb={24} />
<PanelTitle>Or none of our infrastructure at all</PanelTitle>
<Body mb={24}>
Every piece named on this page is a binary you can run yourself. The strongest answer to a
question about our infrastructure is that you do not have to use it.
</Body>
<YStack gap={16}>
{EDGE_FEATURES.map((feature) => (
<Box
key={feature.name}
p={16}
rounded="var(--radius-lg)"
borderWidth={1}
borderColor="var(--border)"
bg={mix('var(--neutral-800)', 50)}
>
<XStack items="center" justify="space-between">
<Text fontWeight="500" color={INK}>{feature.name}</Text>
</XStack>
<Line {...SMALL} color={DIM}>{feature.description}</Line>
</Box>
))}
</YStack>
</Box>
</YStack>
</Band>
{/* Enterprise Security */}
<Band rule={false} pad="$12" measure={1152} ground="linear-gradient(to bottom in oklab, color-mix(in oklab, var(--neutral-900) 50%, transparent), var(--background))">
<YStack gap={64} $lg={{ flexDirection: 'row-reverse', items: 'center' }}>
<Box render={rise()} {...PANE}>
<Chip {...BADGE}>
Enterprise
</Chip>
<Title quiet mb={24}>
If you are the one who has to sign off
</Title>
<Body mb={24}>
Bring the control you are required to demonstrate and we will show you the code that
implements it, or tell you plainly that it does not exist yet. A control we cannot point at is
one you should not put your name to.
</Body>
<Body>
Where a requirement is not met, the honest options are usually self-hosting, a dedicated
deployment, or waiting. Our engineers will say which one applies rather than selling you the
gap.
</Body>
</Box>
<Box render={rise(0.2)} {...PANE}>
<Box p={32} rounded="var(--radius-xl)" borderWidth={1} borderColor="var(--border)" background="linear-gradient(to bottom right in oklab, var(--white-10), transparent)">
<View render={<Shield width={40} height={40} />} display="inline" mb={24} />
<PanelTitle>
What comes with Enterprise
</PanelTitle>
<Body mb={24}>
An engineer who has read your deployment, retention you set rather than inherit, and
somebody to answer the questionnaire your procurement team is about to send.
</Body>
<Grid columns={{ min: 220, max: 2 }} gap={12}>
{ENTERPRISE_FEATURES.map((feature) => (
<XStack key={feature} items="flex-start" gap={8}>
<View render={<CheckCircle width={16} height={16} color={mix('var(--foreground)', 70)} />} mt={2} shrink={0} />
<Line {...SMALL} color={SOFT}>{feature}</Line>
</XStack>
))}
</Grid>
<More
href="/contact-sales"
mt={32}
$touchable={{ minH: 44, minW: 44 }}
>
Contact Sales
<ArrowRight width={16} height={16} />
</More>
</Box>
</Box>
</YStack>
</Band>
{/* Vulnerability Disclosure */}
<Band rule={false} pad="$12" measure={896}>
<Box
render={rise()}
p={32}
rounded="var(--radius-xl)"
borderWidth={1}
borderColor="var(--border)"
background="linear-gradient(to bottom right in oklab, var(--white-20), transparent)"
>
<XStack items="flex-start" gap={16}>
<XStack {...DISC} shrink={0} bg="var(--white-10)">
<Bug width={24} height={24} color={mix('var(--foreground)', 60)} />
</XStack>
<Box>
<PanelTitle mb={8}>
Found something
</PanelTitle>
<Body mb={16}>
Email us rather than opening a public issue, and include a reproduction if you have one. A
real report from a stranger is worth more than an internal review, and we answer quickly.
</Body>
<More
href="mailto:[email protected]"
$touchable={{ minH: 44, minW: 44 }}
>
security@hanzo.ai
<ExternalLink width={16} height={16} />
</More>
</Box>
</XStack>
</Box>
</Band>
{/* CTA Section */}
<Band rule={false} pad="$12" measure={896} ground="linear-gradient(to bottom in oklab, color-mix(in oklab, var(--neutral-900) 30%, transparent), var(--background))">
<Glow corner="top" pct={10} />
<Glow corner="bottom" pct={5} />
<Box position="relative" z="var(--z-raised)" $platform-web={{ textAlign: 'center' }}>
<Title
render={<motion.h2 initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} />}
mb={16}
>
Ask us the hard question
</Title>
<Lede
render={<motion.p initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ delay: 0.1 }} />}
mb={40}
width="100%"
maxW={672}
mx="auto"
mt="$3.5"
>
Bring the requirement you cannot get past, and we will show you the code or tell you it is not
built. Both answers are more useful than a brochure.
</Lede>
<XStack
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ delay: 0.2 }} />}
flexWrap="wrap"
items="center"
justify="center"
gap={16}
>
<Action
href="/login"
fill
>
Try Hanzo
<ArrowRight width={20} height={20} />
</Action>
<Action
href="/contact-sales"
>
Schedule Security Review
</Action>
</XStack>
<Box
render={<motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ delay: 0.3 }} />}
mt={32}
>
<More
href="https://docs.hanzo.ai/docs/cli/security"
target="_blank"
rel="noreferrer noopener"
$touchable={{ minH: 44, minW: 44 }}
>
Read the security documentation
</More>
</Box>
</Box>
</Band>
</main>
</Box>
);
};
export default Security;