Arcjet Nosecone is an open source library
that helps set security headers such as Content-Security-Policy
(CSP),
Strict-Transport-Security
(HSTS), and X-Content-Type-Options
in JS
applications built with Bun, Deno, Next.js, Node.js, or SvelteKit.
What are Arcjet utilities? Arcjet utilities are independent libraries
that do not require the use of the main Arcjet SDK—they can be used with or
without other Arcjet rules.
We take the pain out of implementing security tasks through these utilities
to provide a security as code approach to developer-first security.
Nosecone makes it easy to add and configure these headers:
Content-Security-Policy
(CSP)
Cross-Origin-Embedder-Policy
(COEP)
Cross-Origin-Opener-Policy
Cross-Origin-Resource-Policy
Origin-Agent-Cluster
Referrer-Policy
Strict-Transport-Security
(HSTS)
X-Content-Type-Options
X-DNS-Prefetch-Control
X-Download-Options
X-Frame-Options
X-Permitted-Cross-Domain-Policies
X-XSS-Protection
See the reference guide for full details on each option.
Note
If you are using Express, NestJS, or Remix we recommend using the excellent
Helmet library, which informed much of our work
on Nosecone.
Quick start
This guide will show you how to add our recommended
default security headers.
1. Install Nosecone
In your project root, run the following command to install the Arcjet Nosecone
library for your framework:
npm i @nosecone/sveltekit
pnpm add @nosecone/sveltekit
yarn add @nosecone/sveltekit
Nosecone creates headers that can be used directly as the Response
headers in
your Deno server.
import nosecone from " npm:nosecone " ;
Deno . serve ( { port : 3000 }, async ( req ) => {
return new Response ( " Hello world " , {
Nosecone creates headers that can be used directly as the Response
headers in
your Bun server.
import nosecone from " nosecone " ;
async fetch ( req : Request ) {
return new Response ( " Hello world " , {
import nosecone from " nosecone " ;
return new Response ( " Hello world " , {
Nosecone applies headers to all your routes via middleware in your Next.js
application.
import { createMiddleware } from " @nosecone/next " ;
// Remove your middleware matcher so Nosecone runs on every route.
export default createMiddleware () ;
import { createMiddleware } from " @nosecone/next " ;
// Remove your middleware matcher so Nosecone runs on every route.
export default createMiddleware () ;
You also need to opt-out of static generation in your application. See the
@nosecone/next
reference
guide for more details
about this requirement.
To opt-out of static generation, modify your layout file with the following:
import { connection } from " next/server " ;
import type { PropsWithChildren } from " react " ;
export default async function RootLayout ( props : PropsWithChildren ) {
// Opt-out of static generation for every page so the CSP nonce can be applied
import { connection } from " next/server " ;
export default async function RootLayout ( props ) {
// Opt-out of static generation for every page so the CSP nonce can be applied
import { unstable_noStore as noStore } from " next/cache " ;
import type { PropsWithChildren } from " react " ;
export default function RootLayout ( props : PropsWithChildren ) {
// Opt-out of static generation for every page so the CSP nonce can be applied
import { unstable_noStore as noStore } from " next/cache " ;
export default function RootLayout ( props ) {
// Opt-out of static generation for every page so the CSP nonce can be applied
Nosecone applies headers to all your routes via hooks in your SvelteKit
application.
Create the src/hooks.server.ts
file in your project with the contents:
import { createHook } from " @nosecone/sveltekit " ;
import { sequence } from " @sveltejs/kit/hooks " ;
export const handle = sequence ( createHook ()) ;
Create the src/hooks.server.js
file in your project with the contents:
import { createHook } from " @nosecone/sveltekit " ;
import { sequence } from " @sveltejs/kit/hooks " ;
export const handle = sequence ( createHook ()) ;
SvelteKit provides the Content-Security-Policy header via the framework, so
Nosecone helps you to configure it.
Update your svelte.config.js
to configure csp
:
import adapter from " @sveltejs/adapter-auto " ;
import { vitePreprocess } from " @sveltejs/vite-plugin-svelte " ;
import { csp } from " @nosecone/sveltekit "
/** @ type { import('@sveltejs/kit').Config } */
preprocess : vitePreprocess () ,
// Apply CSP with Nosecone defaults
Nosecone creates headers that can be applied directly with res.setHeaders()
in
your Node.js server.
import nosecone from " nosecone " ;
import * as http from " node:http " ;
const server = http . createServer ( async function ( req , res ) {
res . setHeaders ( nosecone ()) ;
res . writeHead ( 200 , { " Content-Type " : " text/plain " } ) ;
import nosecone from " nosecone " ;
import * as http from " node:http " ;
const server = http . createServer ( async function (
req : http . IncomingMessage ,
res : http . ServerResponse ,
res . setHeaders ( nosecone ()) ;
res . writeHead ( 200 , { " Content-Type " : " text/plain " } ) ;
3. Run your application
deno run --watch index.ts
The default headers apply a pragmatic set of security headers to your
application, but may break things (particularly the CSP header).
We recommend you test your application thoroughly and tweak the settings to
ensure it continues to work as expected.
Using the the curl command, we can inspect the headers:
curl -I -X GET localhost:3000
The printed headers should look something like:
Content-Security-Policy: base-uri 'none'; child-src 'none'; connect-src 'self'; default-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; frame-src 'none'; img-src 'self' blob: data:; manifest-src 'self'; media-src 'self'; object-src 'none'; script-src 'self'; style-src 'self'; worker-src 'self'; upgrade-insecure-requests;
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Referrer-Policy: no-referrer
X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control: off
X-Frame-Options: SAMEORIGIN
x-download-options: noopen
x-permitted-cross-domain-policies: none
content-type: text/plain;charset=utf-8
Date: Wed, 27 Nov 2024 20:13:30 GMT
Using the the curl command, we can inspect the headers:
curl -I -X GET localhost:3000
The printed headers should look something like:
content-security-policy: base-uri 'none'; child-src 'none'; connect-src 'self'; default-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; frame-src 'none'; img-src 'self' blob: data:; manifest-src 'self'; media-src 'self'; object-src 'none'; script-src 'self'; style-src 'self'; worker-src 'self'; upgrade-insecure-requests;
cross-origin-embedder-policy: require-corp
cross-origin-opener-policy: same-origin
cross-origin-resource-policy: same-origin
referrer-policy: no-referrer
strict-transport-security: max-age=31536000; includeSubDomains
x-content-type-options: nosniff
x-dns-prefetch-control: off
x-download-options: noopen
x-frame-options: SAMEORIGIN
x-permitted-cross-domain-policies: none
content-type: text/plain;charset=UTF-8
date: Wed, 27 Nov 2024 20:21:41 GMT
Using the the curl command, we can inspect the headers:
curl -I -X GET localhost:3000
The printed headers should look something like:
content-security-policy: base-uri 'none'; child-src 'none'; connect-src 'self'; default-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; frame-src 'none'; img-src 'self' blob: data:; manifest-src 'self'; media-src 'self'; object-src 'none'; script-src 'nonce-YWUwNGIwMTMtOTgwYi00MWNiLWI3M2QtMjBkYWQwODMxZTNi' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; worker-src 'self'; upgrade-insecure-requests;
cross-origin-embedder-policy: require-corp
cross-origin-opener-policy: same-origin
cross-origin-resource-policy: same-origin
referrer-policy: no-referrer
strict-transport-security: max-age=31536000; includeSubDomains
x-content-type-options: nosniff
x-dns-prefetch-control: off
x-download-options: noopen
x-frame-options: SAMEORIGIN
x-permitted-cross-domain-policies: none
Vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept-Encoding
link: </_next/static/media/a34f9d1faa5f3315-s.p.woff2>; rel=preload; as="font"; crossorigin=""; nonce="YWUwNGIwMTMtOTgwYi00MWNiLWI3M2QtMjBkYWQwODMxZTNi"; type="font/woff2", </vercel.svg>; rel=preload; as="image", </next.svg>; rel=preload; as="image", </_next/static/css/app/layout.css?v=1732741292630>; rel=preload; as="style"; nonce="YWUwNGIwMTMtOTgwYi00MWNiLWI3M2QtMjBkYWQwODMxZTNi"
Cache-Control: no-store, must-revalidate
Content-Type: text/html; charset=utf-8
Date: Wed, 27 Nov 2024 21:01:32 GMT
Transfer-Encoding: chunked
Using the the curl command, we can inspect the headers:
curl -I -X GET localhost:3000
The printed headers should look something like:
content-security-policy: base-uri 'none'; child-src 'none'; connect-src 'self'; default-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; frame-src 'none'; img-src 'self' blob: data:; manifest-src 'self'; media-src 'self'; object-src 'none'; script-src 'self'; style-src 'self'; worker-src 'self'; upgrade-insecure-requests;
cross-origin-embedder-policy: require-corp
cross-origin-opener-policy: same-origin
cross-origin-resource-policy: same-origin
referrer-policy: no-referrer
strict-transport-security: max-age=31536000; includeSubDomains
x-content-type-options: nosniff
x-dns-prefetch-control: off
x-download-options: noopen
x-frame-options: SAMEORIGIN
x-permitted-cross-domain-policies: none
Date: Wed, 27 Nov 2024 21:05:50 GMT
Transfer-Encoding: chunked
Using the the curl command, we can inspect the headers:
curl -I -X GET localhost:5173
The printed headers should look something like:
Access-Control-Allow-Origin: *
content-security-policy: child-src 'none'; default-src 'self'; frame-src 'none'; worker-src 'self'; connect-src 'self'; font-src 'self'; img-src 'self' blob: data:; manifest-src 'self'; media-src 'self'; object-src 'none'; script-src 'self' 'nonce-RsMu23BBsCD1PV101d7Prg=='; style-src 'self' 'unsafe-inline'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests
cross-origin-embedder-policy: require-corp
cross-origin-opener-policy: same-origin
cross-origin-resource-policy: same-origin
referrer-policy: no-referrer
strict-transport-security: max-age=31536000; includeSubDomains
x-content-type-options: nosniff
x-dns-prefetch-control: off
x-download-options: noopen
x-frame-options: SAMEORIGIN
x-permitted-cross-domain-policies: none
Date: Wed, 27 Nov 2024 15:23:13 GMT
What next?
Get help
Need help with anything? Email support@arcjet.com to get support from our
engineering team, join our Discord , or open an
issue on GitHub .
Discussion