feat: seo optimization p1

This commit is contained in:
Nik L
2026-03-19 15:31:15 -04:00
parent 5726d2d210
commit 0ee456ad58
12 changed files with 341 additions and 23 deletions

View File

@@ -49,8 +49,8 @@
} }
@theme inline { @theme inline {
--font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif; --font-sans: var(--font-inter), 'Inter', ui-sans-serif, system-ui, sans-serif;
--font-serif: 'Source Serif 4', 'Source Serif Pro', Georgia, serif; --font-serif: var(--font-source-serif), 'Source Serif 4', 'Source Serif Pro', Georgia, serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
--color-background: rgb(var(--background)); --color-background: rgb(var(--background));

View File

@@ -3,8 +3,35 @@ import type { Metadata } from 'next'
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Greyscan | Greywall', title: 'Greyscan | Greywall',
description: 'Scan your repo and see what an unrestricted AI agent would attempt. Powered by Greywall.', description: 'Scan your repo and see what an unrestricted AI agent would attempt. Powered by Greywall.',
alternates: {
canonical: 'https://greywall.io/greyscan',
},
openGraph: {
title: 'Greyscan | Greywall',
description: 'Scan your repo and see what an unrestricted AI agent would attempt. Powered by Greywall.',
url: 'https://greywall.io/greyscan',
siteName: 'Greywall',
type: 'website',
},
}
const breadcrumbJsonLd = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{ '@type': 'ListItem', position: 1, name: 'Greywall', item: 'https://greywall.io' },
{ '@type': 'ListItem', position: 2, name: 'Greyscan', item: 'https://greywall.io/greyscan' },
],
} }
export default function ExposureLayout({ children }: { children: React.ReactNode }) { export default function ExposureLayout({ children }: { children: React.ReactNode }) {
return children return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
/>
{children}
</>
)
} }

View File

@@ -1,10 +1,26 @@
import type { Metadata } from 'next' import type { Metadata } from 'next'
import { Inter, Source_Serif_4 } from 'next/font/google'
import './globals.css' import './globals.css'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
})
const sourceSerif = Source_Serif_4({
subsets: ['latin'],
display: 'swap',
variable: '--font-source-serif',
style: ['normal', 'italic'],
axes: ['opsz'],
})
export const metadata: Metadata = { export const metadata: Metadata = {
metadataBase: new URL('https://greywall.io'),
title: 'Greywall: Sandbox for AI Agents', title: 'Greywall: Sandbox for AI Agents',
description: description:
'Container-free, default-deny sandboxing with observability for AI agents. Five layers of defense in one command.', 'Container-free, default-deny sandboxing with real-time observability for AI agents on Linux and macOS. Five kernel-enforced security layers in one command. Open source.',
icons: { icons: {
icon: [ icon: [
{ url: '/icon.svg', type: 'image/svg+xml' }, { url: '/icon.svg', type: 'image/svg+xml' },
@@ -13,6 +29,74 @@ export const metadata: Metadata = {
], ],
apple: '/apple-icon.png', apple: '/apple-icon.png',
}, },
openGraph: {
title: 'Greywall: Sandbox for AI Agents',
description: 'Container-free, default-deny sandboxing with real-time observability for AI agents. Five kernel-enforced security layers in one command.',
url: 'https://greywall.io',
siteName: 'Greywall',
type: 'website',
},
twitter: {
card: 'summary',
title: 'Greywall: Sandbox for AI Agents',
description: 'Container-free, default-deny sandboxing with real-time observability for AI agents. Five kernel-enforced security layers in one command.',
},
alternates: {
canonical: 'https://greywall.io',
},
}
const jsonLd = {
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'Organization',
'@id': 'https://greyhaven.co/#organization',
name: 'Greyhaven',
url: 'https://greyhaven.co',
logo: { '@type': 'ImageObject', url: 'https://greywall.io/icon.svg' },
sameAs: ['https://github.com/GreyhavenHQ'],
},
{
'@type': 'WebSite',
'@id': 'https://greywall.io/#website',
name: 'Greywall',
url: 'https://greywall.io',
publisher: { '@id': 'https://greyhaven.co/#organization' },
},
{
'@type': 'SoftwareApplication',
'@id': 'https://greywall.io/#software',
name: 'Greywall',
description:
'Container-free, default-deny sandboxing with real-time observability and dynamic controls for AI agents on Linux and macOS.',
applicationCategory: 'SecurityApplication',
operatingSystem: 'Linux, macOS',
url: 'https://greywall.io',
downloadUrl: 'https://github.com/GreyhavenHQ/greywall',
license: 'https://opensource.org/licenses/Apache-2.0',
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
author: { '@id': 'https://greyhaven.co/#organization' },
featureList: [
'Filesystem isolation',
'Network isolation',
'Command blocking',
'Real-time violation monitoring',
'Learning mode',
'Syscall filtering',
'Dynamic allow/deny controls',
],
isAccessibleForFree: true,
},
{
'@type': 'SoftwareSourceCode',
name: 'Greywall',
codeRepository: 'https://github.com/GreyhavenHQ/greywall',
programmingLanguage: 'Go',
license: 'https://opensource.org/licenses/Apache-2.0',
targetProduct: { '@id': 'https://greywall.io/#software' },
},
],
} }
export default function RootLayout({ export default function RootLayout({
@@ -21,13 +105,11 @@ export default function RootLayout({
children: React.ReactNode children: React.ReactNode
}>) { }>) {
return ( return (
<html lang="en" className="dark"> <html lang="en" className={`dark ${inter.variable} ${sourceSerif.variable}`}>
<head> <head>
<link rel="preconnect" href="https://fonts.googleapis.com" /> <script
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" /> type="application/ld+json"
<link dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
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;1,8..60,600;1,8..60,700&display=swap"
rel="stylesheet"
/> />
</head> </head>
<body className="font-sans antialiased bg-background text-foreground"> <body className="font-sans antialiased bg-background text-foreground">

118
app/privacy/page.tsx Normal file
View File

@@ -0,0 +1,118 @@
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Privacy Policy | Greywall',
description: 'How Greywall handles your data.',
alternates: {
canonical: 'https://greywall.io/privacy',
},
}
export default function PrivacyPage() {
return (
<main className="min-h-screen pt-24 pb-16 px-4 sm:px-6">
<article className="mx-auto max-w-2xl">
<h1 className="font-serif text-3xl sm:text-4xl font-semibold tracking-tight mb-2">
Privacy Policy
</h1>
<p className="text-sm text-muted-foreground font-sans mb-12">
Last updated: March 19, 2026
</p>
<div className="space-y-10 text-muted-foreground font-serif text-base leading-relaxed">
<section>
<h2 className="font-serif text-xl font-semibold text-foreground mb-3">Greywall (the CLI tool)</h2>
<p>
Greywall runs entirely on your machine. It does not phone home, collect telemetry,
or transmit any data. No analytics, no crash reports, no usage tracking. The source
code is open and auditable
at <a href="https://github.com/GreyhavenHQ/greywall" target="_blank" rel="noopener noreferrer" className="text-primary hover:text-primary/80 transition-colors">github.com/GreyhavenHQ/greywall</a>.
</p>
</section>
<section>
<h2 className="font-serif text-xl font-semibold text-foreground mb-3">This website (greywall.io)</h2>
<p className="mb-4">
The Greywall landing page is a static site hosted on Vercel. We do not use cookies,
analytics scripts, or tracking pixels. Vercel may collect minimal server logs
(IP address, user agent, timestamp) as part of standard web hosting.
See <a href="https://vercel.com/legal/privacy-policy" target="_blank" rel="noopener noreferrer" className="text-primary hover:text-primary/80 transition-colors">Vercel&apos;s privacy policy</a> for
details.
</p>
</section>
<section>
<h2 className="font-serif text-xl font-semibold text-foreground mb-3">Greyscan</h2>
<p className="mb-4">
When you use Greyscan at <a href="/greyscan" className="text-primary hover:text-primary/80 transition-colors">/greyscan</a>,
the following happens:
</p>
<ul className="list-disc pl-6 space-y-2">
<li>
Your browser fetches the public file tree, dependency list, and README from GitHub&apos;s
API directly. This data never passes through our servers during collection.
</li>
<li>
To generate the threat report, a summary of the repo structure (file names,
detected stack, dependency names, and up to 8,000 characters of the README) is
sent to our server and forwarded to a third-party LLM provider for analysis.
</li>
<li>
Results are cached in server memory for up to 24 hours to avoid redundant
LLM calls for the same repository, then discarded. We do not persist scan
results to disk or a database.
</li>
<li>
No repository source code is read or transmitted. Only file paths, dependency
names, and the public README are included.
</li>
</ul>
</section>
<section>
<h2 className="font-serif text-xl font-semibold text-foreground mb-3">Third-party services</h2>
<ul className="list-disc pl-6 space-y-2">
<li>
<span className="text-foreground font-medium">GitHub API</span> &mdash; Greyscan
calls the GitHub REST API from your browser to fetch public repository metadata.
Subject to <a href="https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement" target="_blank" rel="noopener noreferrer" className="text-primary hover:text-primary/80 transition-colors">GitHub&apos;s privacy statement</a>.
</li>
<li>
<span className="text-foreground font-medium">LLM provider</span> &mdash; Repo
summaries sent through Greyscan are processed by a third-party LLM to generate
threat reports. The provider may retain data per their own policies.
</li>
<li>
<span className="text-foreground font-medium">Vercel</span> &mdash; Hosting
infrastructure. See <a href="https://vercel.com/legal/privacy-policy" target="_blank" rel="noopener noreferrer" className="text-primary hover:text-primary/80 transition-colors">Vercel&apos;s privacy policy</a>.
</li>
</ul>
</section>
<section>
<h2 className="font-serif text-xl font-semibold text-foreground mb-3">Security</h2>
<p>
If you discover a security issue in Greywall or this website, please report it
via <a href="https://github.com/GreyhavenHQ/greywall/security" target="_blank" rel="noopener noreferrer" className="text-primary hover:text-primary/80 transition-colors">GitHub Security Advisories</a>.
We will respond promptly.
</p>
</section>
<section>
<h2 className="font-serif text-xl font-semibold text-foreground mb-3">Contact</h2>
<p>
For questions about this policy,
reach us at <a href="https://greyhaven.co/contact" target="_blank" rel="noopener noreferrer" className="text-primary hover:text-primary/80 transition-colors">greyhaven.co/contact</a>.
</p>
</section>
</div>
<div className="mt-16 pt-8 border-t border-border/30">
<a href="/" className="text-sm text-muted-foreground hover:text-foreground transition-colors font-sans">
&larr; Back to Greywall
</a>
</div>
</article>
</main>
)
}

9
app/sitemap.ts Normal file
View File

@@ -0,0 +1,9 @@
import type { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{ url: 'https://greywall.io', lastModified: new Date(), changeFrequency: 'weekly', priority: 1 },
{ url: 'https://greywall.io/greyscan', lastModified: new Date(), changeFrequency: 'monthly', priority: 0.8 },
{ url: 'https://greywall.io/privacy', lastModified: new Date(), changeFrequency: 'yearly', priority: 0.3 },
]
}

View File

@@ -1,3 +1,4 @@
import Image from 'next/image'
import { CheckCircle2 } from 'lucide-react' import { CheckCircle2 } from 'lucide-react'
const agents = [ const agents = [
@@ -42,7 +43,7 @@ export function Agents() {
rel="noopener noreferrer" rel="noopener noreferrer"
className="group flex items-center gap-2.5 sm:gap-3 p-3 sm:p-4 rounded-lg border border-border/40 bg-card/30 hover:border-primary/20 hover:bg-card/50 transition-all cursor-pointer" className="group flex items-center gap-2.5 sm:gap-3 p-3 sm:p-4 rounded-lg border border-border/40 bg-card/30 hover:border-primary/20 hover:bg-card/50 transition-all cursor-pointer"
> >
<img <Image
src={`https://github.com/${agent.org}.png?size=64`} src={`https://github.com/${agent.org}.png?size=64`}
alt={agent.name} alt={agent.name}
width={28} width={28}

View File

@@ -109,22 +109,22 @@ const rows: Row[] = [
function CellIcon({ value }: { value: CellValue }) { function CellIcon({ value }: { value: CellValue }) {
if (value === 'yes') { if (value === 'yes') {
return ( return (
<span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-green-400/10"> <span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-green-400/10" aria-label="Supported">
<Check className="h-3 w-3 text-green-400" /> <Check className="h-3 w-3 text-green-400" aria-hidden="true" />
</span> </span>
) )
} }
if (value === 'no') { if (value === 'no') {
return ( return (
<span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-red-400/10"> <span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-red-400/10" aria-label="Not supported">
<X className="h-3 w-3 text-red-400/70" /> <X className="h-3 w-3 text-red-400/70" aria-hidden="true" />
</span> </span>
) )
} }
if (value === 'partial') { if (value === 'partial') {
return ( return (
<span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-yellow-400/10"> <span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-yellow-400/10" aria-label="Partial support">
<Minus className="h-3 w-3 text-yellow-400/70" /> <Minus className="h-3 w-3 text-yellow-400/70" aria-hidden="true" />
</span> </span>
) )
} }
@@ -214,19 +214,19 @@ export function Comparison() {
<div className="mt-6 flex flex-wrap items-center gap-5 text-xs font-sans text-muted-foreground"> <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"> <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"> <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" /> <Check className="h-3 w-3 text-green-400" aria-hidden="true" />
</span> </span>
Supported Supported
</div> </div>
<div className="flex items-center gap-1.5"> <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"> <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" /> <Minus className="h-3 w-3 text-yellow-400/70" aria-hidden="true" />
</span> </span>
Partial Partial
</div> </div>
<div className="flex items-center gap-1.5"> <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"> <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" /> <X className="h-3 w-3 text-red-400/70" aria-hidden="true" />
</span> </span>
Not supported Not supported
</div> </div>

View File

@@ -32,6 +32,12 @@ export function Footer() {
> >
greyhaven.co greyhaven.co
</a> </a>
<a
href="/privacy"
className="hover:text-foreground transition-colors"
>
Privacy
</a>
<span>Apache 2.0</span> <span>Apache 2.0</span>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,7 @@
'use client' 'use client'
import { useState, useEffect, useRef } from 'react' import { useState, useEffect, useRef } from 'react'
import Image from 'next/image'
import { Eye } from 'lucide-react' import { Eye } from 'lucide-react'
const slides = [ const slides = [
@@ -97,20 +98,26 @@ export function Observability() {
{/* Screenshot with crossfade */} {/* Screenshot with crossfade */}
<div className="relative rounded-lg border border-border/40 overflow-hidden bg-white"> <div className="relative rounded-lg border border-border/40 overflow-hidden bg-white">
{/* Hidden reference image to lock container height */} {/* Hidden reference image to lock container height */}
<img <Image
src={slides[0].src} src={slides[0].src}
alt="" alt=""
aria-hidden="true" aria-hidden="true"
width={1977}
height={1444}
className="w-full h-auto invisible" className="w-full h-auto invisible"
priority
/> />
{slides.map((slide, i) => ( {slides.map((slide, i) => (
<img <Image
key={slide.label} key={slide.label}
src={slide.src} src={slide.src}
alt={slide.alt} alt={slide.alt}
width={slide.src === '/pending_requests.png' ? 1752 : 1977}
height={slide.src === '/pending_requests.png' ? 1216 : 1444}
className={`absolute inset-0 w-full h-full object-contain object-top transition-opacity duration-700 ${ className={`absolute inset-0 w-full h-full object-contain object-top transition-opacity duration-700 ${
i === active ? 'opacity-100' : 'opacity-0' i === active ? 'opacity-100' : 'opacity-0'
}`} }`}
priority={i === 0}
/> />
))} ))}
</div> </div>

View File

@@ -4,7 +4,6 @@ const nextConfig = {
ignoreBuildErrors: true, ignoreBuildErrors: true,
}, },
images: { images: {
unoptimized: true,
remotePatterns: [ remotePatterns: [
{ {
protocol: 'https', protocol: 'https',
@@ -12,6 +11,19 @@ const nextConfig = {
}, },
], ],
}, },
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
],
},
]
},
} }
export default nextConfig export default nextConfig

30
public/llms.txt Normal file
View File

@@ -0,0 +1,30 @@
# Greywall
> Container-free, default-deny sandboxing with real-time observability for AI agents on Linux and macOS.
Greywall is an open-source CLI tool that wraps any AI agent (Claude Code, Codex, Cursor, Aider, and others) in a kernel-enforced sandbox. It uses five security layers on Linux (Bubblewrap namespaces, Landlock filesystem, Seccomp BPF syscall filtering, eBPF monitoring, and TUN+SOCKS5 network proxy) and four on macOS (Seatbelt sandbox, filesystem policy, log stream monitor, and proxy-based network control). Default-deny policy means nothing is accessible unless explicitly granted. Built by Greyhaven, licensed Apache 2.0.
## Key Features
- Filesystem isolation (kernel-enforced read/write/deny per path)
- Network isolation (all traffic routed through GreyProxy)
- Command blocking (detects blocked commands in pipes, chains, nested shells)
- Real-time violation monitoring (every denial captured with full context)
- Learning mode (auto-generates least-privilege templates from observed access)
- Syscall filtering (blocks 27+ dangerous system calls via Seccomp BPF)
- Dynamic allow/deny controls (adjust policies live without restarting)
## Links
- [Homepage](https://greywall.io)
- [Documentation](https://docs.greywall.io/)
- [GitHub](https://github.com/GreyhavenHQ/greywall)
- [Greyhaven (parent company)](https://greyhaven.co)
## Install
- Homebrew: `brew tap greyhavenhq/tap && brew install greywall`
- Curl: `curl -fsSL https://raw.githubusercontent.com/GreyhavenHQ/greywall/main/install.sh | sh`
- Go: `go install github.com/GreyhavenHQ/greywall/cmd/greywall@latest`
## Compatibility
Works with: Claude Code, Codex, Cursor, Aider, Goose, Amp, Gemini CLI, Cline, OpenCode, Copilot.
Platforms: Linux (3.8+), macOS.
License: Apache 2.0.

26
public/robots.txt Normal file
View File

@@ -0,0 +1,26 @@
User-agent: *
Allow: /
Disallow: /api/
User-agent: GPTBot
Allow: /
User-agent: OAI-SearchBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: CCBot
Disallow: /
User-agent: anthropic-ai
Disallow: /
User-agent: cohere-ai
Disallow: /
Sitemap: https://greywall.io/sitemap.xml