diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 0b54baed6..8dc9a70f1 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -17,6 +17,9 @@ "headers": "1.0.0-alpha.28", "ip": "1.0.0-alpha.28", "logger": "1.0.0-alpha.28", + "nosecone": "1.0.0-alpha.28", + "nosecone-next": "1.0.0-alpha.28", + "nosecone-sveltekit": "1.0.0-alpha.28", "protocol": "1.0.0-alpha.28", "redact": "1.0.0-alpha.28", "redact-wasm": "1.0.0-alpha.28", diff --git a/.github/release-please-config.json b/.github/release-please-config.json index 540f9e1f3..befd5ff12 100644 --- a/.github/release-please-config.json +++ b/.github/release-please-config.json @@ -96,6 +96,18 @@ "component": "@arcjet/logger", "skip-github-release": true }, + "nosecone": { + "component": "nosecone", + "skip-github-release": true + }, + "nosecone-next": { + "component": "@nosecone/next", + "skip-github-release": true + }, + "nosecone-sveltekit": { + "component": "@nosecone/sveltekit", + "skip-github-release": true + }, "protocol": { "component": "@arcjet/protocol", "skip-github-release": true @@ -156,6 +168,9 @@ "@arcjet/ip", "@arcjet/body", "@arcjet/logger", + "nosecone", + "@nosecone/next", + "@nosecone/sveltekit", "@arcjet/protocol", "@arcjet/redact", "@arcjet/redact-wasm", diff --git a/examples/nextjs-app-dir-rate-limit/middleware.ts b/examples/nextjs-app-dir-rate-limit/middleware.ts index 8a3a57639..7bb7bf869 100644 --- a/examples/nextjs-app-dir-rate-limit/middleware.ts +++ b/examples/nextjs-app-dir-rate-limit/middleware.ts @@ -18,4 +18,4 @@ const aj = arcjet({ ], }); -export default createMiddleware(aj); \ No newline at end of file +export default createMiddleware(aj); diff --git a/examples/nextjs-app-dir-rate-limit/package-lock.json b/examples/nextjs-app-dir-rate-limit/package-lock.json index 14d807714..510511be9 100644 --- a/examples/nextjs-app-dir-rate-limit/package-lock.json +++ b/examples/nextjs-app-dir-rate-limit/package-lock.json @@ -56,24 +56,6 @@ "next": ">=13" } }, - "../../env": { - "name": "@arcjet/env", - "version": "1.0.0-alpha.22", - "extraneous": true, - "license": "Apache-2.0", - "devDependencies": { - "@arcjet/eslint-config": "1.0.0-alpha.22", - "@arcjet/rollup-config": "1.0.0-alpha.22", - "@arcjet/tsconfig": "1.0.0-alpha.22", - "@jest/globals": "29.7.0", - "@rollup/wasm-node": "4.21.1", - "jest": "29.7.0", - "typescript": "5.5.4" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", diff --git a/examples/nextjs-app-dir-validate-email/app/layout.tsx b/examples/nextjs-app-dir-validate-email/app/layout.tsx index 323bd9c95..3de6543c1 100644 --- a/examples/nextjs-app-dir-validate-email/app/layout.tsx +++ b/examples/nextjs-app-dir-validate-email/app/layout.tsx @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import { connection } from "next/server"; import { Inter } from "next/font/google"; import "./globals.css"; @@ -9,11 +10,14 @@ export const metadata: Metadata = { description: "Generated by create next app", }; -export default function RootLayout({ +export default async function RootLayout({ children, }: { children: React.ReactNode; }) { + // Opt-out of static generation for every page so the CSP nonce can be applied + await connection() + return ( {children} diff --git a/examples/nextjs-app-dir-validate-email/middleware.ts b/examples/nextjs-app-dir-validate-email/middleware.ts new file mode 100644 index 000000000..28c473b10 --- /dev/null +++ b/examples/nextjs-app-dir-validate-email/middleware.ts @@ -0,0 +1,8 @@ +import { createMiddleware } from "@nosecone/next"; + +export const config = { + // matcher tells Next.js which routes to run the middleware on + matcher: ["/(.*)"], +}; + +export default createMiddleware(); diff --git a/examples/nextjs-app-dir-validate-email/package-lock.json b/examples/nextjs-app-dir-validate-email/package-lock.json index 3c7ab53bd..be00e8539 100644 --- a/examples/nextjs-app-dir-validate-email/package-lock.json +++ b/examples/nextjs-app-dir-validate-email/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@arcjet/next": "file:../../arcjet-next", + "@nosecone/next": "file:../../nosecone-next", "next": "15.0.1", "react": "^18", "react-dom": "^18" @@ -56,6 +57,24 @@ "next": ">=13" } }, + "../../nosecone-next": { + "version": "1.0.0-alpha.28", + "license": "Apache-2.0", + "dependencies": { + "nosecone": "1.0.0-alpha.28" + }, + "devDependencies": { + "@arcjet/eslint-config": "1.0.0-alpha.28", + "@arcjet/rollup-config": "1.0.0-alpha.28", + "@arcjet/tsconfig": "1.0.0-alpha.28", + "@rollup/wasm-node": "4.24.4", + "@types/node": "18.18.0", + "typescript": "5.6.3" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -788,6 +807,10 @@ "node": ">= 8" } }, + "node_modules/@nosecone/next": { + "resolved": "../../nosecone-next", + "link": true + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", diff --git a/examples/nextjs-app-dir-validate-email/package.json b/examples/nextjs-app-dir-validate-email/package.json index c4b7f4e0a..58bfe6d7b 100644 --- a/examples/nextjs-app-dir-validate-email/package.json +++ b/examples/nextjs-app-dir-validate-email/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@arcjet/next": "file:../../arcjet-next", + "@nosecone/next": "file:../../nosecone-next", "next": "15.0.1", "react": "^18", "react-dom": "^18" diff --git a/examples/sveltekit/package-lock.json b/examples/sveltekit/package-lock.json index 6eec024ef..bb93e5edc 100644 --- a/examples/sveltekit/package-lock.json +++ b/examples/sveltekit/package-lock.json @@ -8,7 +8,8 @@ "name": "sveltekit", "version": "0.0.1", "dependencies": { - "@arcjet/sveltekit": "file:../../arcjet-sveltekit" + "@arcjet/sveltekit": "file:../../arcjet-sveltekit", + "@nosecone/sveltekit": "file:../../nosecone-sveltekit" }, "devDependencies": { "@sveltejs/adapter-auto": "^3.3.1", @@ -57,6 +58,25 @@ "node": ">=18" } }, + "../../nosecone-sveltekit": { + "name": "@nosecone/sveltekit", + "version": "1.0.0-alpha.28", + "license": "Apache-2.0", + "dependencies": { + "nosecone": "1.0.0-alpha.28" + }, + "devDependencies": { + "@arcjet/eslint-config": "1.0.0-alpha.28", + "@arcjet/rollup-config": "1.0.0-alpha.28", + "@arcjet/tsconfig": "1.0.0-alpha.28", + "@rollup/wasm-node": "4.24.4", + "@types/node": "18.18.0", + "typescript": "5.6.3" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -660,6 +680,10 @@ "node": ">= 8" } }, + "node_modules/@nosecone/sveltekit": { + "resolved": "../../nosecone-sveltekit", + "link": true + }, "node_modules/@polka/url": { "version": "1.0.0-next.28", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", diff --git a/examples/sveltekit/package.json b/examples/sveltekit/package.json index adb97fb3b..600b0d767 100644 --- a/examples/sveltekit/package.json +++ b/examples/sveltekit/package.json @@ -13,7 +13,8 @@ "format": "prettier --write ." }, "dependencies": { - "@arcjet/sveltekit": "file:../../arcjet-sveltekit" + "@arcjet/sveltekit": "file:../../arcjet-sveltekit", + "@nosecone/sveltekit": "file:../../nosecone-sveltekit" }, "devDependencies": { "@sveltejs/adapter-auto": "^3.3.1", diff --git a/examples/sveltekit/src/hooks.server.ts b/examples/sveltekit/src/hooks.server.ts index a64616e99..27ed6d07f 100644 --- a/examples/sveltekit/src/hooks.server.ts +++ b/examples/sveltekit/src/hooks.server.ts @@ -1,27 +1,25 @@ import { aj } from "$lib/server/arcjet"; import { error } from "@sveltejs/kit"; -import type { RequestEvent } from "@sveltejs/kit"; +import { createHook } from "@nosecone/sveltekit"; +import { sequence } from "@sveltejs/kit/hooks"; -export async function handle({ - event, - resolve, -}: { - event: RequestEvent; - resolve: (event: RequestEvent) => Response | Promise; -}): Promise { - // Ignore routes that extend the Arcjet rules - they will call `.protect` themselves - const filteredRoutes = ["/api/rate-limited", "/rate-limited"]; - if (filteredRoutes.includes(event.url.pathname)) { - // return - route will handle protection - return resolve(event); - } +export const handle = sequence( + createHook(), + async ({ event, resolve }) => { + // Ignore routes that extend the Arcjet rules - they will call `.protect` themselves + const filteredRoutes = ["/api/rate-limited", "/rate-limited"]; + if (filteredRoutes.includes(event.url.pathname)) { + // return - route will handle protection + return resolve(event); + } - // Ensure every other route is protected with shield - const decision = await aj.protect(event); - if (decision.isDenied()) { - return error(403, "Forbidden"); - } + // Ensure every other route is protected with shield + const decision = await aj.protect(event); + if (decision.isDenied()) { + return error(403, "Forbidden"); + } - // Continue with the route - return resolve(event); -} \ No newline at end of file + // Continue with the route + return await resolve(event); + } +) diff --git a/examples/sveltekit/svelte.config.js b/examples/sveltekit/svelte.config.js index 973bdc965..3e19f2738 100644 --- a/examples/sveltekit/svelte.config.js +++ b/examples/sveltekit/svelte.config.js @@ -1,5 +1,6 @@ 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 = { @@ -8,6 +9,7 @@ const config = { preprocess: vitePreprocess(), kit: { + csp: csp(), // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // If your environment is not supported, or you settled on a specific environment, switch out the adapter. // See https://kit.svelte.dev/docs/adapters for more information about adapters. diff --git a/nosecone-next/.eslintignore b/nosecone-next/.eslintignore new file mode 100644 index 000000000..9cfa2cae7 --- /dev/null +++ b/nosecone-next/.eslintignore @@ -0,0 +1,6 @@ +/.turbo/ +/coverage/ +/node_modules/ +*.d.ts +*.js +!*.config.js diff --git a/nosecone-next/.eslintrc.cjs b/nosecone-next/.eslintrc.cjs new file mode 100644 index 000000000..abe4cd7b4 --- /dev/null +++ b/nosecone-next/.eslintrc.cjs @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: ["@arcjet/eslint-config"], +}; diff --git a/nosecone-next/.gitignore b/nosecone-next/.gitignore new file mode 100644 index 000000000..35b162da3 --- /dev/null +++ b/nosecone-next/.gitignore @@ -0,0 +1,135 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# Generated files +index.js +index.d.ts +test/*.js diff --git a/nosecone-next/LICENSE b/nosecone-next/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/nosecone-next/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/nosecone-next/README.md b/nosecone-next/README.md new file mode 100644 index 000000000..a6038eb02 --- /dev/null +++ b/nosecone-next/README.md @@ -0,0 +1,67 @@ + + + + Arcjet Logo + + + +# `@nosecone/next` + +

+ + + + npm badge + + +

+ +Protect your Next.js application with secure headers. + +## Installation + +```shell +npm install -S @nosecone/next +``` + +## Example + +Create a `middleware.ts` file with the contents: + +```ts +import { createMiddleware } from "@nosecone/next"; + +export const config = { + // matcher tells Next.js to run middleware on all routes + matcher: ["/(.*)"], +}; + +export default createMiddleware(); +``` + +Add `await connection()` in your `app/layout.tsx` file: + +```diff ++ import { connection } from "next/server"; + +export default async function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { ++ // Opt-out of static generation for every page so the CSP nonce can be applied ++ await connection() + + return ( + + {children} + + ); +} +``` + +## License + +Licensed under the [Apache License, Version 2.0][apache-license]. + +[apache-license]: http://www.apache.org/licenses/LICENSE-2.0 diff --git a/nosecone-next/index.ts b/nosecone-next/index.ts new file mode 100644 index 000000000..67b6d8bfe --- /dev/null +++ b/nosecone-next/index.ts @@ -0,0 +1,110 @@ +import nosecone, { defaults } from "nosecone"; +import type { CspDirectives, NoseconeOptions } from "nosecone"; + +// We export `nosecone` as the default so it can be used with `new Response()` +export default nosecone; + +function nonce() { + return `'nonce-${btoa(crypto.randomUUID())}'` as const; +} + +const defaultDirectives = defaults.contentSecurityPolicy.directives; + +function applyNextDefaults(options: NoseconeOptions): NoseconeOptions { + if ( + typeof options.contentSecurityPolicy === "undefined" || + !options.contentSecurityPolicy + ) { + return options; + } + + const directives = + options.contentSecurityPolicy === true || + typeof options.contentSecurityPolicy.directives === "undefined" + ? defaultDirectives + : options.contentSecurityPolicy.directives; + + let scriptSrc: CspDirectives["scriptSrc"]; + if (directives.scriptSrc === true) { + scriptSrc = defaultDirectives.scriptSrc; + } else { + scriptSrc = directives.scriptSrc; + } + if (scriptSrc) { + const scriptSrcSet = new Set(scriptSrc); + scriptSrcSet.delete("'self'"); + scriptSrcSet.add(nonce()); + scriptSrcSet.add("'strict-dynamic'"); + // Next.js hot reloading relies on `eval` so we enable it in development + if (process.env.NODE_ENV === "development") { + scriptSrcSet.add("'unsafe-eval'"); + } + scriptSrc = Array.from(scriptSrcSet); + } + + let styleSrc: CspDirectives["styleSrc"]; + if (directives.styleSrc === true) { + styleSrc = defaultDirectives.styleSrc; + } else { + styleSrc = directives.styleSrc; + } + if (styleSrc) { + const styleSrcSet = new Set(styleSrc); + styleSrcSet.add("'unsafe-inline'"); + styleSrc = Array.from(styleSrcSet); + } + + return { + ...options, + contentSecurityPolicy: { + directives: { + ...directives, + scriptSrc, + styleSrc, + }, + }, + }; +} + +// Setting specific headers is the way that Next.js implements middleware +// See: https://github.com/vercel/next.js/blob/5c45d58cd058a9683e435fd3a1a9b8fede8376c3/packages/next/src/server/web/spec-extension/response.ts#L148 +function nextMiddlewareHeaders( + headers: Record, +): Record { + const forwardedHeaders: Record = { + "x-middleware-next": "1", + }; + + // This applies the logic to forward headers from Next.js middleware + // https://github.com/vercel/next.js/blob/5c45d58cd058a9683e435fd3a1a9b8fede8376c3/packages/next/src/server/web/spec-extension/response.ts#L22-L27 + for (const [headerName, headerValue] of Object.entries(headers)) { + if (typeof headerValue !== "string") { + throw new Error(`impossible: missing value for ${headerName}`); + } + forwardedHeaders[`x-middleware-request-${headerName}`] = headerValue; + } + forwardedHeaders["x-middleware-override-headers"] = + Object.keys(headers).join(","); + + return forwardedHeaders; +} + +/** + * Create Next.js middleware that sets secure headers on every request. + * + * @param options: Configuration to provide to Nosecone + * @returns Next.js middleware that sets secure headers + */ +export function createMiddleware(options: NoseconeOptions = defaults) { + return async () => { + const opts = applyNextDefaults(options); + const headers = nosecone(opts); + + return new Response(null, { + headers: { + ...headers, + ...nextMiddlewareHeaders(headers), + }, + }); + }; +} diff --git a/nosecone-next/package.json b/nosecone-next/package.json new file mode 100644 index 000000000..0e78892db --- /dev/null +++ b/nosecone-next/package.json @@ -0,0 +1,61 @@ +{ + "name": "@nosecone/next", + "version": "1.0.0-alpha.28", + "description": "Protect your Next.js application with secure headers", + "license": "Apache-2.0", + "homepage": "https://arcjet.com", + "repository": { + "type": "git", + "url": "git+https://github.com/arcjet/arcjet-js.git", + "directory": "nosecone-next" + }, + "bugs": { + "url": "https://github.com/arcjet/arcjet-js/issues", + "email": "support@arcjet.com" + }, + "author": { + "name": "Arcjet", + "email": "support@arcjet.com", + "url": "https://arcjet.com" + }, + "engines": { + "node": ">=18" + }, + "type": "module", + "main": "./index.js", + "types": "./index.d.ts", + "files": [ + "LICENSE", + "README.md", + "*.js", + "*.d.ts", + "*.ts", + "!*.config.js" + ], + "scripts": { + "prepublishOnly": "npm run build", + "build": "rollup --config rollup.config.js", + "lint": "eslint .", + "pretest": "npm run build", + "test": "node --test" + }, + "dependencies": { + "nosecone": "1.0.0-alpha.28" + }, + "peerDependencies": { + "next": ">=14" + }, + "devDependencies": { + "@arcjet/eslint-config": "1.0.0-alpha.28", + "@arcjet/rollup-config": "1.0.0-alpha.28", + "@arcjet/tsconfig": "1.0.0-alpha.28", + "@rollup/wasm-node": "4.24.4", + "@types/node": "18.18.0", + "next": "15.0.1", + "typescript": "5.6.3" + }, + "publishConfig": { + "access": "public", + "tag": "latest" + } +} diff --git a/nosecone-next/rollup.config.js b/nosecone-next/rollup.config.js new file mode 100644 index 000000000..79177f236 --- /dev/null +++ b/nosecone-next/rollup.config.js @@ -0,0 +1,3 @@ +import { createConfig } from "@arcjet/rollup-config"; + +export default createConfig(import.meta.url); diff --git a/nosecone-next/tsconfig.json b/nosecone-next/tsconfig.json new file mode 100644 index 000000000..95929e097 --- /dev/null +++ b/nosecone-next/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@arcjet/tsconfig/base", + "include": ["index.ts", "test/*.ts"] +} diff --git a/nosecone-sveltekit/.eslintignore b/nosecone-sveltekit/.eslintignore new file mode 100644 index 000000000..9cfa2cae7 --- /dev/null +++ b/nosecone-sveltekit/.eslintignore @@ -0,0 +1,6 @@ +/.turbo/ +/coverage/ +/node_modules/ +*.d.ts +*.js +!*.config.js diff --git a/nosecone-sveltekit/.eslintrc.cjs b/nosecone-sveltekit/.eslintrc.cjs new file mode 100644 index 000000000..abe4cd7b4 --- /dev/null +++ b/nosecone-sveltekit/.eslintrc.cjs @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: ["@arcjet/eslint-config"], +}; diff --git a/nosecone-sveltekit/.gitignore b/nosecone-sveltekit/.gitignore new file mode 100644 index 000000000..35b162da3 --- /dev/null +++ b/nosecone-sveltekit/.gitignore @@ -0,0 +1,135 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# Generated files +index.js +index.d.ts +test/*.js diff --git a/nosecone-sveltekit/LICENSE b/nosecone-sveltekit/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/nosecone-sveltekit/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/nosecone-sveltekit/README.md b/nosecone-sveltekit/README.md new file mode 100644 index 000000000..f330e3422 --- /dev/null +++ b/nosecone-sveltekit/README.md @@ -0,0 +1,70 @@ + + + + Arcjet Logo + + + +# `@nosecone/sveltekit` + +

+ + + + npm badge + + +

+ +Protect your SvelteKit application with secure headers. + +## Installation + +```shell +npm install -S @nosecone/sveltekit +``` + +## Example + +Update your `svelte.config.js` file for `csp`: + +```diff +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 = { + // Consult https://kit.svelte.dev/docs/integrations#preprocessors + // for more information about preprocessors + preprocess: vitePreprocess(), + + kit: { ++ csp: csp(), + // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://kit.svelte.dev/docs/adapters for more information about adapters. + adapter: adapter(), + }, +}; + +export default config; +``` + +Create a `src/hooks.server.ts` file with the contents: + +```ts +import { createHook } from "@nosecone/sveltekit"; +import { sequence } from "@sveltejs/kit/hooks"; + +export const handle = sequence( + createHook(), + // ... other hooks can go here +); +``` + +## License + +Licensed under the [Apache License, Version 2.0][apache-license]. + +[apache-license]: http://www.apache.org/licenses/LICENSE-2.0 diff --git a/nosecone-sveltekit/index.ts b/nosecone-sveltekit/index.ts new file mode 100644 index 000000000..1ad332d8f --- /dev/null +++ b/nosecone-sveltekit/index.ts @@ -0,0 +1,116 @@ +import nosecone, { + CONTENT_SECURITY_POLICY_DIRECTIVES, + QUOTED, + defaults, + NoseconeValidationError, +} from "nosecone"; +import type { CspDirectives, NoseconeOptions } from "nosecone"; +import type { Handle, KitConfig } from "@sveltejs/kit"; + +// We export `nosecone` as the default so it can be used with `new Response()` +export default nosecone; + +/** + * Create a SvelteKit hook that sets secure headers on every request. + * + * @param options: Configuration to provide to Nosecone + * @returns A SvelteKit hook that sets secure headers + */ +export function createHook(options: NoseconeOptions = defaults): Handle { + return async ({ event, resolve }) => { + const response = await resolve(event); + + const headers = nosecone(options); + for (const [headerName, headerValue] of Object.entries(headers)) { + // Only add headers that aren't already set. For example, SvelteKit will + // likely have added `Content-Security-Policy` if configured with `csp` + if (!response.headers.has(headerName)) { + response.headers.set(headerName, headerValue); + } + } + + return response; + }; +} + +type SvelteKitCsp = Exclude; + +export type ContentSecurityPolicyConfig = { + mode?: SvelteKitCsp["mode"]; + directives?: CspDirectives; + // TODO: Support `reportOnly` +}; + +const directives: CspDirectives = { + ...defaults.contentSecurityPolicy.directives, + scriptSrc: ["'strict-dynamic'"], +}; + +function unquote(value?: string) { + for (const [unquoted, quoted] of QUOTED) { + if (value === quoted) { + return unquoted; + } + } + + return value; +} + +function resolveValue(v: (() => string) | string) { + if (typeof v === "function") { + return v(); + } else { + return v; + } +} + +function directivesToSvelteKitConfig( + directives: Readonly, +): SvelteKitCsp["directives"] { + const sveltekitDirectives: SvelteKitCsp["directives"] = {}; + for (const [optionKey, optionValues] of Object.entries(directives)) { + const key = CONTENT_SECURITY_POLICY_DIRECTIVES.get( + // @ts-expect-error because we're validating this option key + optionKey, + ); + if (!key) { + throw new NoseconeValidationError( + `${optionKey} is not a Content-Security-Policy directive`, + ); + } + + // Skip anything falsey + if (!optionValues) { + continue; + } + + // TODO: What do we want to do if array is empty? I think they work differently for some directives + const resolvedValues = Array.isArray(optionValues) + ? new Set(optionValues.map(resolveValue)) + : new Set(); + + // TODO: Add validations for SvelteKit CSP directives + + const values = Array.from(resolvedValues); + + if (key === "upgrade-insecure-requests") { + sveltekitDirectives[key] = true; + } else { + // @ts-ignore because we're mapping to SvelteKit options + sveltekitDirectives[key] = values.map(unquote); + } + } + + return sveltekitDirectives; +} + +export function csp( + options: ContentSecurityPolicyConfig = { mode: "auto", directives }, +): SvelteKitCsp { + return { + mode: options.mode ? options.mode : "auto", + directives: directivesToSvelteKitConfig( + options.directives ?? defaults.contentSecurityPolicy.directives, + ), + }; +} diff --git a/nosecone-sveltekit/package.json b/nosecone-sveltekit/package.json new file mode 100644 index 000000000..94957f7bf --- /dev/null +++ b/nosecone-sveltekit/package.json @@ -0,0 +1,61 @@ +{ + "name": "@nosecone/sveltekit", + "version": "1.0.0-alpha.28", + "description": "Protect your SvelteKit application with secure headers", + "license": "Apache-2.0", + "homepage": "https://arcjet.com", + "repository": { + "type": "git", + "url": "git+https://github.com/arcjet/arcjet-js.git", + "directory": "nosecone-sveltekit" + }, + "bugs": { + "url": "https://github.com/arcjet/arcjet-js/issues", + "email": "support@arcjet.com" + }, + "author": { + "name": "Arcjet", + "email": "support@arcjet.com", + "url": "https://arcjet.com" + }, + "engines": { + "node": ">=18" + }, + "type": "module", + "main": "./index.js", + "types": "./index.d.ts", + "files": [ + "LICENSE", + "README.md", + "*.js", + "*.d.ts", + "*.ts", + "!*.config.js" + ], + "scripts": { + "prepublishOnly": "npm run build", + "build": "rollup --config rollup.config.js", + "lint": "eslint .", + "pretest": "npm run build", + "test": "node --test" + }, + "dependencies": { + "nosecone": "1.0.0-alpha.28" + }, + "peerDependencies": { + "@sveltejs/kit": ">=2" + }, + "devDependencies": { + "@arcjet/eslint-config": "1.0.0-alpha.28", + "@arcjet/rollup-config": "1.0.0-alpha.28", + "@arcjet/tsconfig": "1.0.0-alpha.28", + "@rollup/wasm-node": "4.24.4", + "@sveltejs/kit": "^2.8.0", + "@types/node": "18.18.0", + "typescript": "5.6.3" + }, + "publishConfig": { + "access": "public", + "tag": "latest" + } +} diff --git a/nosecone-sveltekit/rollup.config.js b/nosecone-sveltekit/rollup.config.js new file mode 100644 index 000000000..79177f236 --- /dev/null +++ b/nosecone-sveltekit/rollup.config.js @@ -0,0 +1,3 @@ +import { createConfig } from "@arcjet/rollup-config"; + +export default createConfig(import.meta.url); diff --git a/nosecone-sveltekit/tsconfig.json b/nosecone-sveltekit/tsconfig.json new file mode 100644 index 000000000..95929e097 --- /dev/null +++ b/nosecone-sveltekit/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@arcjet/tsconfig/base", + "include": ["index.ts", "test/*.ts"] +} diff --git a/nosecone/.eslintignore b/nosecone/.eslintignore new file mode 100644 index 000000000..9cfa2cae7 --- /dev/null +++ b/nosecone/.eslintignore @@ -0,0 +1,6 @@ +/.turbo/ +/coverage/ +/node_modules/ +*.d.ts +*.js +!*.config.js diff --git a/nosecone/.eslintrc.cjs b/nosecone/.eslintrc.cjs new file mode 100644 index 000000000..abe4cd7b4 --- /dev/null +++ b/nosecone/.eslintrc.cjs @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: ["@arcjet/eslint-config"], +}; diff --git a/nosecone/.gitignore b/nosecone/.gitignore new file mode 100644 index 000000000..35b162da3 --- /dev/null +++ b/nosecone/.gitignore @@ -0,0 +1,135 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# Generated files +index.js +index.d.ts +test/*.js diff --git a/nosecone/LICENSE b/nosecone/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/nosecone/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/nosecone/README.md b/nosecone/README.md new file mode 100644 index 000000000..556579c34 --- /dev/null +++ b/nosecone/README.md @@ -0,0 +1,41 @@ + + + + Arcjet Logo + + + +# `nosecone` + +

+ + + + npm badge + + +

+ +Protect your `Response` with secure headers. + +## Installation + +```shell +npm install -S nosecone +``` + +## Example + +```ts +import nosecone from "nosecone"; + +const secureResponse = new Response(null, { + headers: nosecone(), +}); +``` + +## License + +Licensed under the [Apache License, Version 2.0][apache-license]. + +[apache-license]: http://www.apache.org/licenses/LICENSE-2.0 diff --git a/nosecone/index.ts b/nosecone/index.ts new file mode 100644 index 000000000..3ef84f339 --- /dev/null +++ b/nosecone/index.ts @@ -0,0 +1,700 @@ +// Types based on +// https://github.com/josh-hemphill/csp-typed-directives/blob/6e2cbc6d3cc18bbdc9b13d42c4556e786e28b243/src/csp.types.ts +// +// MIT License +// +// Copyright (c) 2021-present, Joshua Hemphill +// Copyright (c) 2021, Tecnico Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +export type ActionSource = "'strict-dynamic'" | "'report-sample'"; +export type BaseSource = + | "'self'" + | "'unsafe-eval'" + | "'unsafe-hashes'" + | "'unsafe-inline'" + | "'wasm-unsafe-eval'" + | "'none'"; +export type CryptoSource = + `'${"nonce" | "sha256" | "sha384" | "sha512"}-${string}'`; +export type FrameSource = HostSource | SchemeSource | "'self'" | "'none'"; +export type HostNameScheme = `${string}.${string}` | "localhost"; +export type HostSource = `${HostProtocolSchemes}${HostNameScheme}${PortScheme}`; +export type HostProtocolSchemes = `${string}://` | ""; +export type PortScheme = `:${number}` | "" | ":*"; +export type SchemeSource = + | "http:" + | "https:" + | "data:" + | "mediastream:" + | "blob:" + | "filesystem:"; +export type Source = HostSource | SchemeSource | CryptoSource | BaseSource; +export type StaticOrDynamic = boolean | null | ReadonlyArray S)>; + +export interface CspDirectives { + baseUri?: StaticOrDynamic; + childSrc?: StaticOrDynamic; + defaultSrc?: StaticOrDynamic; + frameSrc?: StaticOrDynamic; + workerSrc?: StaticOrDynamic; + connectSrc?: StaticOrDynamic; + fontSrc?: StaticOrDynamic; + imgSrc?: StaticOrDynamic; + manifestSrc?: StaticOrDynamic; + mediaSrc?: StaticOrDynamic; + objectSrc?: StaticOrDynamic; + prefetchSrc?: StaticOrDynamic; + scriptSrc?: StaticOrDynamic; + scriptSrcElem?: StaticOrDynamic; + scriptSrcAttr?: StaticOrDynamic; + styleSrc?: StaticOrDynamic; + styleSrcElem?: StaticOrDynamic; + styleSrcAttr?: StaticOrDynamic; + sandbox?: ReadonlyArray< + | "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" + >; + formAction?: StaticOrDynamic; + frameAncestors?: StaticOrDynamic; + navigateTo?: StaticOrDynamic; + reportUri?: string[]; + reportTo?: string[]; + requireTrustedTypesFor?: ReadonlyArray<"script">; + trustedTypes?: ReadonlyArray<"none" | "allow-duplicates" | "*" | string>; + upgradeInsecureRequests?: boolean; +} + +export type ReferrerPolicyToken = + | "no-referrer" + | "no-referrer-when-downgrade" + | "same-origin" + | "origin" + | "strict-origin" + | "origin-when-cross-origin" + | "strict-origin-when-cross-origin" + | "unsafe-url" + | ""; + +export interface ContentSecurityPolicyConfig { + directives?: Readonly; +} + +export interface CrossOriginEmbedderPolicyConfig { + policy?: "require-corp" | "credentialless" | "unsafe-none"; +} + +export interface CrossOriginOpenerPolicyConfig { + policy?: "same-origin" | "same-origin-allow-popups" | "unsafe-none"; +} + +export interface CrossOriginResourcePolicyConfig { + policy?: "same-origin" | "same-site" | "cross-origin"; +} + +export interface ReferrerPolicyConfig { + policy?: ReadonlyArray; +} + +export interface StrictTransportSecurityConfig { + maxAge?: number; + includeSubDomains?: boolean; + preload?: boolean; +} + +export interface DnsPrefetchControlConfig { + allow?: boolean; +} + +export interface FrameOptionsConfig { + action?: "deny" | "sameorigin"; +} + +export interface PermittedCrossDomainPoliciesConfig { + permittedPolicies?: "none" | "master-only" | "by-content-type" | "all"; +} + +export interface NoseconeOptions { + contentSecurityPolicy?: ContentSecurityPolicyConfig | boolean; + crossOriginEmbedderPolicy?: CrossOriginEmbedderPolicyConfig | boolean; + crossOriginOpenerPolicy?: CrossOriginOpenerPolicyConfig | boolean; + crossOriginResourcePolicy?: CrossOriginResourcePolicyConfig | boolean; + originAgentCluster?: boolean; + referrerPolicy?: ReferrerPolicyConfig | boolean; + strictTransportSecurity?: StrictTransportSecurityConfig | boolean; + xContentTypeOptions?: boolean; + xDnsPrefetchControl?: DnsPrefetchControlConfig | boolean; + xDownloadOptions?: boolean; + xFrameOptions?: FrameOptionsConfig | boolean; + xPermittedCrossDomainPolicies?: PermittedCrossDomainPoliciesConfig | boolean; + xXssProtection?: boolean; +} + +// Map of configuration options to the kebab-case names for +// `Content-Security-Policy` directives +export const CONTENT_SECURITY_POLICY_DIRECTIVES = new Map([ + ["baseUri", "base-uri"], + ["childSrc", "child-src"], + ["defaultSrc", "default-src"], + ["frameSrc", "frame-src"], + ["workerSrc", "worker-src"], + ["connectSrc", "connect-src"], + ["fontSrc", "font-src"], + ["imgSrc", "img-src"], + ["manifestSrc", "manifest-src"], + ["mediaSrc", "media-src"], + ["objectSrc", "object-src"], + ["prefetchSrc", "prefetch-src"], + ["scriptSrc", "script-src"], + ["scriptSrcElem", "script-src-elem"], + ["scriptSrcAttr", "script-src-attr"], + ["styleSrc", "style-src"], + ["styleSrcElem", "style-src-elem"], + ["styleSrcAttr", "style-src-attr"], + ["sandbox", "sandbox"], + ["formAction", "form-action"], + ["frameAncestors", "frame-ancestors"], + ["navigateTo", "navigate-to"], + ["reportUri", "report-uri"], + ["reportTo", "report-to"], + ["requireTrustedTypesFor", "require-trusted-types-for"], + ["trustedTypes", "trusted-types"], + ["upgradeInsecureRequests", "upgrade-insecure-requests"], +] as const); + +// Set of valid `Cross-Origin-Embedder-Policy` values +export const CROSS_ORIGIN_EMBEDDER_POLICIES = new Set([ + "require-corp", + "credentialless", + "unsafe-none", +] as const); + +// Set of valid `Cross-Origin-Opener-Policy` values +export const CROSS_ORIGIN_OPENER_POLICIES = new Set([ + "same-origin", + "same-origin-allow-popups", + "unsafe-none", +] as const); + +// Set of valid `Cross-Origin-Resource-Policy` values +export const CROSS_ORIGIN_RESOURCE_POLICIES = new Set([ + "same-origin", + "same-site", + "cross-origin", +] as const); + +// Set of valid `Resource-Policy` tokens +export const REFERRER_POLICIES = new Set([ + "no-referrer", + "no-referrer-when-downgrade", + "same-origin", + "origin", + "strict-origin", + "origin-when-cross-origin", + "strict-origin-when-cross-origin", + "unsafe-url", + "", +] as const); + +// Set of valid `X-Permitted-Cross-Domain-Policies` values +export const PERMITTED_CROSS_DOMAIN_POLICIES = new Set([ + "none", + "master-only", + "by-content-type", + "all", +] as const); + +// Set of valid values for the `sandbox` directive of `Content-Security-Policy` +export const SANDBOX_DIRECTIVES = new Set([ + "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", +] as const); + +// Mapping of values that need to be quoted in `Content-Security-Policy`; +// however, it does not include `nonce-*` or `sha*-*` because those are dynamic +export const QUOTED = new Map([ + ["self", "'self'"], + ["unsafe-eval", "'unsafe-eval'"], + ["unsafe-hashes", "'unsafe-hashes'"], + ["unsafe-inline", "'unsafe-inline'"], + ["none", "'none'"], + ["strict-dynamic", "'strict-dynamic'"], + ["report-sample", "'report-sample'"], + ["wasm-unsafe-eval", "'wasm-unsafe-eval'"], + ["script", "'script'"], +] as const); + +const 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'"], + upgradeInsecureRequests: true, +} as const; + +export const defaults = { + contentSecurityPolicy: { + directives, + }, + crossOriginEmbedderPolicy: { + policy: "require-corp", + }, + crossOriginOpenerPolicy: { + policy: "same-origin", + }, + crossOriginResourcePolicy: { + policy: "same-origin", + }, + originAgentCluster: true, + referrerPolicy: { + policy: ["no-referrer"], + }, + strictTransportSecurity: { + maxAge: 365 * 24 * 60 * 60, + includeSubDomains: true, + preload: false, + }, + xContentTypeOptions: true, + xDnsPrefetchControl: { + allow: false, + }, + xDownloadOptions: true, + xFrameOptions: { + action: "sameorigin", + }, + xPermittedCrossDomainPolicies: { + permittedPolicies: "none", + }, + xXssProtection: true, +} as const; + +function resolveValue(v: (() => S) | S): S { + if (typeof v === "function") { + return v(); + } else { + return v; + } +} + +export class NoseconeValidationError extends Error { + constructor(message: string) { + super(`validation error: ${message}`); + } +} + +// Header defaults and construction inspired by +// https://github.com/helmetjs/helmet/tree/9a8e6d5322aad6090394b0bb2e81448c5f5b3e74 +// +// The MIT License +// +// Copyright (c) 2012-2024 Evan Hahn, Adam Baldwin +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// 'Software'), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +export function createContentSecurityPolicy( + { + directives = defaults.contentSecurityPolicy.directives, + }: ContentSecurityPolicyConfig = defaults.contentSecurityPolicy, +) { + const cspEntries = []; + for (const [optionKey, optionValues] of Object.entries(directives)) { + const key = CONTENT_SECURITY_POLICY_DIRECTIVES.get( + // @ts-expect-error because we're validating this option key + optionKey, + ); + if (!key) { + throw new NoseconeValidationError( + `${optionKey} is not a Content-Security-Policy directive`, + ); + } + + // Skip anything falsey + if (!optionValues) { + continue; + } + + // TODO: What do we want to do if array is empty? I think they work differently for some directives + const resolvedValues = Array.isArray(optionValues) + ? new Set(optionValues.map(resolveValue)) + : new Set(); + + // TODO: Add more validation + for (const value of resolvedValues) { + if ( + QUOTED.has( + // @ts-expect-error because we are validation this value + value, + ) + ) { + throw new NoseconeValidationError( + `"${value}" must be quoted using single-quotes, e.g. "'${value}'"`, + ); + } + if (key === "sandbox") { + if ( + !SANDBOX_DIRECTIVES.has( + // @ts-expect-error because we are validation this value + value, + ) + ) { + throw new NoseconeValidationError( + "invalid sandbox value in Content-Security-Policy", + ); + } + } + } + + const values = Array.from(resolvedValues); + + const entry = `${key} ${values.join(" ")}`.trim(); + const entryWithSep = `${entry};`; + cspEntries.push(entryWithSep); + } + return ["content-security-policy", cspEntries.join(" ")] as const; +} + +export function createCrossOriginEmbedderPolicy( + { + policy = defaults.crossOriginEmbedderPolicy.policy, + }: CrossOriginEmbedderPolicyConfig = defaults.crossOriginEmbedderPolicy, +) { + if (CROSS_ORIGIN_EMBEDDER_POLICIES.has(policy)) { + return ["cross-origin-embedder-policy", policy] as const; + } else { + throw new NoseconeValidationError( + `invalid value for Cross-Origin-Embedder-Policy`, + ); + } +} + +export function createCrossOriginOpenerPolicy( + { + policy = defaults.crossOriginOpenerPolicy.policy, + }: CrossOriginOpenerPolicyConfig = defaults.crossOriginOpenerPolicy, +) { + if (CROSS_ORIGIN_OPENER_POLICIES.has(policy)) { + return ["cross-origin-opener-policy", policy] as const; + } else { + throw new NoseconeValidationError( + `invalid value for Cross-Origin-Opener-Policy`, + ); + } +} + +export function createCrossOriginResourcePolicy( + { + policy = defaults.crossOriginResourcePolicy.policy, + }: CrossOriginResourcePolicyConfig = defaults.crossOriginResourcePolicy, +) { + if (CROSS_ORIGIN_RESOURCE_POLICIES.has(policy)) { + return ["cross-origin-resource-policy", policy] as const; + } else { + throw new NoseconeValidationError( + `invalid value for Cross-Origin-Resource-Policy`, + ); + } +} + +export function createOriginAgentCluster() { + return ["origin-agent-cluster", "?1"] as const; +} + +export function createReferrerPolicy( + { + policy = defaults.referrerPolicy.policy, + }: ReferrerPolicyConfig = defaults.referrerPolicy, +) { + if (Array.isArray(policy)) { + if (policy.length > 0) { + const tokens = new Set(); + for (const token of policy) { + if (REFERRER_POLICIES.has(token)) { + tokens.add(token); + } else { + throw new NoseconeValidationError( + `invalid value for Referrer-Policy`, + ); + } + } + + return ["referrer-policy", Array.from(tokens).join(",")] as const; + } else { + throw new NoseconeValidationError( + "must provide at least one policy for Referrer-Policy", + ); + } + } + + throw new NoseconeValidationError("must provide array for Referrer-Policy"); +} + +export function createStrictTransportSecurity( + { + maxAge = defaults.strictTransportSecurity.maxAge, + includeSubDomains = defaults.strictTransportSecurity.includeSubDomains, + preload = defaults.strictTransportSecurity.preload, + }: StrictTransportSecurityConfig = defaults.strictTransportSecurity, +) { + if (maxAge >= 0 && Number.isFinite(maxAge)) { + maxAge = Math.floor(maxAge); + } else { + throw new NoseconeValidationError( + "must provide a finite, positive integer for the maxAge of Strict-Transport-Security", + ); + } + const directives: string[] = [`max-age=${maxAge}`]; + + if (includeSubDomains) { + directives.push("includeSubDomains"); + } + + if (preload) { + directives.push("preload"); + } + + return ["strict-transport-security", directives.join("; ")] as const; +} + +export function createContentTypeOptions() { + return ["x-content-type-options", "nosniff"] as const; +} + +export function createDnsPrefetchControl( + { + allow = defaults.xDnsPrefetchControl.allow, + }: DnsPrefetchControlConfig = defaults.xDnsPrefetchControl, +) { + const headerValue = allow ? "on" : "off"; + return ["x-dns-prefetch-control", headerValue] as const; +} + +export function createDownloadOptions() { + return ["x-download-options", "noopen"] as const; +} + +export function createFrameOptions( + { + action = defaults.xFrameOptions.action, + }: FrameOptionsConfig = defaults.xFrameOptions, +) { + if (typeof action === "string") { + const headerValue = action.toUpperCase(); + if (headerValue === "SAMEORIGIN" || headerValue === "DENY") { + return ["x-frame-options", headerValue] as const; + } + } + + throw new NoseconeValidationError("invalid value for X-Frame-Options"); +} + +export function createPermittedCrossDomainPolicies( + { + permittedPolicies = defaults.xPermittedCrossDomainPolicies + .permittedPolicies, + }: PermittedCrossDomainPoliciesConfig = defaults.xPermittedCrossDomainPolicies, +) { + if (PERMITTED_CROSS_DOMAIN_POLICIES.has(permittedPolicies)) { + return ["x-permitted-cross-domain-policies", permittedPolicies] as const; + } else { + throw new NoseconeValidationError( + `invalid value for X-Permitted-Cross-Domain-Policies`, + ); + } +} + +export function createXssProtection() { + return ["x-xss-protection", "0"] as const; +} + +export default function nosecone({ + contentSecurityPolicy = defaults.contentSecurityPolicy, + crossOriginEmbedderPolicy = defaults.crossOriginEmbedderPolicy, + crossOriginOpenerPolicy = defaults.crossOriginOpenerPolicy, + crossOriginResourcePolicy = defaults.crossOriginResourcePolicy, + originAgentCluster = defaults.originAgentCluster, + referrerPolicy = defaults.referrerPolicy, + strictTransportSecurity = defaults.strictTransportSecurity, + xContentTypeOptions = defaults.xContentTypeOptions, + xDnsPrefetchControl = defaults.xDnsPrefetchControl, + xDownloadOptions = defaults.xDownloadOptions, + xFrameOptions = defaults.xFrameOptions, + xPermittedCrossDomainPolicies = defaults.xPermittedCrossDomainPolicies, + xXssProtection = defaults.xXssProtection, +}: NoseconeOptions = defaults) { + if (contentSecurityPolicy === true) { + contentSecurityPolicy = defaults.contentSecurityPolicy; + } + if (crossOriginEmbedderPolicy === true) { + crossOriginEmbedderPolicy = defaults.crossOriginEmbedderPolicy; + } + if (crossOriginOpenerPolicy === true) { + crossOriginOpenerPolicy = defaults.crossOriginOpenerPolicy; + } + if (crossOriginResourcePolicy === true) { + crossOriginResourcePolicy = defaults.crossOriginResourcePolicy; + } + if (referrerPolicy === true) { + referrerPolicy = defaults.referrerPolicy; + } + if (strictTransportSecurity === true) { + strictTransportSecurity = defaults.strictTransportSecurity; + } + if (xDnsPrefetchControl === true) { + xDnsPrefetchControl = defaults.xDnsPrefetchControl; + } + if (xFrameOptions === true) { + xFrameOptions = defaults.xFrameOptions; + } + if (xPermittedCrossDomainPolicies === true) { + xPermittedCrossDomainPolicies = defaults.xPermittedCrossDomainPolicies; + } + + const headers: Record = {}; + + if (contentSecurityPolicy) { + const [headerName, headerValue] = createContentSecurityPolicy( + contentSecurityPolicy, + ); + headers[headerName] = headerValue; + } + + if (crossOriginEmbedderPolicy) { + const [headerName, headerValue] = createCrossOriginEmbedderPolicy( + crossOriginEmbedderPolicy, + ); + headers[headerName] = headerValue; + } + + if (crossOriginOpenerPolicy) { + const [headerName, headerValue] = createCrossOriginOpenerPolicy( + crossOriginOpenerPolicy, + ); + headers[headerName] = headerValue; + } + + if (crossOriginResourcePolicy) { + const [headerName, headerValue] = createCrossOriginResourcePolicy( + crossOriginResourcePolicy, + ); + headers[headerName] = headerValue; + } + + if (originAgentCluster) { + const [headerName, headerValue] = createOriginAgentCluster(); + headers[headerName] = headerValue; + } + + if (referrerPolicy) { + const [headerName, headerValue] = createReferrerPolicy(referrerPolicy); + headers[headerName] = headerValue; + } + + if (strictTransportSecurity) { + const [headerName, headerValue] = createStrictTransportSecurity( + strictTransportSecurity, + ); + headers[headerName] = headerValue; + } + + if (xContentTypeOptions) { + const [headerName, headerValue] = createContentTypeOptions(); + headers[headerName] = headerValue; + } + + if (xDnsPrefetchControl) { + const [headerName, headerValue] = + createDnsPrefetchControl(xDnsPrefetchControl); + headers[headerName] = headerValue; + } + + if (xDownloadOptions) { + const [headerName, headerValue] = createDownloadOptions(); + headers[headerName] = headerValue; + } + + if (xFrameOptions) { + const [headerName, headerValue] = createFrameOptions(xFrameOptions); + headers[headerName] = headerValue; + } + + if (xPermittedCrossDomainPolicies) { + const [headerName, headerValue] = createPermittedCrossDomainPolicies( + xPermittedCrossDomainPolicies, + ); + headers[headerName] = headerValue; + } + + if (xXssProtection) { + const [headerName, headerValue] = createXssProtection(); + headers[headerName] = headerValue; + } + + return headers; +} diff --git a/nosecone/package.json b/nosecone/package.json new file mode 100644 index 000000000..853cbe32d --- /dev/null +++ b/nosecone/package.json @@ -0,0 +1,55 @@ +{ + "name": "nosecone", + "version": "1.0.0-alpha.28", + "description": "Protect your Response with secure headers", + "license": "Apache-2.0", + "homepage": "https://arcjet.com", + "repository": { + "type": "git", + "url": "git+https://github.com/arcjet/arcjet-js.git", + "directory": "nosecone" + }, + "bugs": { + "url": "https://github.com/arcjet/arcjet-js/issues", + "email": "support@arcjet.com" + }, + "author": { + "name": "Arcjet", + "email": "support@arcjet.com", + "url": "https://arcjet.com" + }, + "engines": { + "node": ">=18" + }, + "type": "module", + "main": "./index.js", + "types": "./index.d.ts", + "files": [ + "LICENSE", + "README.md", + "*.js", + "*.d.ts", + "*.ts", + "!*.config.js" + ], + "scripts": { + "prepublishOnly": "npm run build", + "build": "rollup --config rollup.config.js", + "lint": "eslint .", + "pretest": "npm run build", + "test": "node --test --experimental-test-coverage" + }, + "dependencies": {}, + "devDependencies": { + "@arcjet/eslint-config": "1.0.0-alpha.28", + "@arcjet/rollup-config": "1.0.0-alpha.28", + "@arcjet/tsconfig": "1.0.0-alpha.28", + "@rollup/wasm-node": "4.24.4", + "@types/node": "18.18.0", + "typescript": "5.6.3" + }, + "publishConfig": { + "access": "public", + "tag": "latest" + } +} diff --git a/nosecone/rollup.config.js b/nosecone/rollup.config.js new file mode 100644 index 000000000..79177f236 --- /dev/null +++ b/nosecone/rollup.config.js @@ -0,0 +1,3 @@ +import { createConfig } from "@arcjet/rollup-config"; + +export default createConfig(import.meta.url); diff --git a/nosecone/test/nosecone.test.ts b/nosecone/test/nosecone.test.ts new file mode 100644 index 000000000..65587b5df --- /dev/null +++ b/nosecone/test/nosecone.test.ts @@ -0,0 +1,641 @@ +import assert from "node:assert"; +import { describe, it } from "node:test"; + +import nosecone, { + createContentSecurityPolicy, + createContentTypeOptions, + createCrossOriginEmbedderPolicy, + createCrossOriginOpenerPolicy, + createCrossOriginResourcePolicy, + createDnsPrefetchControl, + createDownloadOptions, + createFrameOptions, + createOriginAgentCluster, + createPermittedCrossDomainPolicies, + createReferrerPolicy, + createStrictTransportSecurity, + createXssProtection, + NoseconeValidationError, +} from "../index"; + +describe("nosecone", () => { + describe("NoseconeValidationError", () => { + it("prefixes the error", () => { + const err = new NoseconeValidationError("test"); + assert(err.message.startsWith("validation error:")); + }); + }); + + describe("createContentSecurityPolicy", () => { + it("uses default configuration if no options provided", () => { + const policy = createContentSecurityPolicy(); + assert.deepStrictEqual(policy, [ + "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;", + ]); + }); + + it("uses default directive if none provided", () => { + const policy = createContentSecurityPolicy({}); + assert.deepStrictEqual(policy, [ + "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;", + ]); + }); + + it("builds the header with options", () => { + const policy = createContentSecurityPolicy({ + directives: { + defaultSrc: ["'none'"], + scriptSrc: ["'none'"], + styleSrc: ["'none'"], + }, + }); + assert.deepStrictEqual(policy, [ + "content-security-policy", + "default-src 'none'; script-src 'none'; style-src 'none';", + ]); + }); + + it("resolve functions in directives", () => { + const policy = createContentSecurityPolicy({ + directives: { + defaultSrc: ["'self'"], + scriptSrc: [() => "'nonce-123456'", "'strict-dynamic'"], + styleSrc: ["'self'"], + }, + }); + assert.deepStrictEqual(policy, [ + "content-security-policy", + "default-src 'self'; script-src 'nonce-123456' 'strict-dynamic'; style-src 'self';", + ]); + }); + + it("throws if provided an unsupported directive", () => { + assert.throws( + () => { + createContentSecurityPolicy({ + directives: { + // @ts-expect-error + invalid: "directive", + }, + }); + }, + { + message: + "validation error: invalid is not a Content-Security-Policy directive", + }, + ); + }); + + it("throws if provided an unquoted value", () => { + assert.throws( + () => { + createContentSecurityPolicy({ + directives: { + // @ts-expect-error + defaultSrc: ["self"], + }, + }); + }, + { + message: `validation error: "self" must be quoted using single-quotes, e.g. "'self'"`, + }, + ); + }); + + it("throws if provided an invalid sandbox value", () => { + assert.throws( + () => { + createContentSecurityPolicy({ + directives: { + // @ts-expect-error + sandbox: ["invalid"], + }, + }); + }, + { + message: + "validation error: invalid sandbox value in Content-Security-Policy", + }, + ); + }); + + it("skips falsey values", () => { + const policy = createContentSecurityPolicy({ + directives: { + defaultSrc: false, + scriptSrc: null, + styleSrc: undefined, + }, + }); + assert.deepStrictEqual(policy, ["content-security-policy", ""]); + }); + }); + + describe("createCrossOriginEmbedderPolicy", () => { + it("uses default configuration if no options provided", () => { + const policy = createCrossOriginEmbedderPolicy(); + assert.deepStrictEqual(policy, [ + "cross-origin-embedder-policy", + "require-corp", + ]); + }); + + it("uses default policy if none provided", () => { + const policy = createCrossOriginEmbedderPolicy({}); + assert.deepStrictEqual(policy, [ + "cross-origin-embedder-policy", + "require-corp", + ]); + }); + + it("builds the header with options", () => { + const policy = createCrossOriginEmbedderPolicy({ + policy: "credentialless", + }); + assert.deepStrictEqual(policy, [ + "cross-origin-embedder-policy", + "credentialless", + ]); + }); + + it("throws if provided an invalid policy", () => { + assert.throws( + () => { + createCrossOriginEmbedderPolicy({ + // @ts-expect-error + policy: "invalid", + }); + }, + { + message: + "validation error: invalid value for Cross-Origin-Embedder-Policy", + }, + ); + }); + }); + + describe("createCrossOriginOpenerPolicy", () => { + it("uses default configuration if no options provided", () => { + const policy = createCrossOriginOpenerPolicy(); + assert.deepStrictEqual(policy, [ + "cross-origin-opener-policy", + "same-origin", + ]); + }); + + it("uses default policy if none provided", () => { + const policy = createCrossOriginOpenerPolicy({}); + assert.deepStrictEqual(policy, [ + "cross-origin-opener-policy", + "same-origin", + ]); + }); + + it("builds the header with options", () => { + const policy = createCrossOriginOpenerPolicy({ + policy: "same-origin-allow-popups", + }); + assert.deepStrictEqual(policy, [ + "cross-origin-opener-policy", + "same-origin-allow-popups", + ]); + }); + + it("throws if provided an invalid policy", () => { + assert.throws( + () => { + createCrossOriginOpenerPolicy({ + // @ts-expect-error + policy: "invalid", + }); + }, + { + message: + "validation error: invalid value for Cross-Origin-Opener-Policy", + }, + ); + }); + }); + + describe("createCrossOriginResourcePolicy", () => { + it("uses default configuration if no options provided", () => { + const policy = createCrossOriginResourcePolicy(); + assert.deepStrictEqual(policy, [ + "cross-origin-resource-policy", + "same-origin", + ]); + }); + + it("uses default policy if none provided", () => { + const policy = createCrossOriginResourcePolicy({}); + assert.deepStrictEqual(policy, [ + "cross-origin-resource-policy", + "same-origin", + ]); + }); + + it("builds the header with options", () => { + const policy = createCrossOriginResourcePolicy({ + policy: "cross-origin", + }); + assert.deepStrictEqual(policy, [ + "cross-origin-resource-policy", + "cross-origin", + ]); + }); + + it("throws if provided an invalid policy", () => { + assert.throws( + () => { + createCrossOriginResourcePolicy({ + // @ts-expect-error + policy: "invalid", + }); + }, + { + message: + "validation error: invalid value for Cross-Origin-Resource-Policy", + }, + ); + }); + }); + + describe("createOriginAgentCluster", () => { + it("builds the header", () => { + const policy = createOriginAgentCluster(); + assert.deepStrictEqual(policy, ["origin-agent-cluster", "?1"]); + }); + }); + + describe("createReferrerPolicy", () => { + it("uses default configuration if no options provided", () => { + const policy = createReferrerPolicy(); + assert.deepStrictEqual(policy, ["referrer-policy", "no-referrer"]); + }); + + it("uses default policy if none provided", () => { + const policy = createReferrerPolicy({}); + assert.deepStrictEqual(policy, ["referrer-policy", "no-referrer"]); + }); + + it("builds the header with options", () => { + const policy = createReferrerPolicy({ + policy: ["origin-when-cross-origin", "no-referrer-when-downgrade"], + }); + assert.deepStrictEqual(policy, [ + "referrer-policy", + "origin-when-cross-origin,no-referrer-when-downgrade", + ]); + }); + + it("throws if provided policy is not array", () => { + assert.throws( + () => { + createReferrerPolicy({ + // @ts-expect-error + policy: "invalid", + }); + }, + { + message: "validation error: must provide array for Referrer-Policy", + }, + ); + }); + + it("throws if provided policy is empty", () => { + assert.throws( + () => { + createReferrerPolicy({ + policy: [], + }); + }, + { + message: + "validation error: must provide at least one policy for Referrer-Policy", + }, + ); + }); + + it("throws if provided invalid policy", () => { + assert.throws( + () => { + createReferrerPolicy({ + policy: [ + // @ts-expect-error + "invalid", + ], + }); + }, + { + message: "validation error: invalid value for Referrer-Policy", + }, + ); + }); + }); + + describe("createStrictTransportSecurity", () => { + it("uses default configuration if no options provided", () => { + const policy = createStrictTransportSecurity(); + assert.deepStrictEqual(policy, [ + "strict-transport-security", + "max-age=31536000; includeSubDomains", + ]); + }); + + it("uses default policy if none provided", () => { + const policy = createStrictTransportSecurity({}); + assert.deepStrictEqual(policy, [ + "strict-transport-security", + "max-age=31536000; includeSubDomains", + ]); + }); + + it("builds the header with options", () => { + const policy = createStrictTransportSecurity({ + maxAge: 10, + includeSubDomains: false, + preload: true, + }); + assert.deepStrictEqual(policy, [ + "strict-transport-security", + "max-age=10; preload", + ]); + }); + + it("can disable includeSubDomains and preload", () => { + const policy = createStrictTransportSecurity({ + includeSubDomains: false, + preload: false, + }); + assert.deepStrictEqual(policy, [ + "strict-transport-security", + "max-age=31536000", + ]); + }); + + it("rounds maxAge down if decimal", () => { + const policy = createStrictTransportSecurity({ + maxAge: 100.22, + }); + assert.deepStrictEqual(policy, [ + "strict-transport-security", + "max-age=100; includeSubDomains", + ]); + }); + + it("throws if maxAge is not finite", () => { + assert.throws( + () => { + createStrictTransportSecurity({ + maxAge: Infinity, + }); + }, + { + message: + "validation error: must provide a finite, positive integer for the maxAge of Strict-Transport-Security", + }, + ); + }); + + it("throws if maxAge is negative", () => { + assert.throws( + () => { + createStrictTransportSecurity({ + maxAge: -1, + }); + }, + { + message: + "validation error: must provide a finite, positive integer for the maxAge of Strict-Transport-Security", + }, + ); + }); + }); + + describe("createContentTypeOptions", () => { + it("builds the header", () => { + const policy = createContentTypeOptions(); + assert.deepStrictEqual(policy, ["x-content-type-options", "nosniff"]); + }); + }); + + describe("createDnsPrefetchControl", () => { + it("uses default configuration if no options provided", () => { + const policy = createDnsPrefetchControl(); + assert.deepStrictEqual(policy, ["x-dns-prefetch-control", "off"]); + }); + + it("uses default policy if none provided", () => { + const policy = createDnsPrefetchControl({}); + assert.deepStrictEqual(policy, ["x-dns-prefetch-control", "off"]); + }); + + it("builds the header with options", () => { + const policy = createDnsPrefetchControl({ + allow: true, + }); + assert.deepStrictEqual(policy, ["x-dns-prefetch-control", "on"]); + }); + }); + + describe("createDownloadOptions", () => { + it("builds the header", () => { + const policy = createDownloadOptions(); + assert.deepStrictEqual(policy, ["x-download-options", "noopen"]); + }); + }); + + describe("createFrameOptions", () => { + it("uses default configuration if no options provided", () => { + const policy = createFrameOptions(); + assert.deepStrictEqual(policy, ["x-frame-options", "SAMEORIGIN"]); + }); + + it("uses default policy if none provided", () => { + const policy = createFrameOptions({}); + assert.deepStrictEqual(policy, ["x-frame-options", "SAMEORIGIN"]); + }); + + it("builds the header with options", () => { + const policy = createFrameOptions({ + action: "deny", + }); + assert.deepStrictEqual(policy, ["x-frame-options", "DENY"]); + }); + + it("throws if action is not string", () => { + assert.throws( + () => { + createFrameOptions({ + // @ts-expect-error + action: 12345, + }); + }, + { + message: "validation error: invalid value for X-Frame-Options", + }, + ); + }); + + it("throws if action is not valid", () => { + assert.throws( + () => { + createFrameOptions({ + // @ts-expect-error + action: "invalid", + }); + }, + { + message: "validation error: invalid value for X-Frame-Options", + }, + ); + }); + }); + + describe("createPermittedCrossDomainPolicies", () => { + it("uses default configuration if no options provided", () => { + const policy = createPermittedCrossDomainPolicies(); + assert.deepStrictEqual(policy, [ + "x-permitted-cross-domain-policies", + "none", + ]); + }); + + it("uses default policy if none provided", () => { + const policy = createPermittedCrossDomainPolicies({}); + assert.deepStrictEqual(policy, [ + "x-permitted-cross-domain-policies", + "none", + ]); + }); + + it("builds the header with options", () => { + const policy = createPermittedCrossDomainPolicies({ + permittedPolicies: "master-only", + }); + assert.deepStrictEqual(policy, [ + "x-permitted-cross-domain-policies", + "master-only", + ]); + }); + + it("throws if provided an invalid permittedPolicies", () => { + assert.throws( + () => { + createPermittedCrossDomainPolicies({ + // @ts-expect-error + permittedPolicies: "invalid", + }); + }, + { + message: + "validation error: invalid value for X-Permitted-Cross-Domain-Policies", + }, + ); + }); + }); + + describe("createXssProtection", () => { + it("builds the header", () => { + const policy = createXssProtection(); + assert.deepStrictEqual(policy, ["x-xss-protection", "0"]); + }); + }); + + describe("nosecone", () => { + it("uses default configuration if no options provided", () => { + const headers = nosecone(); + assert.deepStrictEqual(headers, { + "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", + "origin-agent-cluster": "?1", + "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", + "x-xss-protection": "0", + }); + }); + + it("uses default configuration if field not provided", () => { + const headers = nosecone({}); + assert.deepStrictEqual(headers, { + "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", + "origin-agent-cluster": "?1", + "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", + "x-xss-protection": "0", + }); + }); + + it("disables header with explicit false", () => { + const headers = nosecone({ + contentSecurityPolicy: false, + crossOriginEmbedderPolicy: false, + crossOriginOpenerPolicy: false, + crossOriginResourcePolicy: false, + originAgentCluster: false, + referrerPolicy: false, + strictTransportSecurity: false, + xContentTypeOptions: false, + xDnsPrefetchControl: false, + xDownloadOptions: false, + xFrameOptions: false, + xPermittedCrossDomainPolicies: false, + xXssProtection: false, + }); + assert.deepStrictEqual(headers, {}); + }); + + it("enabled default header with explicit true", () => { + const headers = nosecone({ + contentSecurityPolicy: true, + crossOriginEmbedderPolicy: true, + crossOriginOpenerPolicy: true, + crossOriginResourcePolicy: true, + originAgentCluster: true, + referrerPolicy: true, + strictTransportSecurity: true, + xContentTypeOptions: true, + xDnsPrefetchControl: true, + xDownloadOptions: true, + xFrameOptions: true, + xPermittedCrossDomainPolicies: true, + xXssProtection: true, + }); + assert.deepStrictEqual(headers, { + "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", + "origin-agent-cluster": "?1", + "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", + "x-xss-protection": "0", + }); + }); + }); +}); diff --git a/nosecone/tsconfig.json b/nosecone/tsconfig.json new file mode 100644 index 000000000..95929e097 --- /dev/null +++ b/nosecone/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@arcjet/tsconfig/base", + "include": ["index.ts", "test/*.ts"] +} diff --git a/package-lock.json b/package-lock.json index 69fe0b7b0..ac016222e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -425,13 +425,14 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -1461,6 +1462,420 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2454,14 +2869,15 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -2477,10 +2893,11 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -2496,15 +2913,17 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -2725,6 +3144,21 @@ "node": ">= 8" } }, + "node_modules/@nosecone/next": { + "resolved": "nosecone-next", + "link": true + }, + "node_modules/@nosecone/sveltekit": { + "resolved": "nosecone-sveltekit", + "link": true + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/plugin-replace": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.1.tgz", @@ -2837,6 +3271,102 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@sveltejs/kit": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.8.1.tgz", + "integrity": "sha512-uuOfFwZ4xvnfPsiTB6a4H1ljjTUksGhWnYq5X/Y9z4x5+3uM2Md8q/YVeHL+7w+mygAwoEFdgKZ8YkUuk+VKww==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^0.6.0", + "devalue": "^5.1.0", + "esm-env": "^1.0.0", + "import-meta-resolve": "^4.1.0", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "sade": "^1.8.1", + "set-cookie-parser": "^2.6.0", + "sirv": "^3.0.0", + "tiny-glob": "^0.2.9" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.3" + } + }, + "node_modules/@sveltejs/kit/node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.1.tgz", + "integrity": "sha512-prXoAE/GleD2C4pKgHa9vkdjpzdYwCSw/kmjw6adIyu0vk5YKCfqIztkLg10m+kOYnzZu3bb0NaPTxlWre2a9Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^3.0.0-next.0||^3.0.0", + "debug": "^4.3.7", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.12", + "vitefu": "^1.0.3" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0-next.96 || ^5.0.0", + "vite": "^5.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-3.0.1.tgz", + "integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.3.7" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^4.0.0-next.0||^4.0.0", + "svelte": "^5.0.0-next.96 || ^5.0.0", + "vite": "^5.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte/node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -2895,6 +3425,13 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/deno": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/deno/-/deno-2.0.0.tgz", @@ -3182,9 +3719,10 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -3200,6 +3738,17 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-typescript": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz", + "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "acorn": ">=8.9.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3286,6 +3835,17 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -3294,6 +3854,17 @@ "node": ">=8" } }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -3767,6 +4338,16 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -3806,9 +4387,10 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3819,11 +4401,12 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -3882,6 +4465,13 @@ "node": ">=8" } }, + "node_modules/devalue": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", + "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", + "dev": true, + "license": "MIT" + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -3956,6 +4546,46 @@ "dev": true, "license": "MIT" }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -4092,6 +4722,13 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esm-env": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.1.4.tgz", + "integrity": "sha512-oO82nKPHKkzIj/hbtuDYy/JHqBHFlMIW36SDiPCVsj87ntDLcWN+sJ1erdVryd4NxODacFTsdrIE3b7IamqbOg==", + "dev": true, + "license": "MIT" + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -4132,6 +4769,18 @@ "node": ">=0.10" } }, + "node_modules/esrap": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz", + "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1" + } + }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -4351,7 +5000,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -4464,6 +5112,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", + "dev": true, + "license": "MIT" + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -4483,6 +5138,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4570,6 +5232,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -4674,6 +5347,17 @@ "node": ">=8" } }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.6" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -5845,6 +6529,14 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -5927,14 +6619,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.13", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.13.tgz", + "integrity": "sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g==", + "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-dir": { @@ -6023,10 +6713,31 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.7", @@ -6155,6 +6866,10 @@ "node": ">=0.10.0" } }, + "node_modules/nosecone": { + "resolved": "nosecone", + "link": true + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -6395,10 +7110,11 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -6484,6 +7200,36 @@ "node": ">=8" } }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6716,6 +7462,28 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "name": "@rollup/wasm-node", + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/@rollup/wasm-node/-/wasm-node-4.27.3.tgz", + "integrity": "sha512-HlaetiNZq+cdDeebt6KagcsKeAWDTs+LZVBYBLIq+m6POIUXPMexJ+KwCU/cgqdtDhzUj7e8a144Gzo1YB58Ow==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -6749,6 +7517,19 @@ "tslib": "^2.1.0" } }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -6771,6 +7552,13 @@ "node": ">=10" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "dev": true, + "license": "MIT" + }, "node_modules/sharp": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", @@ -6856,6 +7644,21 @@ "license": "MIT", "optional": true }, + "node_modules/sirv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz", + "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -6880,10 +7683,11 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -7065,6 +7869,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svelte": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.2.3.tgz", + "integrity": "sha512-DRrWXdzo6+gfX9H/hQofQYyAtsGqC99+CFBvttImGt6gAy4Xzh0hHBrCHw5OtBgaPOdVGNW+S+mDcYcEsvTPOw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@types/estree": "^1.0.5", + "acorn": "^8.12.1", + "acorn-typescript": "^1.4.13", + "aria-query": "^5.3.1", + "axobject-query": "^4.1.0", + "esm-env": "^1.0.0", + "esrap": "^1.2.2", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/terser": { "version": "5.29.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz", @@ -7118,6 +7948,17 @@ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -7144,6 +7985,16 @@ "node": ">=8.0" } }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -7412,6 +8263,87 @@ "node": ">=10.12.0" } }, + "node_modules/vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.3.tgz", + "integrity": "sha512-iKKfOMBHob2WxEJbqbJjHAkmYgvFDPhuqrO82om83S8RLk+17FtyMBfcyeH8GqD0ihShtkMW/zzJgiA51hCNCQ==", + "dev": true, + "license": "MIT", + "peer": true, + "workspaces": [ + "tests/deps/*", + "tests/projects/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -7523,6 +8455,75 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zimmerframe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "nosecone": { + "version": "1.0.0-alpha.28", + "license": "Apache-2.0", + "devDependencies": { + "@arcjet/eslint-config": "1.0.0-alpha.28", + "@arcjet/rollup-config": "1.0.0-alpha.28", + "@arcjet/tsconfig": "1.0.0-alpha.28", + "@rollup/wasm-node": "4.24.4", + "@types/node": "18.18.0", + "typescript": "5.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "nosecone-next": { + "name": "@nosecone/next", + "version": "1.0.0-alpha.28", + "license": "Apache-2.0", + "dependencies": { + "nosecone": "1.0.0-alpha.28" + }, + "devDependencies": { + "@arcjet/eslint-config": "1.0.0-alpha.28", + "@arcjet/rollup-config": "1.0.0-alpha.28", + "@arcjet/tsconfig": "1.0.0-alpha.28", + "@rollup/wasm-node": "4.24.4", + "@types/node": "18.18.0", + "next": "15.0.1", + "typescript": "5.6.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "next": ">=14" + } + }, + "nosecone-sveltekit": { + "name": "@nosecone/sveltekit", + "version": "1.0.0-alpha.28", + "license": "Apache-2.0", + "dependencies": { + "nosecone": "1.0.0-alpha.28" + }, + "devDependencies": { + "@arcjet/eslint-config": "1.0.0-alpha.28", + "@arcjet/rollup-config": "1.0.0-alpha.28", + "@arcjet/tsconfig": "1.0.0-alpha.28", + "@rollup/wasm-node": "4.24.4", + "@sveltejs/kit": "^2.8.0", + "@types/node": "18.18.0", + "typescript": "5.6.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@sveltejs/kit": ">=2" + } + }, "protocol": { "name": "@arcjet/protocol", "version": "1.0.0-alpha.28", diff --git a/package.json b/package.json index 8d4085225..8430c27cc 100644 --- a/package.json +++ b/package.json @@ -16,5 +16,9 @@ }, "devDependencies": { "turbo": "^2.3.0" + }, + "overrides": { + "cookie": "0.7.2", + "rollup": "npm:@rollup/wasm-node" } }