Skip to content
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

Safe-nonce extension #44

Closed
wants to merge 12 commits into from
28 changes: 28 additions & 0 deletions .github/workflows/safe-nonce.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Tests - safe-nonce

on:
push:
branches: [ main ]
paths:
- 'src/safe-nonce/**'
- '.github/workflows/safe-nonce.yml'
pull_request:
branches: [ main ]
paths:
- 'src/safe-nonce/**'
- '.github/workflows/safe-nonce.yml'
workflow_dispatch:

jobs:
test_suite:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- run: npm ci
working-directory: ./src/safe-nonce
- run: npm test
working-directory: ./src/safe-nonce
14 changes: 14 additions & 0 deletions src/safe-nonce/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
BSD Zero Clause License

Copyright (c) 2023, Alexander Petros

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
52 changes: 52 additions & 0 deletions src/safe-nonce/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
The `safe-nonce` extension can be used to improve the security of the application/web-site and help avoid XSS issues by allowing you to return known trusted inline scripts with full [nonce](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/nonce) support while blocking all other inline scripts via an appropriate [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP). It improves the base security provided with the htmx.config.inlineScriptNonce feature by forcing you to provide a unique HX-Nonce response header along with the same random nonce on all trusted inline scripts to reduce the risk of untrusted script tags being allowed to run. By default htmx is designed to trust all inline scripts provided by your server when using inlineScriptNonce feature and changes it to not trust them by default.

This feature is not a replacement for a good secure backend server implementation where all potential user input strings are sanitized by auto-escaping or a templating engine. This is just another layer of protection you can choose to add on top if needed.

To implement this extension complete the following steps:

1. Install the safe-nonce.js script in your page head or elsewhere
2. Set the hx-ext attribute in the body tag of all full page requests to `safe-nonce`
3. Generate a truly random nonce value on each server response
4. Return the random nonce in your CSP response header or in a CSP meta tag
5. Return a htmx-config meta tag to set safeInlineScriptNonce to your nonce at the top of your page head (Note that htmx only reads the first htmx-config meta tag in the page so move it as high as you can)
6. Return the `HX-Nonce` response header set to the value of your random nonce
7. Update all inline script tags you trust to include `nonce="{random-nonce}"` attribute
8. Use developer tools to test your website loads without CSP warnings in console output

When partial AJAX requests are swapped into part of the page the `HX-Nonce` header will be used to update the nonce attribute of only the trusted inline scripts to match the initial page load nonce value which allows just these scripts to execute.

Note It would be ideal to use the existing Content-Security-Policy header instead of a custom HX-Nonce header for this purpose but browsers only process CSP headers on full page loads and not the partial AJAX requests htmx uses.

This extension is not compatible with part of the htmx history feature which fetches the page from the server via AJAX when the history is not cached because it updates the page without updating the script nonces correctly so the extension forces `refreshOnHistoryMiss` config to true handle this use case.

Using this extension with the `selfRequestsOnly` default config disabled to allow external domains to be accessed via htmx requests is not recommended as it can undo some of the protections.

## Install

```html
<script src="https://unpkg.com/htmx-ext-safe-nonce@2.0.0/safe-nonce.js"></script>
```

## Usage

A sample initial page load response:

```html
HX-Nonce: "{random-nonce}"
Content-Security-Policy: "default-src 'self' 'nonce-{random-nonce}'; style-src 'self' 'nonce-{random-nonce}'"
<head>
<meta name="htmx-config" content='{"safeInlineScriptNonce":"{random-nonce}","inlineStyleNonce":"{random-nonce}"}'>
<script src="https://unpkg.com/htmx-ext-safe-nonce@2.0.0/safe-nonce.js"></script>
<script nonce="{random-nonce}">console.log('safe')</script>
</head>
<body hx-ext="safe-nonce">
...
</body>
```

A sample htmx partial ajax page response:

```html
HX-Nonce: "{another-random-nonce}"
<script nonce="{another-random-nonce}">console.log('also safe')</script>
```
Loading