{section.title}
+-
+ {section.body.map((item) => (
+
- {item} + ))} +
From ed7cac833c77c41b7c7ad2cd9ba92f9a81162f91 Mon Sep 17 00:00:00 2001
From: Aanish Bhirud <47579874+baanish@users.noreply.github.com>
Date: Tue, 5 May 2026 15:19:07 -0400
Subject: [PATCH 1/5] docs: add public security page
---
src/app/globals.css | 38 ++++++++++++
src/app/security/page.tsx | 107 ++++++++++++++++++++++++++++++++
src/components/viewer-shell.tsx | 20 ++++--
tests/e2e/viewer.spec.ts | 13 ++++
4 files changed, 174 insertions(+), 4 deletions(-)
create mode 100644 src/app/security/page.tsx
diff --git a/src/app/globals.css b/src/app/globals.css
index c69e4d5..4b6e41e 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -198,6 +198,23 @@ select {
border-bottom: 1px solid var(--border);
}
+.nav-text-link {
+ color: var(--text-muted);
+ font-size: 0.9rem;
+ font-weight: 600;
+ line-height: 1.4;
+ transition: color 150ms ease;
+}
+
+.nav-text-link:hover {
+ color: var(--text-primary);
+}
+
+.nav-text-link:focus-visible {
+ outline: 2px solid color-mix(in srgb, var(--accent) 48%, transparent);
+ outline-offset: 4px;
+}
+
/* ── Full-bleed editorial sections ── */
.home-hero-section {
padding-top: 2rem;
@@ -244,6 +261,27 @@ select {
}
}
+.site-footer {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ border-top: 1px solid var(--border);
+ padding-top: 1.25rem;
+ color: var(--text-soft);
+ font-family: var(--font-mono), monospace;
+ font-size: 0.78rem;
+}
+
+.site-footer a {
+ color: var(--text-muted);
+}
+
+.site-footer a:hover {
+ color: var(--text-primary);
+}
+
/* ── Bento grid — tonal separation via 1px gaps ── */
.bento-grid {
display: grid;
diff --git a/src/app/security/page.tsx b/src/app/security/page.tsx
new file mode 100644
index 0000000..4b64966
--- /dev/null
+++ b/src/app/security/page.tsx
@@ -0,0 +1,107 @@
+import Link from "next/link";
+import type { Metadata } from "next";
+
+export const metadata: Metadata = {
+ title: "Security - agent-render",
+ description: "Security notes for agent-render static artifact links, markdown rendering, Mermaid, CSP, and reports.",
+};
+
+const sections = [
+ {
+ title: "What reaches the server",
+ body: [
+ "Static mode sends HTML, CSS, and JavaScript to the browser. It does not send artifact payloads to the static host.",
+ "Artifact payloads stay in the URL fragment and are not sent to the static host as part of the HTTP request path, query string, or request body.",
+ "The server can still receive normal static asset requests, IP address, user agent, referrer headers, and access logs from the hosting layer.",
+ ],
+ },
+ {
+ title: "What can still leak",
+ body: [
+ "agent-render is zero-retention by host design. It is not a secret manager.",
+ "Artifact contents can still leak through copied URLs, browser history, bookmarks, screenshots, screen sharing, crash reports, extensions, referrer behavior, and future client-side analytics if someone adds them.",
+ "Do not put secrets, credentials, private keys, production tokens, or regulated data in artifact links.",
+ ],
+ },
+ {
+ title: "Markdown and Mermaid",
+ body: [
+ "Markdown artifacts are rendered as GitHub-flavored Markdown and passed through rehype-sanitize before display.",
+ "React Markdown is configured with skipHtml, so raw HTML embedded in markdown is skipped instead of rendered.",
+ "Mermaid diagrams are only rendered from fenced mermaid code blocks. Mermaid runs with securityLevel: \"strict\" and falls back to showing source text if rendering fails.",
+ ],
+ },
+ {
+ title: "CSP and security headers",
+ body: [
+ "The default static export does not require a runtime server. Configure Content-Security-Policy and other security headers at your static host or CDN.",
+ "Recommended headers include a restrictive Content-Security-Policy, Referrer-Policy, X-Content-Type-Options, Permissions-Policy, and HSTS when served over HTTPS.",
+ "If you loosen CSP for a custom deployment, review markdown, Mermaid, fonts, images, and script sources together before publishing.",
+ ],
+ },
+ {
+ title: "Known limitations",
+ body: [
+ "URL fragments are client-side, but they are still visible to the browser, local machine, extensions, and anyone who receives the link.",
+ "Self-hosted UUID mode is a different deployment mode and stores payloads server-side by design.",
+ "The viewer treats payloads as untrusted input, but the safest policy is to keep sensitive material out of links entirely.",
+ ],
+ },
+] as const;
+
+/**
+ * Public security page documenting the static host boundary and renderer safety posture.
+ * Keeps the copy direct and linkable for operators, reviewers, and security reports.
+ */
+export default function SecurityPage() {
+ return (
+ Public security notes
+ agent-render is a static artifact viewer. The core security boundary is simple: artifact data belongs in
+ the URL fragment and is decoded in the browser.
+ Reports
+ Report security issues through the GitHub repository. Use a private vulnerability report when available;
+ otherwise open a minimal issue asking for a private contact path and do not include exploit details in public.
+ Security
+ {section.title}
+
+ {section.body.map((item) => (
+
+ Security contact
+
{step}
Security
-+ Read the security page +
The payload never leaves the URL hash. Rendering is entirely client-side.
-Hosting
@@ -811,6 +818,11 @@ export function ViewerShell() { )} + +
Public security notes
- agent-render is a static artifact viewer. The core security boundary is simple: artifact data belongs in - the URL fragment and is decoded in the browser. + agent-render is a static artifact viewer. Its core host boundary is simple: artifact data lives in the + URL fragment, so the static host does not receive it as part of the initial page request.
diff --git a/src/components/viewer-shell.tsx b/src/components/viewer-shell.tsx index 5da8403..4fa1eb9 100644 --- a/src/components/viewer-shell.tsx +++ b/src/components/viewer-shell.tsx @@ -805,7 +805,7 @@ export function ViewerShell() {Security
Read the security page- The payload never leaves the URL hash. Rendering is entirely client-side. + Fragment payloads stay out of the static host request path, but links are not secret-safe.
- View markdown, code, diffs, CSV, and JSON from a single static link. Nothing leaves the browser. + View markdown, code, diffs, CSV, and JSON from a fragment link the static host does not receive.
{activeArtifact ? `${getArtifactSubtitle(activeArtifact)} selected.` - : "Select a fragment above to render it here. Everything stays in the URL."} + : "Select a fragment above to render it here. Payloads stay off the host request path, but links still need care."}