Files
zooqueen 8c2476fff1 console: the meta description leaked Hanzo's name on Lux and Zoo
generateMetadata already resolves the title from the request host — a
Lux console renders <title>Lux Cloud Console</title>. The description
beside it was a literal and shipped 'Unified admin console for Hanzo
Cloud and all cloud products.' to console.lux.cloud and
console.zoo.cloud.

The correct title is exactly why nobody caught it: the visible tab looked
white-labeled, so the head element behind it was never read.

Both strings now come from brandName. Every brand-visible value in this
function must; a literal here re-opens the leak.
2026-07-27 18:33:26 -07:00

58 lines
2.4 KiB
TypeScript

import './fonts.css'
import '@hanzogui/core/reset.css'
// Hanzo Design System tokens (vendored from hanzoai/design) — the monochrome
// source of truth. Imported BEFORE globals.css so the console's Tamagui theme can
// derive its ladder from the design neutral/semantic tokens.
import './design/index.css'
import './globals.css'
import type { Metadata, Viewport } from 'next'
import type { ReactNode } from 'react'
import { headers } from 'next/headers'
import { Provider } from '~/components/Provider'
import { ChunkGuard } from '~/components/ChunkGuard'
import { BrandTitle } from '~/components/BrandTitle'
import { resolveConfig } from '~/config'
// The document <title> is SSR metadata, so it must reflect the REQUEST host's
// brand (console.lux.cloud -> "Lux Cloud Console"), not the build-time default.
// The visible shell resolves the brand client-side from window.location, but the
// tab title is server-rendered — without reading the Host header here the browser
// tab leaks "Hanzo Cloud Console" on Lux/Zoo hosts, a white-label violation.
//
// The description is the same metadata read by the same brand, so it resolves the
// same way. It did not, and shipped `content="Unified admin console for Hanzo Cloud
// and all cloud products."` to console.lux.cloud and console.zoo.cloud — the title
// beside it was already correct, which is exactly why nobody noticed. Every
// brand-visible string in this function comes from `brandName`; adding a literal
// here re-opens the leak.
export async function generateMetadata(): Promise<Metadata> {
const host = (await headers()).get('host') ?? undefined
const { brandName } = resolveConfig(host)
return {
title: `${brandName} Console`,
description: `Unified admin console for ${brandName} and all cloud products.`,
}
}
export const viewport: Viewport = {
themeColor: '#000000',
// Extend the layout into the display cutout / home-indicator area so the
// `env(safe-area-inset-*)` values become non-zero on notched devices — the mobile
// drawers + chat composer read them to keep content clear of the notch/indicator.
viewportFit: 'cover',
}
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en" className="t_dark" style={{ backgroundColor: '#000000', colorScheme: 'dark' }} suppressHydrationWarning>
<body style={{ margin: 0 }}>
<ChunkGuard />
<BrandTitle />
<Provider>{children}</Provider>
</body>
</html>
)
}