-
Notifications
You must be signed in to change notification settings - Fork 359
Improve security docs #1103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve security docs #1103
Changes from 25 commits
bba6f2f
cd23779
bd473d1
b329396
786e7fd
516bc8b
9a740d5
2c03b54
216b219
4b00e2b
8e3c7d6
6170e10
6849828
21b1e15
aa2d138
7d1bdca
9e9dcbf
25b8d5a
b55ce18
9ff7ede
fd97efa
fb7263d
930df93
21bde21
dc164f9
0660e97
e18766c
0c9472e
5cbc5aa
b2b586c
404740b
8d5f66f
5195d4e
1ff3de3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,35 +2,203 @@ | |
| title: Security | ||
| --- | ||
|
|
||
| As a non-opinionated framework, SolidStart doesn't enforce any security practices, though it enables developers to implement them as needed. | ||
| It is important to know what are the requirements for your own app and implement the fitting security measures. | ||
| If at any point you are unsure about the security of your app, or how to achieve something within the constraints of SolidStart reach us on [Discord](https://discord.gg/solidjs). | ||
| ## XSS (Cross Site Scripting) | ||
|
|
||
| Below you will find a few notes on how to establish some measures. | ||
| Solid automatically escape values passed to JSX expressions to reduce the risk of XSS attacks. | ||
| However, this protection does not apply when using [`innerHTML`](https://docs.solidjs.com/reference/jsx-attributes/innerhtml-or-textcontent#innerhtml-or-textcontent). | ||
|
|
||
| ## Security Headers | ||
| To protect your application from XSS attacks: | ||
|
|
||
| Through the use of a [middleware](/solid-start/reference/server/create-middleware#example) it is possible to tab into the `onRequest` event handlers and make sure every request going through your servers have the proper security headers set. | ||
| With this, it is possible to setup headers like `Content-Security-Policy`, `X-Frame-Options`, `X-XSS-Protection`, `X-Content-Type-Options`, among others. | ||
| - Avoid using [`innerHTML`](/reference/jsx-attributes/innerhtml-or-textcontent#innerhtml-or-textcontent) when possible. | ||
|
amirhhashemi marked this conversation as resolved.
Outdated
|
||
| If necessary, make sure to sanitize user-supplied data with libraries such as [DOMPurify](https://github.com/cure53/DOMPurify). | ||
| - Validate and sanitize user inputs, especially form inputs on the server and client. | ||
| - Set a [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP). | ||
| - Sanitize attributes containing user-supplied data within `<noscript>` elements. | ||
| This includes both the attributes of the `<noscript>` element itself and its children. | ||
|
|
||
| ### Nonces | ||
| It is highly recommended to read the [Cross Site Scripting Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) for further guidance. | ||
|
|
||
| When using `Content-Security-Policy` it is possible to use nonces to allow inline scripts and styles to be executed. | ||
| SolidStart enables that smoothly in the [`entry-server.tsx`](/solid-start/reference/entrypoints/entry-server). | ||
| ## Content Security Policy (CSP) | ||
|
|
||
| By passing generating the `nonce` within a middleware and storing it in the `request.locals` object, it is possible to use it in the `entry-server.tsx` to generate the `Content-Security-Policy` header. | ||
| To configure the `Content-Security-Policy` HTTP header, you can use a [middleware](/solid-start/advanced/middleware). | ||
|
amirhhashemi marked this conversation as resolved.
Outdated
|
||
|
|
||
| ## Cross Request Forgery (CSRF) | ||
| ### With nonce (recommended) | ||
|
|
||
| There are multiple ways to add CSRF Protection to your SolidStart app. | ||
| The quickest and most common way is to check the `request.referrer` header when the HTTP method is `POST`, `PUT`, `PATCH` or `DELETE`. | ||
| This can also be achieved through an `onRequest` [middleware](/solid-start/reference/server/create-middleware#example). | ||
| If you want to use a strict CSP with nonces: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a bit of a description to nonces plus a link to the MDN where it talks more about it
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a link to MDN docs about strict CSP and added a step about generating the nonce. |
||
|
|
||
| ## Cross Site Scripting (XSS) | ||
| 1. Create a middleware that configures the CSP header, then register it to run on the [`onRequest`](/solid-start/advanced/middleware#onrequest) event. | ||
|
LadyBluenotes marked this conversation as resolved.
Outdated
|
||
| 2. Store the nonce in the [`locals`](/solid-start/advanced/middleware#locals) object. | ||
| 3. Configure SolidStart to use the nonce in your [`entry-server.tsx`](/solid-start/reference/entrypoints/entry-server) file. | ||
|
|
||
| SolidStart automatically escape inserts and attributes in HTML. | ||
| The exception is when HTML is inserted via the `innerHTML` property, which bypasses the escaping. | ||
| Additionally, it's important to note that `<noscript>` are also outside of the purview of SolidStart, since those tags and its contents are evaluated even without JavaScript. | ||
| It is important to sanitize any strings in attributes, especially when inside `<noscript>` tags. | ||
| <TabsCodeBlocks> | ||
| <div id="Middleware"> | ||
|
|
||
| As a rule-of-thumb it is recommended to avoid injecting HTML into your page as much as possible, make sure the contents of `<noscript>` are properly sanitized, and add a strict Content Security Policy to your application. | ||
| ```tsx | ||
| import { createMiddleware } from "@solidjs/start/middleware"; | ||
| import { randomBytes } from "crypto"; | ||
|
|
||
| export default createMiddleware({ | ||
| onRequest: (event) => { | ||
| const nonce = randomBytes(16).toString("base64"); | ||
|
|
||
| event.locals.nonce = nonce; | ||
|
|
||
| const csp = ` | ||
| default-src 'self'; | ||
| script-src 'nonce-${nonce}' 'strict-dynamic' 'unsafe-eval'; | ||
| object-src 'none'; | ||
| base-uri 'none'; | ||
| frame-ancestors 'none'; | ||
| form-action 'self'; | ||
| `.replace(/\s+/g, " "); | ||
|
|
||
| event.response.headers.set("Content-Security-Policy", csp); | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| </div> | ||
| <div id="entry-server.tsx"> | ||
|
|
||
| ```tsx {6} title="src/entry-server.tsx" | ||
| // @refresh reload | ||
| import { createHandler, StartServer } from "@solidjs/start/server"; | ||
|
|
||
| export default createHandler( | ||
| () => <StartServer /* ... */ />, | ||
| (event) => ({ nonce: event.locals.nonce }) | ||
| ); | ||
| ``` | ||
|
|
||
| </div> | ||
| </TabsCodeBlocks> | ||
|
|
||
| ### Without nonce | ||
|
|
||
| To configure CSP without a nonce, create a middleware that configures the CSP header, then register it to run on the [`onBeforeResponse`](/solid-start/advanced/middleware#onbeforeresponse) event: | ||
|
amirhhashemi marked this conversation as resolved.
Outdated
|
||
|
|
||
| ```tsx | ||
| import { createMiddleware } from "@solidjs/start/middleware"; | ||
|
|
||
| export default createMiddleware({ | ||
| onBeforeResponse: (event) => { | ||
| const csp = ` | ||
| default-src 'self'; | ||
| font-src 'self' ; | ||
| object-src 'none'; | ||
| base-uri 'none'; | ||
| frame-ancestors 'none'; | ||
| form-action 'self'; | ||
| `.replace(/\s+/g, " "); | ||
|
|
||
| event.response.headers.set("Content-Security-Policy", csp); | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| ## CORS (Cross-Origin Resource Sharing) | ||
|
|
||
| When you want to allow other applications to access your API endpoints, you can use a middleware to configure the CORS headers: | ||
|
amirhhashemi marked this conversation as resolved.
Outdated
|
||
|
|
||
| ```tsx | ||
| import { createMiddleware } from "@solidjs/start/middleware"; | ||
| import { json } from "@solidjs/router"; | ||
|
|
||
| const TRUSTED_ORIGINS = ["https://my-app.com", "https://another-app.com"]; | ||
|
|
||
| export default createMiddleware({ | ||
| onBeforeResponse: (event) => { | ||
| const { request, response } = event; | ||
|
|
||
| response.headers.append("Vary", "Origin, Access-Control-Request-Method"); | ||
|
|
||
| const origin = request.headers.get("Origin"); | ||
| const requestUrl = new URL(request.url); | ||
| const isApiRequest = requestUrl && requestUrl.pathname.startsWith("/api"); | ||
|
|
||
| if (isApiRequest && origin && TRUSTED_ORIGINS.includes(origin)) { | ||
| // Handle preflight requests. | ||
| if ( | ||
| request.method === "OPTIONS" && | ||
| request.headers.get("Access-Control-Request-Method") | ||
| ) { | ||
| // Preflight requests are standalone, so we immediately send a response. | ||
| return json(null, { | ||
| headers: { | ||
| "Access-Control-Allow-Origin": origin, | ||
| "Access-Control-Allow-Methods": "OPTIONS, POST, PUT, PATCH, DELETE", | ||
| "Access-Control-Allow-Headers": "Authorization, Content-Type", | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| // Handle normal requests. | ||
| response.headers.set("Access-Control-Allow-Origin", origin); | ||
| } | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| ## CSRF (Cross-Site Request Forgery) | ||
|
|
||
| To prevent CSRF attacks, a middleware can be used to block untrusted requests: | ||
|
|
||
| ```tsx | ||
| import { createMiddleware } from "@solidjs/start/middleware"; | ||
| import { json } from "@solidjs/router"; | ||
|
|
||
| const SAFE_METHODS = ["GET", "HEAD", "OPTIONS", "TRACE"]; | ||
| const TRUSTED_ORIGINS = ["https://another-app.com"]; | ||
|
|
||
| export default createMiddleware({ | ||
| onRequest: (event) => { | ||
| const { request } = event; | ||
|
|
||
| if (!SAFE_METHODS.includes(request.method)) { | ||
| const requestUrl = new URL(request.url); | ||
| const origin = request.headers.get("Origin"); | ||
|
|
||
| // If we have an Origin header, check it against our allowlist. | ||
| if (origin) { | ||
| const parsedOrigin = new URL(origin); | ||
|
|
||
| if ( | ||
| parsedOrigin.origin !== requestUrl.origin && | ||
| !TRUSTED_ORIGINS.includes(parsedOrigin.host) | ||
| ) { | ||
| return json({ error: "origin invalid" }, { status: 403 }); | ||
| } | ||
| } | ||
|
|
||
| // If we are serving via TLS and have no Origin header, prevent against | ||
| // CSRF via HTTP man-in-the-middle attacks by enforcing strict Referer | ||
| // origin checks. | ||
| if (!origin && requestUrl.protocol === "https:") { | ||
| const referer = request.headers.get("Referer"); | ||
|
|
||
| if (!referer) { | ||
| return json({ error: "referer not supplied" }, { status: 403 }); | ||
| } | ||
|
|
||
| const parsedReferer = new URL(referer); | ||
|
|
||
| if (parsedReferer.protocol !== "https:") { | ||
| return json({ error: "referer invalid" }, { status: 403 }); | ||
| } | ||
|
|
||
| if ( | ||
| parsedReferer.host !== requestUrl.host && | ||
| !TRUSTED_ORIGINS.includes(parsedReferer.host) | ||
| ) { | ||
| return json({ error: "referer invalid" }, { status: 403 }); | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| This example demonstrates a basic CSRF protection that verifies the `Origin` and `Referer` headers, blocking requests from untrusted origins. | ||
| Additionally, consider implementing a more robust CSRF protection mechanism, such as the [Double-Submit Cookie Pattern](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#alternative-using-a-double-submit-cookie-pattern). | ||
|
|
||
| For further guidance, you can look at the [Cross-Site Request Forgery Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html). | ||
Uh oh!
There was an error while loading. Please reload this page.