{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/6] 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() { )} + +
URL explainer
++ The long part after #agent-render= is the artifact itself, compressed into the URL fragment so a static host can show it without receiving the content in the page request. +
+The shape
++ https://agent-render.com/#agent-render=v1.arx.1.<compressed-payload> +
++ Everything before # loads the app. Everything after # stays in the browser and tells the app what to render. +
+v1
++ The payload format version. It lets old and new links fail clearly instead of guessing. +
+arx
++ The compression method. It uses an agent-render dictionary, Brotli compression, and URL-safe text encoding to keep rich artifacts linkable. +
+Privacy
++ The static host does not receive fragment contents during the page request. The link is still not a secret: browser history, copied URLs, screenshots, logs from tools that inspect the full URL, and future client-side analytics can expose it. +
+In 30 seconds
++ A normal page URL asks the server for a route. An agent-render URL also carries a compressed artifact after the hash mark. Browsers do not send that hash to the server in the initial request, so the static app loads first and then decodes the artifact locally. +
++ The weird-looking text is a transport format, not a tracking code. Shorter codecs like deflate and arx make markdown, code, diffs, CSV, and JSON fit into shareable links. +
++ Use fragment links for quick static sharing. Use the optional self-hosted UUID mode when the payload is too large, the target chat app mangles long links, or you need a short URL and accept server-side storage. +
+- 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 single static link while the host stays out of the payload.
#{PAYLOAD_FRAGMENT_KEY}=v1.<codec>.<payload>
+ +Why
@@ -798,8 +804,12 @@ export function ViewerShell() {Security
- The payload never leaves the URL hash. Rendering is entirely client-side. + The static host does not receive the URL hash, but copied links, browser history, screenshots, and client-side analytics can still expose it.
+ + Read the privacy tradeoff +Hosting
From d3aafd63660551d48f47d51e61f0adf244356dd0 Mon Sep 17 00:00:00 2001 From: Aanish Bhirud <47579874+baanish@users.noreply.github.com> Date: Thu, 7 May 2026 21:10:10 -0400 Subject: [PATCH 3/6] fix: tighten security page copy --- src/app/security/page.tsx | 8 ++++---- src/components/viewer-shell.tsx | 2 +- tests/e2e/viewer.spec.ts | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/security/page.tsx b/src/app/security/page.tsx index 4b64966..ad762a1 100644 --- a/src/app/security/page.tsx +++ b/src/app/security/page.tsx @@ -10,8 +10,8 @@ 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.", + "Static mode sends HTML, CSS, and JavaScript to the browser. Artifact payloads are not sent to the static host as part of the initial page request.", + "Fragment payloads stay out of the HTTP request path, query string, and request body for the static host.", "The server can still receive normal static asset requests, IP address, user agent, referrer headers, and access logs from the hosting layer.", ], }, @@ -67,8 +67,8 @@ export default function SecurityPage() {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."}