diff --git a/fresh.config.ts b/fresh.config.ts index 30b0efe44..feba40422 100644 --- a/fresh.config.ts +++ b/fresh.config.ts @@ -4,6 +4,7 @@ import twindConfig from "./twind.config.ts"; import kvOAuthPlugin from "./plugins/kv_oauth.ts"; import sessionPlugin from "./plugins/session.ts"; import errorHandling from "./plugins/error_handling.ts"; +import securityHeaders from "./plugins/security_headers.ts"; import { FreshOptions } from "$fresh/server.ts"; export default { @@ -12,5 +13,6 @@ export default { sessionPlugin, twindPlugin(twindConfig), errorHandling, + securityHeaders, ], } as FreshOptions; diff --git a/plugins/security_headers.ts b/plugins/security_headers.ts new file mode 100644 index 000000000..becde9c29 --- /dev/null +++ b/plugins/security_headers.ts @@ -0,0 +1,36 @@ +// Copyright 2023 the Deno authors. All rights reserved. MIT license. +import { type Plugin } from "$fresh/server.ts"; + +export default { + name: "security-headers", + middlewares: [ + { + path: "/", + middleware: { + handler: async (req, ctx) => { + if ( + ctx.destination !== "route" || + new URL(req.url).pathname.startsWith("/api") + ) return await ctx.next(); + + const response = await ctx.next(); + + /** @todo(Jabolol) Implement `Content-Security-Policy` once https://github.com/denoland/fresh/pull/1787 lands */ + response.headers.set( + "Strict-Transport-Security", + "max-age=63072000;", + ); + response.headers.set( + "Referrer-Policy", + "strict-origin-when-cross-origin", + ); + response.headers.set("X-Content-Type-Options", "nosniff"); + response.headers.set("X-Frame-Options", "SAMEORIGIN"); + response.headers.set("X-XSS-Protection", "1; mode=block"); + + return response; + }, + }, + }, + ], +} as Plugin; diff --git a/plugins/security_headers_test.ts b/plugins/security_headers_test.ts new file mode 100644 index 000000000..33e20a481 --- /dev/null +++ b/plugins/security_headers_test.ts @@ -0,0 +1,23 @@ +// Copyright 2023 the Deno authors. All rights reserved. MIT license. +import { createHandler } from "$fresh/server.ts"; +import { assertEquals } from "std/assert/assert_equals.ts"; +import manifest from "@/fresh.gen.ts"; +import options from "@/fresh.config.ts"; + +const handler = await createHandler(manifest, options); + +Deno.test("[middleware] security headers are present", async () => { + const resp = await handler(new Request("http://localhost")); + + assertEquals( + resp.headers.get("strict-transport-security"), + "max-age=63072000;", + ); + assertEquals( + resp.headers.get("referrer-policy"), + "strict-origin-when-cross-origin", + ); + assertEquals(resp.headers.get("x-content-type-options"), "nosniff"); + assertEquals(resp.headers.get("x-frame-options"), "SAMEORIGIN"); + assertEquals(resp.headers.get("x-xss-protection"), "1; mode=block"); +});