/docs/security

Document policy and security

Manage authentication and CSP in the Hono app, then apply language, nonce, and external iframe settings to each screen.

Review the routes created by default

By default, the router creates the index, viewer, slide document, presentation, presenter, and print routes. hono-decks does not add authentication to those routes. Only the external /:slug/embed route is opt-in. Disable unused surfaces through router.pages.

The Hono application owns authentication, authorization, and CSP headers. hono-decks applies the resolved language and nonce to the HTML it generates.

Detect language in Hono and pass it to every surface

Hono's languageDetector() checks query, cookie, then Accept-Language by default and exposes the result through c.get("language").

TypeScript
import { languageDetector } from "hono/language"

app.use(languageDetector({
  supportedLanguages: ["ja", "en"],
  fallbackLanguage: "en",
}))

Pass that value to document.lang so index, viewer, render, print, presentation, and presenter all receive the same <html lang>.

Use the same nonce in the CSP header and generated HTML

Create one nonce per request and store it in Hono Variables. The value in the CSP header must match the value returned by document.nonce.

Application middleware
import { Hono } from "hono"

type AppEnv = {
  Variables: {
    cspNonce: string
    language: string
  }
}

const app = new Hono<AppEnv>()

app.use("*", async (c, next) => {
  const nonce = crypto.randomUUID()
  c.set("cspNonce", nonce)
  c.header("Content-Security-Policy", [
    "default-src 'self'",
    "object-src 'none'",
    "base-uri 'self'",
    `script-src 'self' 'nonce-${nonce}'`,
    `style-src 'self' 'nonce-${nonce}'`,
    "img-src 'self' data: https:",
    "frame-src 'self' https://www.youtube.com",
  ].join("; "))
  await next()
})
hono-decks configuration
decks.router({
  document: {
    lang: ({ c }) => c.get("language"),
    nonce: ({ c }) => c.get("cspNonce"),
    head: ({ surface }) => (
      <meta name="hono-decks-surface" content={surface} />
    ),
  },
})

hono-decks adds the nonce to package-generated <style> and <script> elements. Add required origins to the application CSP when using YouTube, remote images, or islands.

Allow explicit embedding origins

Enable embed only for external iframes and list allowed parent origins.

TypeScript
decks.router({
  embed: {
    frameAncestors: ["https://blog.example.com"],
    document: { nonce: ({ c }) => c.get("cspNonce") },
    viewer: { controls: false },
  },
})

Inspect real responses before release

Drafts and presenter
Confirm routes that should stay private return 404 in production.
CSP
Check the browser console for CSP violations and confirm header and HTML nonces match.
External embed
Verify the allowed parent loads and an unlisted origin is rejected.

Open the Document and Embed API →