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.
Supported headers
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.
Quick start
This guide will show you how to add our recommended
1. Install Nosecone
In your project root, run the following command to install the Arcjet Nosecone library for your framework:
bun add nosecone
deno add npm:nosecone
npm i @nosecone/next
pnpm add @nosecone/next
yarn add @nosecone/next
npm i nosecone
pnpm add nosecone
yarn add nosecone
npm i @nosecone/sveltekit
pnpm add @nosecone/sveltekit
yarn add @nosecone/sveltekit
2. Configure your application
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", { headers: nosecone(), });});
Nosecone creates headers that can be used directly as the Response
headers in
your Bun server.
import nosecone from "nosecone";
Bun.serve({ port: 3000, async fetch(req: Request) { return new Response("Hello world", { headers: nosecone(), }); },});
import nosecone from "nosecone";
Bun.serve({ port: 3000, async fetch(req) { return new Response("Hello world", { headers: nosecone(), }); },});
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();
We recommend you remove any export const config = ...
from your middleware so
Nosecone will run on every route. This ensures the headers are applied to all
requests.
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 await connection();
// ...}
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 await connection();
// ...}
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 noStore();
// ...}
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 noStore();
// ...}
We recommend you remove any export const config = ...
from your middleware so
Nosecone will run on every route. This ensures the headers are applied to all
requests.
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} */const config = { preprocess: vitePreprocess(),
kit: { // Apply CSP with Nosecone defaults csp: csp(), adapter: adapter(), },};
export default config;
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" }); res.end("Hello world");});
server.listen(3000);
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" }); res.end("Hello world");});
server.listen(3000);
3. Run your application
bun run --hot index.ts
bun run --hot index.js
deno run --watch index.ts
npm run dev
pnpm run dev
yarn run dev
npx tsx index.ts
node index.js
npm run dev
pnpm run dev
yarn run dev
4. Inspect the headers
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:
HTTP/1.1 200 OKContent-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-corpCross-Origin-Opener-Policy: same-originCross-Origin-Resource-Policy: same-originReferrer-Policy: no-referrerX-Content-Type-Options: nosniffX-DNS-Prefetch-Control: offX-Frame-Options: SAMEORIGINX-XSS-Protection: 0origin-agent-cluster: ?1x-download-options: noopenx-permitted-cross-domain-policies: nonecontent-type: text/plain;charset=utf-8Date: Wed, 27 Nov 2024 20:13:30 GMTContent-Length: 11
Using the the curl command, we can inspect the headers:
curl -I -X GET localhost:3000
The printed headers should look something like:
HTTP/1.1 200 OKcontent-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-corpcross-origin-opener-policy: same-origincross-origin-resource-policy: same-originorigin-agent-cluster: ?1referrer-policy: no-referrerstrict-transport-security: max-age=31536000; includeSubDomainsx-content-type-options: nosniffx-dns-prefetch-control: offx-download-options: noopenx-frame-options: SAMEORIGINx-permitted-cross-domain-policies: nonex-xss-protection: 0content-type: text/plain;charset=UTF-8vary: Accept-Encodingcontent-length: 13date: 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:
HTTP/1.1 200 OKcontent-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-corpcross-origin-opener-policy: same-origincross-origin-resource-policy: same-originorigin-agent-cluster: ?1referrer-policy: no-referrerstrict-transport-security: max-age=31536000; includeSubDomainsx-content-type-options: nosniffx-dns-prefetch-control: offx-download-options: noopenx-frame-options: SAMEORIGINx-permitted-cross-domain-policies: nonex-xss-protection: 0Vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept-Encodinglink: </_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-revalidateX-Powered-By: Next.jsContent-Type: text/html; charset=utf-8Date: Wed, 27 Nov 2024 21:01:32 GMTConnection: keep-aliveKeep-Alive: timeout=5Transfer-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:
HTTP/1.1 200 OKcontent-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-corpcross-origin-opener-policy: same-origincross-origin-resource-policy: same-originorigin-agent-cluster: ?1referrer-policy: no-referrerstrict-transport-security: max-age=31536000; includeSubDomainsx-content-type-options: nosniffx-dns-prefetch-control: offx-download-options: noopenx-frame-options: SAMEORIGINx-permitted-cross-domain-policies: nonex-xss-protection: 0Content-Type: text/plainDate: Wed, 27 Nov 2024 21:05:50 GMTConnection: keep-aliveKeep-Alive: timeout=5Transfer-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:
HTTP/1.1 200 OKAccess-Control-Allow-Origin: *content-length: 1475content-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-requestscontent-type: text/htmlcross-origin-embedder-policy: require-corpcross-origin-opener-policy: same-origincross-origin-resource-policy: same-originetag: "bz71zn"origin-agent-cluster: ?1referrer-policy: no-referrerstrict-transport-security: max-age=31536000; includeSubDomainsx-content-type-options: nosniffx-dns-prefetch-control: offx-download-options: noopenx-frame-options: SAMEORIGINx-permitted-cross-domain-policies: nonex-sveltekit-page: truex-xss-protection: 0Date: Wed, 27 Nov 2024 15:23:13 GMTConnection: keep-aliveKeep-Alive: timeout=5
What next?
Explore
Arcjet can protect your entire app or individual routes with just a few lines of code. Using the main Arcjet SDK you can setup bot protection, rate limiting for your API, minimize fraudulent registrations with the signup form protection and more.
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.