/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").
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.
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()
})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.
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.