Arcjet Nosecone reference
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.
Quick start
Section titled “Quick start”To get started, follow our quick start guide.
Next.js security headers middleware
Section titled “Next.js security headers middleware”Next.js security headers can be set using the @nosecone/next adapter.
npm i @nosecone/nextpnpm add @nosecone/nextyarn add @nosecone/nextThis provides defaults to ensure Next.js applications work in production &
development environments and the createMiddleware(options?: NoseconeOptions)
API to create a Next.js middleware that runs on every request.
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.
If the Content-Security-Policy header is specified via middleware, Next.js
will look for a script-src that starts with nonce- and apply it for each
<script> generated by the framework. This requires that opting-out of static
generation, which requires modifying your Layout:
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();
// ...}If you cannot opt-out of static generation, you will need to remove the
nonce() function from the provided defaults.
Example configurations
Section titled “Example configurations”Plausible Analytics + YouTube embed
Section titled “Plausible Analytics + YouTube embed”This configuration applies the default Nosecone headers, but allows scripts from and connections to Plausible Analytics and YouTube embeds from the YouTube No Cookie domain (privacy enhanced mode).
It also sets the upgrade insecure requests directive to true in production to
ensure all requests are served with HTTPS.
This configuration also enables the special handler for the Vercel Toolbar when deployed to Vercel preview environments.
import * as nosecone from "@nosecone/next";
const noseconeConfig: nosecone.NoseconeOptions = { ...nosecone.defaults, contentSecurityPolicy: { ...nosecone.defaults.contentSecurityPolicy, directives: { ...nosecone.defaults.contentSecurityPolicy.directives, scriptSrc: [ ...nosecone.defaults.contentSecurityPolicy.directives.scriptSrc, "https://plausible.io", // Analytics ], connectSrc: [ ...nosecone.defaults.contentSecurityPolicy.directives.connectSrc, "https://plausible.io", // Analytics ], // Set some URLs as `frameSrc` so don't include the default of `'none'` frameSrc: ["https://www.youtube-nocookie.com"], // We only set this in production because the server may be started // without HTTPS upgradeInsecureRequests: process.env.NODE_ENV === "production", }, }, crossOriginEmbedderPolicy: { // YouTube embeds are not served with the correct headers to support being // loaded with any COEP other than `unsafe-none`. // See: // * https://issuetracker.google.com/issues/240387105 // * https://issuetracker.google.com/issues/351843802 policy: "unsafe-none", },} as const;
const noseconeMiddleware = nosecone.createMiddleware( process.env.VERCEL_ENV === "preview" ? nosecone.withVercelToolbar(noseconeConfig) : noseconeConfig,);
export default noseconeMiddleware;Clerk authentication
Section titled “Clerk authentication”This configuration applies the default Nosecone headers, but allows scripts from and connections to Clerk for authentication.
import * as nosecone from "@nosecone/next";
const noseconeConfig: nosecone.NoseconeOptions = { ...nosecone.defaults, contentSecurityPolicy: { ...nosecone.defaults.contentSecurityPolicy, directives: { ...nosecone.defaults.contentSecurityPolicy.directives, scriptSrc: [ ...nosecone.defaults.contentSecurityPolicy.directives.scriptSrc, "https://*.clerk.accounts.dev", ], connectSrc: [ ...nosecone.defaults.contentSecurityPolicy.directives.connectSrc, "https://*.clerk.accounts.dev", "https://clerk-telemetry.com", ], workerSrc: [ ...nosecone.defaults.contentSecurityPolicy.directives.workerSrc, "blob:", "https://*.clerk.accounts.dev", ], imgSrc: [ ...nosecone.defaults.contentSecurityPolicy.directives.imgSrc, "https://img.clerk.com", ], objectSrc: [ ...nosecone.defaults.contentSecurityPolicy.directives.objectSrc, ], // We only set this in production because the server may be started // without HTTPS upgradeInsecureRequests: process.env.NODE_ENV === "production", }, },} as const;
export default nosecone.createMiddleware(noseconeConfig);Chaining middleware
Section titled “Chaining middleware”If you are using multiple Next.js middleware functions, you can chain them together to add the security headers.
Auth.js example
Section titled “Auth.js example”This example shows how to chain Arcjet with Auth.js middleware:
import { createMiddleware, defaults } from "@nosecone/next";import { auth } from "./auth";
// Nosecone security headers configuration// https://docs.arcjet.com/nosecone/quick-startconst noseconeOptions = { ...defaults,};
const securityHeaders = createMiddleware(noseconeOptions);
// @ts-ignore: This type will be correct when used in a real appexport default auth(async (req) => { // Redirect to signin page if not authenticated // Example from https://authjs.dev/getting-started/session-management/protecting if (!req.auth && !req.nextUrl.pathname.startsWith("/auth")) { const newUrl = new URL("/auth/signin", req.nextUrl.origin); return Response.redirect(newUrl); }
// Otherwise return security headers return securityHeaders();});
export const config = { matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],};import { type NoseconeOptions, createMiddleware, defaults,} from "@nosecone/next";// @ts-ignore: Import your auth library. See https://authjs.devimport { auth } from "./auth";
// Nosecone security headers configuration// https://docs.arcjet.com/nosecone/quick-startconst noseconeOptions: NoseconeOptions = { ...defaults,};
const securityHeaders = createMiddleware(noseconeOptions);
// @ts-ignore: This type will be correct when used in a real appexport default auth(async (req) => { // Redirect to signin page if not authenticated // Example from https://authjs.dev/getting-started/session-management/protecting if (!req.auth && !req.nextUrl.pathname.startsWith("/auth")) { const newUrl = new URL("/auth/signin", req.nextUrl.origin); return Response.redirect(newUrl); }
// Otherwise return security headers return securityHeaders();});
export const config = { matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],};Multiple middleware functions
Section titled “Multiple middleware functions”If you have multiple middleware functions you will need to chain them together. We have written a small utility to help run different middleware for different paths.
In this example code, the Nosecone middleware is run on every route path. You can add other middleware into the array and add more paths to run different middleware.
Additional resources
Section titled “Additional resources”- Next.js Content-Security-Policy guide
- Next Forge SaaS template security headers example
- Arcjet example app with Plausible analytics
SvelteKit security headers configuration
Section titled “SvelteKit security headers configuration”The @nosecone/sveltekit adapter provides defaults to ensure SvelteKit is
configured correctly.
npm i @nosecone/sveltekitpnpm add @nosecone/sveltekityarn add @nosecone/sveltekitThis also provides the csp(options?: ContentSecurityPolicyConfig) function
to translate Nosecone configuration to SvelteKit configuration in
svelte.config.js, and the createHook(options?: NoseconeOptions) function to
create a SvelteKit hook that runs on every request.
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;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());Additional resources:
nosecone(options?: NoseconeOptions): Headers
Section titled “nosecone(options?: NoseconeOptions): Headers”The default export is a function that returns a set of Headers to apply to a
request that help you secure your application. The defaults should work for many
applications, but all can be configured or disabled for specific requirements.
NoseconeOptions
Section titled “NoseconeOptions”All entries in NoseconeOptions can be enabled with defaults by specifying
true or disabled by specifying false. Additionally, many allow individual
configuration via their own configuration objects, which override any defaults.
You can use object spread, e.g. ...defaults, to combine the default values
with your overrides.
interface NoseconeOptions { /** * Configure the `Content-Security-Policy` header, which helps mitigate a * large number of attacks, such as cross-site scripting. */ contentSecurityPolicy?: ContentSecurityPolicyConfig | boolean; /** * Configure the `Cross-Origin-Embedder-Policy` header, which helps control * what resources can be loaded cross-origin. */ crossOriginEmbedderPolicy?: CrossOriginEmbedderPolicyConfig | boolean; /** * Configure the `Cross-Origin-Opener-Policy` header, which helps * process-isolate your page. */ crossOriginOpenerPolicy?: CrossOriginOpenerPolicyConfig | boolean; /** * Configure the `Cross-Origin-Resource-Policy` header, which blocks others * from loading your resources cross-origin in some cases. */ crossOriginResourcePolicy?: CrossOriginResourcePolicyConfig | boolean; /** * Configure the `Origin-Agent-Cluster` header, which provides a mechanism to * allow web applications to isolate their origins from other processes. */ originAgentCluster?: boolean; /** * Configure the `Referrer-Policy` header, which controls what information is * set in the `Referer` request header. */ referrerPolicy?: ReferrerPolicyConfig | boolean; /** * Configure the `Strict-Transport-Security` header, which tells browsers to * prefer HTTPS instead of insecure HTTP. */ strictTransportSecurity?: StrictTransportSecurityConfig | boolean; /** * Configure the `X-Content-Type-Options` header, which helps mitigate MIME * type sniffing that can cause security issues. */ xContentTypeOptions?: boolean; /** * Configure the `X-DNS-Prefetch-Control` header, which helps control DNS * prefetching to improve user privacy at the expense of performance. */ xDnsPrefetchControl?: DnsPrefetchControlConfig | boolean; /** * Configure the `X-Download-Options` header, which prevents a user from * opening a file directly in Internet Explorer 8 to avoid prevent script * injection. */ xDownloadOptions?: boolean; /** * Configure the `X-Frame-Options` header, which helps mitigate clickjacking * attacks in legacy browsers. This header is superceded by a directive in the * `Content-Security-Policy` header. */ xFrameOptions?: FrameOptionsConfig | boolean; /** * Configure the `X-Permitted-Cross-Domain-Policies` header, which tells some * clients, like Adobe products, your domain's policy for loading cross-domain * content. */ xPermittedCrossDomainPolicies?: PermittedCrossDomainPoliciesConfig | boolean; /** * Disable the `X-XSS-Protection` header, which could introduce a browser * side-channel if enabled. */ xXssProtection?: boolean;}withVercelToolbar(options: NoseconeOptions): NoseconeOptions
Section titled “withVercelToolbar(options: NoseconeOptions): NoseconeOptions”Nosecone provides the withVercelToolbar utility function to augment Nosecone
options so the Vercel
Toolbar is
allowed by the various headers.
This follows the guidelines documented by Vercel under Using a Content Security
Policy,
but we recommend conditionally adding this based on process.env.VERCEL_ENV:
import nosecone, { defaults, withVercelToolbar } from "nosecone";import type { NoseconeOptions } from "nosecone";
const noseconeConfig: NoseconeOptions = { ...defaults, // Any customizations needed};
const headers = nosecone( process.env.VERCEL_ENV === "preview" ? withVercelToolbar(noseconeConfig) : noseconeConfig,);import nosecone, { defaults, withVercelToolbar } from "nosecone";
const noseconeConfig = { ...defaults, // Any customizations needed};
const headers = nosecone( process.env.VERCEL_ENV === "preview" ? withVercelToolbar(noseconeConfig) : noseconeConfig,);Headers
Section titled “Headers”Content-Security-Policy
Section titled “Content-Security-Policy”The Content-Security-Policy header is a powerful allow-list of what can happen
on your page which helps mitigate many attacks, such as cross-site scripting.
Types:
type StaticOrDynamic<S> = boolean | readonly (S | (() => S))[] | null;type Source = | `${string}.${string}` | "localhost" | `${string}.${string}:${number}` | `${string}.${string}:*` | `localhost:${number}` | "localhost:*" | `${string}://${string}.${string}` | `${string}://${string}.${string}:${number}` | `${string}://${string}.${string}:*` | `${string}://localhost` | `${string}://localhost:${number}` | `${string}://localhost:*` | "http:" | "https:" | "data:" | "mediastream:" | "blob:" | "filesystem:" | `'nonce-${string}'` | `'sha256-${string}'` | `'sha384-${string}'` | `'sha512-${string}'` | "'self'" | "'unsafe-eval'" | "'unsafe-hashes'" | "'unsafe-inline'" | "'wasm-unsafe-eval'" | "'none'";type HostSource = | `${string}.${string}` | "localhost" | `${string}.${string}:${number}` | `${string}.${string}:*` | `localhost:${number}` | "localhost:*" | `${string}://${string}.${string}` | `${string}://${string}.${string}:${number}` | `${string}://${string}.${string}:*` | `${string}://localhost` | `${string}://localhost:${number}` | `${string}://localhost:*`;type SchemeSource = | "http:" | "https:" | "data:" | "mediastream:" | "blob:" | "filesystem:";type FrameSource = | `${string}.${string}` | "localhost" | `${string}.${string}:${number}` | `${string}.${string}:*` | `localhost:${number}` | "localhost:*" | `${string}://${string}.${string}` | `${string}://${string}.${string}:${number}` | `${string}://${string}.${string}:*` | `${string}://localhost` | `${string}://localhost:${number}` | `${string}://localhost:*` | "http:" | "https:" | "data:" | "mediastream:" | "blob:" | "filesystem:" | "'self'" | "'none'";type ActionSource = "'strict-dynamic'" | "'report-sample'";type CspDirectives = { baseUri?: StaticOrDynamic<Source | ActionSource> | undefined; childSrc?: StaticOrDynamic<Source> | undefined; defaultSrc?: StaticOrDynamic<Source | ActionSource> | undefined; frameSrc?: StaticOrDynamic<Source> | undefined; workerSrc?: StaticOrDynamic<Source> | undefined; connectSrc?: StaticOrDynamic<Source> | undefined; fontSrc?: StaticOrDynamic<Source> | undefined; imgSrc?: StaticOrDynamic<Source> | undefined; manifestSrc?: StaticOrDynamic<Source> | undefined; mediaSrc?: StaticOrDynamic<Source> | undefined; objectSrc?: StaticOrDynamic<Source> | undefined; prefetchSrc?: StaticOrDynamic<Source> | undefined; scriptSrc?: StaticOrDynamic<Source | ActionSource> | undefined; scriptSrcElem?: StaticOrDynamic<Source> | undefined; scriptSrcAttr?: StaticOrDynamic<Source> | undefined; styleSrc?: StaticOrDynamic<Source | ActionSource> | undefined; styleSrcElem?: StaticOrDynamic<Source> | undefined; styleSrcAttr?: StaticOrDynamic<Source> | undefined; sandbox?: | readonly ( | "allow-downloads-without-user-activation" | "allow-forms" | "allow-modals" | "allow-orientation-lock" | "allow-pointer-lock" | "allow-popups" | "allow-popups-to-escape-sandbox" | "allow-presentation" | "allow-same-origin" | "allow-scripts" | "allow-storage-access-by-user-activation" | "allow-top-navigation" | "allow-top-navigation-by-user-activation" )[] | undefined; formAction?: StaticOrDynamic<Source | ActionSource> | undefined; frameAncestors?: | StaticOrDynamic< | `${string}.${string}` | "localhost" | `${string}.${string}:${number}` | `${string}.${string}:*` | `localhost:${number}` | "localhost:*" | `${string}://${string}.${string}` | `${string}://${string}.${string}:${number}` | `${string}://${string}.${string}:*` | `${string}://localhost` | `${string}://localhost:${number}` | `${string}://localhost:*` | "http:" | "https:" | "data:" | "mediastream:" | "blob:" | "filesystem:" | "'self'" | "'none'" > | undefined; navigateTo?: StaticOrDynamic<Source | ActionSource> | undefined; reportUri?: string[] | undefined | undefined; reportTo?: string[] | undefined | undefined; requireTrustedTypesFor?: readonly "script"[] | undefined; trustedTypes?: readonly string[] | undefined; upgradeInsecureRequests?: boolean | undefined | undefined;};type ContentSecurityPolicyConfig = { directives?: Readonly<CspDirectives> | undefined;};Defaults:
{ directives: { baseUri: ["'none'"], childSrc: ["'none'"], connectSrc: ["'self'"], defaultSrc: ["'self'"], fontSrc: ["'self'"], formAction: ["'self'"], frameAncestors: ["'none'"], frameSrc: ["'none'"], imgSrc: ["'self'", "blob:", "data:"], manifestSrc: ["'self'"], mediaSrc: ["'self'"], objectSrc: ["'none'"], scriptSrc: ["'self'"], styleSrc: ["'self'"], workerSrc: ["'self'"], },}The defaults will produce the following header:
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';Additional resources:
- Google’s CSP Evaluator
- CSP Validator
- Strict Content-Security-Policy article on web.dev
- MDN’s Content-Security-Policy guide
- OWASP Secure Headers Project - Content-Security-Policy
Cross-Origin-Embedder-Policy
Section titled “Cross-Origin-Embedder-Policy”The Cross-Origin-Embedder-Policy header helps control what resources can be
loaded cross-origin.
Types:
type CrossOriginEmbedderPolicyConfig = { policy?: | "require-corp" | "credentialless" | "unsafe-none" | undefined | undefined;};Defaults:
{ policy: "require-corp",}The defaults will produce the following header:
Cross-Origin-Embedder-Policy: require-corpAdditional resources:
- MDN’s Cross-Origin-Embedder-Policy reference
- OWASP Secure Headers Project - Cross-Origin-Embedder-Policy
Cross-Origin-Opener-Policy
Section titled “Cross-Origin-Opener-Policy”The Cross-Origin-Opener-Policy header helps process-isolate your page.
Types:
type CrossOriginOpenerPolicyConfig = { policy?: | "same-origin" | "same-origin-allow-popups" | "unsafe-none" | undefined | undefined;};Defaults:
{ policy: "same-origin",}The defaults will produce the following header:
Cross-Origin-Opener-Policy: same-originAdditional resources:
- MDN’s Cross-Origin-Opener-Policy reference
- OWASP Secure Headers Project - Cross-Origin-Opener-Policy
Cross-Origin-Resource-Policy
Section titled “Cross-Origin-Resource-Policy”The Cross-Origin-Resource-Policy header blocks others from loading your
resources cross-origin in some cases.
Types:
type CrossOriginResourcePolicyConfig = { policy?: "same-origin" | "same-site" | "cross-origin" | undefined | undefined;};Defaults:
{ policy: "same-origin",}The defaults will produce the following header:
Cross-Origin-Resource-Policy: same-originAdditional resources:
- Resource Policy FYI
- MDN’s Cross-Origin-Resource-Policy reference
- OWASP Secure Headers Project - Cross-Origin-Resource-Policy
Origin-Agent-Cluster
Section titled “Origin-Agent-Cluster”The Origin-Agent-Cluster header provides a mechanism to allow web applications
to isolate their origins from other processes.
Types:
type OriginAgentClusterConfig = boolean;Defaults:
true;The defaults will produce the following header:
Origin-Agent-Cluster: ?1Additional resources:
Referrer-Policy
Section titled “Referrer-Policy”The Referrer-Policy header controls what information is set in the Referer
request header.
Types:
type ReferrerPolicyConfig = { policy?: readonly ReferrerPolicyToken[] | undefined;};type ReferrerPolicyToken = | "" | "no-referrer" | "no-referrer-when-downgrade" | "same-origin" | "origin" | "strict-origin" | "origin-when-cross-origin" | "strict-origin-when-cross-origin" | "unsafe-url";Defaults:
{ policy: ["no-referrer"],}The defaults will produce the following header:
Referrer-Policy: no-referrerAdditional resources:
- Referer header: Privacy and security concerns
- MDN’s Referrer-Policy reference
- OWASP Secure Headers Project - Referrer-Policy
Strict-Transport-Security
Section titled “Strict-Transport-Security”The Strict-Transport-Security header tells browsers to prefer HTTPS instead of
insecure HTTP.
Types:
type StrictTransportSecurityConfig = { maxAge?: number | undefined | undefined; includeSubDomains?: boolean | undefined | undefined; preload?: boolean | undefined | undefined;};Defaults:
{ maxAge: 31536000, // 1 year includeSubDomains: true, preload: false,}The defaults will produce the following header:
Strict-Transport-Security: max-age=31536000; includeSubDomainsAdditional resources:
X-Content-Type-Options
Section titled “X-Content-Type-Options”The X-Content-Type-Options header helps mitigate MIME type sniffing that can
cause security issues.
Types:
type ContentTypeOptionsConfig = boolean;Defaults:
true;The defaults will produce the following header:
X-Content-Type-Options: nosniffAdditional resources:
- MDN’s X-Content-Type-Options reference
- MDN on MIME Sniffing
- OWASP Secure Headers Project - X-Content-Type-Options
X-DNS-Prefetch-Control
Section titled “X-DNS-Prefetch-Control”The X-DNS-Prefetch-Control header helps control DNS prefetching to improve
user privacy at the expense of performance.
Types:
type DnsPrefetchControlConfig = { allow?: boolean | undefined | undefined;};Defaults:
{ allow: false,}The defaults will produce the following header:
X-DNS-Prefetch-Control: offAdditional resources:
X-Download-Options (legacy)
Section titled “X-Download-Options (legacy)”The legacy X-Download-Options header prevented a user from opening a file
directly in Internet Explorer to avoid prevent script injection.
Types:
type DownloadOptionsCofig = boolean;Defaults:
true;The defaults will produce the following header:
X-Download-Options: noopenAdditional resources:
X-Frame-Options (legacy)
Section titled “X-Frame-Options (legacy)”The legacy X-Frame-Options header helped mitigate clickjacking attacks in
older browsers.
Types:
type FrameOptionsConfig = { action?: "deny" | "sameorigin" | undefined | undefined;};Defaults:
{ action: "sameorigin",}The defaults will produce the following header:
X-Frame-Options: SAMEORIGINAdditional resources:
X-Permitted-Cross-Domain-Policies
Section titled “X-Permitted-Cross-Domain-Policies”The X-Permitted-Cross-Domain-Policies header tells some clients, like Adobe
products, your domain’s policy for loading cross-domain content.
Types:
type PermittedCrossDomainPoliciesConfig = { permittedPolicies?: | "none" | "master-only" | "by-content-type" | "all" | undefined | undefined;};Defaults:
{ permittedPolicies: "none",}The defaults will produce the following header:
X-Permitted-Cross-Domain-Policies: noneAdditional resources:
X-XSS-Protection (legacy)
Section titled “X-XSS-Protection (legacy)”The legacy X-XSS-Protection header tried to mitigate XSS attacks, but
accidentally enabled side-channel attacks in older browsers, so Nosecone
disables it.
Types:
type XssProtectionConfig = boolean;Defaults:
true;The defaults will produce the following header:
X-XSS-Protection: 0Additional resources: