diff --git a/website/data/category_intros/web-security.md b/website/data/category_intros/web-security.md index 345b5d0d..e7ae0beb 100644 --- a/website/data/category_intros/web-security.md +++ b/website/data/category_intros/web-security.md @@ -1,16 +1,13 @@ -Every response your app sends should carry security headers, and a Python web security library like secure defines them once for Django, Flask, or FastAPI. +Sanitize HTML your users write with nh3, then add a Python web security library for response headers. secure sets CSP and HSTS once for the whole app. How to choose: -- FastAPI or Starlette: secure's ASGI middleware -- Django, Flask, and other frameworks: secure in a response hook or middleware -- Most apps: the default BALANCED preset -- Tighter CSP and stronger isolation: the STRICT preset +- User-written HTML, like comments and posts, or code moving off bleach: nh3 +- HTML rendered from user Markdown: nh3, run on the rendered output +- Security headers like CSP and HSTS, one policy across ASGI and WSGI apps: secure -secure lets you [keep one `Secure` policy object instead of scattered header strings](https://typeerror.com/secure/), and applies it through ASGI middleware, WSGI middleware, or your framework's response hooks. Start with `Secure.with_default_headers()`, which matches `Preset.BALANCED`, [the recommended default for most applications](https://github.com/TypeError/secure/blob/main/docs/usage.md#start-with-a-preset). [Review the headers it generates, and move stricter only when needed](https://typeerror.com/secure/#presets): `Preset.STRICT` tightens the CSP and isolation. +nh3 is a Python binding to ammonia, an allowlist-based HTML sanitizer built to stop cross-site scripting from untrusted user HTML. It parses fragments the same way browsers do. Call [`nh3.clean()` on each fragment](https://nh3.readthedocs.io/en/latest/#usage), and pass `tags` and `attributes` to set what it allows, like `tags={"b"}`. When you use the same options everywhere, create an [`nh3.Cleaner`](https://nh3.readthedocs.io/en/latest/#nh3.Cleaner) once and reuse it. ammonia won't linkify bare URLs or add line breaks, so its docs say to [run a markup processor before the sanitizer](https://github.com/rust-ammonia/ammonia#html-sanitization): render Markdown first, then clean the HTML it outputs. -The framework guide says to [prefer middleware when your framework makes it easy](https://github.com/TypeError/secure/blob/main/docs/frameworks.md#how-to-choose-an-integration-style) and you want app-wide coverage. On FastAPI, [add `SecureASGIMiddleware` with `app.add_middleware()`](https://github.com/TypeError/secure/blob/main/docs/frameworks.md#fastapi). On Flask, [call `set_headers(response)` in an `after_request` hook](https://github.com/TypeError/secure/blob/main/docs/frameworks.md#flask). On Django, write [a small Django middleware class](https://github.com/TypeError/secure/blob/main/docs/frameworks.md#django) that calls `set_headers()` and register it in `MIDDLEWARE`. For a framework the guide doesn't cover, [configure one `Secure` instance and apply it to the response as late as possible](https://github.com/TypeError/secure/blob/main/docs/frameworks.md#custom-frameworks) before it's sent. +secure keeps all your security headers in one policy object, instead of header strings copied across handlers and hooks. Start from `Secure.with_default_headers()`, which its docs call [the recommended starting point](https://github.com/TypeError/secure/blob/main/docs/usage.md#start-with-a-preset), configure it once, and reuse it everywhere. [Prefer its ASGI or WSGI middleware](https://github.com/TypeError/secure/blob/main/docs/frameworks.md#how-to-choose-an-integration-style) to cover the whole app, and set headers per response when you're inside an existing hook or view. The defaults are only a starting point: [adjust the Content Security Policy](https://github.com/TypeError/secure#why-use-secure) for the scripts, styles, and third-party services your app really uses. Then [test a stricter policy against the real app](https://github.com/TypeError/secure/blob/main/docs/usage.md#build-an-explicit-configuration) before rollout. -The defaults are [a starting point, not a substitute for a review against your own app](https://typeerror.com/secure/). Adjust the Content Security Policy in particular for the scripts, styles, assets, and third-party services your app actually uses. Build it with the `ContentSecurityPolicy` builder instead of a header string, and [test a stricter policy against the real app before rollout](https://github.com/TypeError/secure/blob/main/docs/usage.md#build-an-explicit-configuration). - -Security headers help the browser enforce transport, embedding, and content-loading rules, but they [don't replace output encoding, CSRF protection, authentication, or input validation](https://github.com/TypeError/secure/blob/main/docs/security_considerations.md). For web security materials beyond Python libraries, see [awesome-web-security](https://github.com/qazbnm456/awesome-web-security). +Use both, with sanitizing as the main defense. OWASP says to [sanitize HTML when users author it](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#html-sanitization), and to treat CSP as [an additional layer of defense](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#other-controls), never the primary one.